1 / 7 ノートの操作
判定時刻から発行する対象を選び、残り時間を位置へ変換して、同じ時間軸でノートを動かします。
第02回で、曲の時刻とノートの判定時刻を秒へそろえました。ここでは、その二つの時刻から表示を始める条件と、画面上の位置を求めます。ノートの発行は、表示用のオブジェクトを用意して有効にする処理です。
NoteDataのlaneは左から0〜3、timeは曲の何秒で押すかを表します。次は、左端を曲の開始から3秒で押すノートの説明用の例です。
new NoteData(0, 3.0)実習では配布済みの8音の練習譜面を使います。時刻順の配列で、同時押しも含みます。ここで譜面を書き換える必要はありません。
ノートの発行
発行予定時刻に達した対象を選ぶ
approachTimeは、判定時刻の何秒前にノートを用意するかを表します。判定時刻が3秒、先読み時間が2.25秒なら、発行する予定時刻は0.75秒です。
発行予定時刻 = 判定時刻 - 先読み時間
発行する条件 = 曲の現在時刻 >= 発行予定時刻画面の更新は一定間隔とは限りません。あるフレームで0.74秒、次のフレームで0.76秒を読んだ場合、0.75秒との完全一致を待つと発行できません。予定時刻に達したかを調べれば、次の更新で対象を取り出せます。
配列は時刻順なので、NextIndexが示す次のノートから調べます。同じ時刻のノートが複数ある場合も、一度の更新で条件を満たすものをすべて発行します。
時刻順の配列からノートを発行する
NoteSpawner.csの配布済みのSpawnDueを、次のコードと比べてください。位置を計算するRefreshが使うPositionYは、次の節で確認します。
public void SpawnDue(double songTime)
{
while (NextIndex < notes.Length && notes[NextIndex].time <= songTime + approachTime)
{
var note = _available.Count > 0 ? _available.Pop() : Instantiate(prefab, root);
note.gameObject.SetActive(false);
note.Prepare(notes[NextIndex], this);
note.Refresh(songTime, speed);
activeNotes.Add(note);
note.gameObject.SetActive(true);
NextIndex++;
}
}time <= songTime + approachTimeは、先ほどの発行条件と同じ意味です。配列の末尾を越えないよう、先にNextIndex < notes.Lengthを確認します。whileで繰り返すので、同時刻の二つのノートも同じフレームで用意できます。
_availableは、使い終えて非表示になったノートを再利用するための入れ物です。あれば取り出し、なければPrefabから生成します。Prepareで前回の判定と見た目を初期化し、Refreshで現在時刻の位置へ置いてから表示します。activeNotesに表示中のノートを登録し、NextIndex++で次の対象へ進みます。
フレームの更新が予定より遅れた場合も、発行後の位置はその時点の曲時刻から求めます。予定時刻へさかのぼって画面を描くのではなく、現在の位置を計算する処理です。
ノートの移動
残り時間を判定線までの距離へ変換する
判定時刻から曲の現在時刻を引くと、押すまでの残り時間になります。その値に毎秒の移動速度を掛け、判定線からの距離へ変換します。
残り時間 = 判定時刻 - 曲の現在時刻
判定線からの距離 = 残り時間 × 毎秒の移動速度3秒で押すノートを毎秒270の速さで動かす場合、曲の1秒では残り2秒なので、距離は540です。
| 曲の現在時刻 | 残り時間 | 判定線からのY距離 |
|---|---|---|
| 1秒 | 2秒 | 540、線より上 |
| 3秒 | 0秒 | 0、線に重なる |
| 3.1秒 | −0.1秒 | −27、線を通過 |
曲の2秒での距離を計算してから、NoteMotion.csの配布済みのPositionYを次のコードと比べてください。
public static float PositionY(double hitTime, double songTime, float speed)
{
return (float)((hitTime - songTime) * speed);
}speedの単位はCanvas上の座標単位/秒です。時刻差はdoubleで計算し、UIへ渡す距離をfloatへ変換します。hitTime - songTimeが0になる時刻では、速度がいくつでも距離は0です。
この処理は、前の位置へ移動量を足すのではなく、現在時刻に対応する位置を毎回求めます。フレームの間隔が変わっても、次の更新で曲の現在位置に対応する距離を計算できます。画面更新の遅れがなくなるわけではなく、更新時の位置を音声の時間軸に合わせる方法です。
判定線からの距離をUIの座標へ変換する
距離が0でも、親オブジェクト内でのY座標が0とは限りません。ノートの親の領域と、判定線のRectTransformは別だからです。配布済みのRefreshは、距離に判定線の座標を足して配置します。
var x = _spawner.LaneCenterX(data.lane);
var y = _spawner.JudgmentY + PositionY(data.time, songTime, speed);
rect.anchoredPosition = new Vector2(x, y);LaneCenterXはレーンの中心、JudgmentYはノートの親の座標で表した判定線の中心です。今回は配布済みの座標変換を使い、時間から距離を求める部分を担当します。
速度と先読み時間を変えて比較する
UnityのConsoleにコンパイルエラーがないことを確認します。Chart SourceのUse CsvをオフにしてPlayすると、固定譜面の8音が流れます。入力判定も配布済みなので、この段階から演奏できます。判定の処理は次のステップで確認します。
Note SpawnerのSpeedを270から180へ変える前に、出現時の高さと判定線へ届く時刻を予想してください。Play中に変更して比べ、確認後は270へ戻します。速度を下げると近い位置からゆっくり進みますが、判定時刻は同じです。
次に、速度270のままApproach Timeを2.25から2へ変更してください。発行予定時刻は遅くなり、発行時の距離は607.5から540へ変わります。確認後は2.25へ戻してください。
表示領域の外のノートはRectMask2Dで隠れるため、発行した時刻と、画面で見え始める時刻が一致しない場合があります。発行はオブジェクトを有効にする処理、見え始める時刻は表示領域にも依存する結果として区別します。
- 発行予定時刻を、判定時刻と先読み時間から求められる。
- 完全一致ではなく、予定時刻に達したかを調べる理由を説明できる。
- 同時刻のノートを
whileで発行する理由を説明できる。 - 残り時間と速度から距離を計算し、距離0とUIの座標0を区別できる。
- 速度と先読み時間を一つずつ変え、予想と結果を比べた。
-
Speedを270、Approach Timeを2.25へ戻した。