3 / 7 終了と再挑戦を確かめる
開始から終了までを試し、曲・ノート・判定件数を一緒に初期化して、繰り返し遊べることを確認します。
状態によって動かす処理を変える
RhythmGame.GameStateはReady、Playing、Finished、Errorの4状態です。配布済みの処理が、開始前や終了後の入力を演奏中の入力と分けています。
| 状態 | ゲームの動作 |
|---|---|
| Ready | 開始を待つ |
| Playing | 準備時間を含め、ノートと入力を更新する |
| Finished | 音源を止め、結果を表示する |
| Error | 譜面またはSceneの設定を直す案内を表示する |
図を準備しています…
Playingには曲が鳴る前の2秒間も含まれます。演奏中のRは最初からのやり直し、待機中や終了後のSpaceは開始の操作です。
曲以外にも最初へ戻すものがある
BeginRoundとNoteSpawner.ResetNotesを開き、次の初期化を探してください。
| 対象 | 再挑戦で行うこと |
|---|---|
| 音源と時計 | 停止し、新しい開始点で再生を予約する |
| 表示中のノート | 非表示にしてプールへ戻す |
| 発行位置 | NextIndexを0にする |
| 判定件数 | Perfect・Good・Missを0にする |
| ゲーム状態 | Playingにする |
ノートのプールは、非表示にしたオブジェクトを後で再利用する入れ物です。再挑戦のたびに全ノートを破棄する必要はありません。音源だけを戻してNextIndexを戻さなかったら、何が起きるか説明してください。
一通り遊んで確認する
まず8音を何も押さずに流します。終了時のMissが8で、PerfectとGoodが0になることを確かめます。終了は最後のノートから約2秒後です。短い練習譜面なので、曲の途中で音量が下がって終わります。
次に開始し、同時押しを含めて入力します。命中と見逃しの件数を合わせると8になるはずです。途中でRを押して、件数が0へ戻り、古いノートが残らないことも確認してください。
最後に演奏中に別ウィンドウへ移り、Unityへ戻ります。フォーカスを失うと曲を止めてReadyへ戻るため、Spaceで最初から開始します。止めた位置から続ける再開ではありません。
ずれの種類から調べる場所を選ぶ
| 観察したずれ | 先に確認する場所 |
|---|---|
| ノートの中心が判定線と重ならない | Note SpawnerのJudgment OriginとRefresh |
| 全体で同じ時間だけ音とずれる | 譜面の開始位置と音声出力機器 |
| 後半ほど音とずれる | 譜面のBPMと曲時刻の基準 |
| 入力判定だけが遅く感じる | 入力を検出したフレーム、入力・表示・音声の遅延 |
曲の時計を音声システムへ合わせても、画面や機器の遅延まで消えるわけではありません。同じ曲と出力先で、一つずつ条件を変えて調べます。曲の開始位置やBPMは、次のステップからCSVで扱います。
時間の扱いを説明する
120 BPM、4 Tick/拍の全体のTick 6にあるノートを考えます。譜面の開始位置は0秒、先読み時間は2秒です。判定時刻、発行予定時刻、曲の0.5秒での判定線からの距離を求めてください。速度は毎秒180とします。
計算してから下の例と比べ、なぜ発行予定時刻が負になるのか説明してください。これは計算用の課題で、練習譜面やSceneの設定は変更しません。
計算例
1 Tickは60 / 120 / 4 = 0.125秒です。判定時刻は6 × 0.125 = 0.75秒、発行予定時刻は0.75 - 2 = -1.25秒になります。曲の0.5秒での距離は(0.75 - 0.5) × 180 = 45です。
この例から、拍とTickで予定を決め、秒へ換算し、同じ基準の時刻差を表示と判定へ使う流れを確認します。予定時刻に達した対象を取り出す方法は敵の出現や演出の開始へ、時刻差を受付幅と比べる方法は攻撃や操作の受付時間へ応用できます。
条件を変える課題
速度を180にした状態で、押す時刻を変えずに、出現時の距離を540にするには、先読み時間を何秒にしますか。式で予想し、Play中に試してください。基本の確認へ戻るときはSpeedを270、Approach Timeを2.25へ戻します。
発展として、入力イベントの時刻で判定する課題や、得点・コンボの追加を選べます。まず時刻、位置、判定、再挑戦が一通り動く状態を残してください。
- 命中と見逃しを合わせて8音を一度ずつ数えた。
- 再挑戦で曲、ノート、発行位置、判定件数が戻ることを確認した。
- 拍・Tickを秒へ換算し、負の発行予定時刻を説明できる。
- 条件を変える課題を、式と実行結果で説明できる。
- 同じ時計を使う方法と、時刻差を使う処理の別の利用例を挙げられる。
参考実装
参考実装
動作確認と条件を変える課題を終えてから比較してください。各メソッドは、編集した同名メソッドの中身を比較するための抜粋です。必要な変数や参照は開始版にあります。
NoteSpawner.cs
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);
// Prefabの原点は変えず、取り出したインスタンスだけを判定線基準にします。
note.Prepare(notes[NextIndex], this);
note.Refresh(songTime, speed);
activeNotes.Add(note);
note.gameObject.SetActive(true);
NextIndex++;
}
}NoteMotion.cs
public static float PositionY(double hitTime, double songTime, float speed)
{
return (float)((hitTime - songTime) * speed);
}HitJudge.cs
public HitResult Evaluate(double difference)
{
double distance = Math.Abs(difference);
if (distance <= perfectWindow)
return HitResult.Perfect;
if (distance <= goodWindow)
return HitResult.Good;
return HitResult.None;
}HitJudge.cs
public NoteMotion FindCandidate(List<NoteMotion> notes, int lane, double inputTime)
{
NoteMotion best = null;
double nearest = goodWindow;
foreach (var note in notes)
{
if (note.consumed || note.data.lane != lane) continue;
double distance = Math.Abs(inputTime - note.data.time);
if (distance <= nearest)
{
// 同距離なら、先に発行されたノートを優先します。
if (best != null && distance == nearest) continue;
nearest = distance;
best = note;
}
}
return best;
}