Skip to content
第03回 リズムゲーム2

2 / 7 入力時刻との差で判定する ​

このステップの目的

押したレーンの候補を探し、早い・遅いを時刻差として判定して、一つのノートを一度だけ数えます。

前のステップまでにそろえた曲の時刻を、入力判定にも使います。時間差を受付範囲と比べる考え方は、攻撃や操作を受け付ける時間の判定にも使えます。

タイミングと許容誤差 ​

ユーザーの入力時刻からノートの到達予定時刻を引くと、早い場合は負、遅い場合は正になります。
例えば曲の3秒目のノートを、2.97秒でヒットした場合は、0.03秒早い入力となります。

text
時刻差 = 入力時刻 - 判定時刻

差の符号で早い・遅いを、絶対値でずれの大きさを調べます。

入力を予定時刻と完全に一致させるのは難しく、ゲーム側で入力を確認する機会もフレームごとです。
また、プレイする人間にも反応の遅れや操作の誤差があり、入力を正確に判定しすぎると遊びにくくなってしまいます。

そこで、予定時刻の前後に入力を受け付ける範囲を設けます。
この範囲が、ゲームの判定で許される許容時間です。

このサンプルでは、前後40ミリ秒以内をPerfect、前後100ミリ秒以内をGoodとしています。

これは作例の設定であり、すべてのリズムゲームに共通する値ではありません。
自由に調節してみましょう。
進行の早い曲であれば、誤差時間自体を調整してもいいかもしれません。

早い入力と遅い入力を数値にする ​

タイミングと許容誤差で扱った時刻差を、判定の処理へ使います。この作例の入力時刻は、入力を確認したフレームの曲時刻です。

text
時刻差 = 入力時刻 - ノートの判定時刻

この作例のPerfectは前後40ミリ秒以内、Goodは前後100ミリ秒以内です。次の入力がどの判定になるか、秒とミリ秒を換算して考えてください。

入力時刻判定時刻時刻差
2.970秒3秒−0.030秒
3.080秒3秒+0.080秒
3.120秒3秒+0.120秒

判定の良さは差の絶対値で決めます。HitJudge.csの配布済みのEvaluateを、次のコードと比べてください。

csharp
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;
}

Math.Absは絶対値を求めます。狭いPerfectの範囲から調べるので、Goodにも入る入力を先にPerfectと判定できます。範囲外ならNoneです。判定幅はこの作例の設定で、リズムゲーム共通の基準ではありません。

押したレーンの候補を探す ​

押したレーンの未判定ノートから、受付範囲内で入力時刻に最も近いものを選びます。配布済みのFindCandidateを次のコードと比べます。bestは現在の候補、nearestは候補までの時間差です。

csharp
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;
}

consumedがtrueなら判定済みです。別レーンと判定済みをcontinueで飛ばし、残りの候補を比べます。同じ距離なら先に見つけた候補を残します。候補がなければnullなので、空のレーンを押してもMissは増えません。

表示が残っても再判定しない ​

配布済みのTryHitは候補の時刻差をEvaluateへ渡し、命中したらNoteMotion.Hitを呼びます。Hitはconsumedをtrueにしてから、ノートを白く光らせます。約0.2秒の演出中も判定済みなので、連打して二度数えることはありません。演出後は非表示にして再利用します。

押さなかったノートは、次の条件でMissになります。±100ミリ秒の端はGoodの範囲内です。100ミリ秒を過ぎた未判定ノートをMissにします。

csharp
return !note.consumed && songTime - note.data.time > goodWindow;

これはHitJudge.IsMissの読み取り用の抜粋です。RhythmGame.Advanceがこの結果を使い、通過したノートを一度だけ消費します。

入力と表示を試す ​

保存してPlayし、左からD、F、J、Kで押してください。Perfect・Goodに加え、早い場合はEARLY、遅い場合はLATEが表示されます。ほぼ差がない入力はJUSTです。レーンは押した瞬間に光り、命中時はノートも光ります。

練習譜面の同時押しは、左端と左から3番目の組み合わせです。DとJを一緒に押し、二つが別々に判定されることを確認します。RhythmGame.Updateでは各レーンを独立して調べています。

何も押さずに流す、別レーンを押す、命中直後に同じキーを連打する、の三つも試してください。Missの増え方と命中件数を見て、一つを二度数えていないか確かめます。

Perfect Windowだけを0.040から0.020へ変えると、0.030秒差の入力は何になりますか。まず計算し、Playでは判定の傾向を比べます。実際の入力は毎回同じ時刻に押せるとは限りません。確認後は0.040へ戻します。

チェックリスト
  • 剰余を使う理由と、入力の時刻差の符号を説明できる。
  • 時刻差の符号と絶対値の役割を説明できる。
  • 同レーン、未判定、受付範囲内という候補の条件を説明できる。
  • 同時押しを判定し、連打で件数が二重に増えないことを確認した。
  • 判定幅を変えた結果を説明し、元の値へ戻した。