メインコンテンツへスキップ

【リズムゲームの作り方】 #8 判定結果を求める

目次
リズムゲームの作り方 - シリーズ
Part 8: この記事

前回は、キーを押したときにタイミングが最も近いノーツを調べることで、判定対象となるノーツを得られるようにしました。

今回は、そのノーツについてベストタイミングとの差を求め、PerfectやMissなどの判定評価を出す機能を作っていきます。

判定評価について
#

リズムゲームでは、ノーツを叩いたタイミングとそのノーツのベストタイミングを比較して、PerfectやGood、Missなどの判定評価を出します。

処理の内容としては、前回書いたFindNearestNoteメソッドの処理と同じく、ゲーム開始からの時刻とノーツのベストタイミングの差を求めるといったものになります。

float elapsedTime = Time.time - notesMover.startTime;
float diffTime = Mathf.Abs(elapsedTime - noteTime);

そして、求めたdiffTimeを場合分けして評価すれば良いです。

判定評価を求めるメソッドを作る
#

JudgementManagerクラスに判定評価を求める処理を追加します。

JudgementManager.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class JudgementManager : MonoBehaviour
{
    // ...(省略)

    NoteInfo FindNearestNote(int lane)
    {
        // リズムゲーム開始からの経過時間を求める
        float elapsedTime = Time.time - notesMover.startTime;

        NoteInfo nearestNote = null;
        float minDiff = float.MaxValue;

        // 1. リストに入っているすべてのノーツに対して処理
        foreach (NoteInfo noteInfo in noteInfoList)
        {
            // 2. レーンが一致していなければ無視
            if (noteInfo.lane != lane)
            {
                continue;
            }

            // 3. 時間のズレを計算
            float currentDiffTime = Mathf.Abs(elapsedTime - noteInfo.time);

            // 4. 今までの最小のズレより小さければ更新
            if (currentDiffTime < minDiff)
            {
                minDiff = currentDiffTime;
                nearestNote = noteInfo;
            }
        }

        return nearestNote;
    }

    string GetNoteEvaluation(float noteTime)
    {
        // 時間のズレを計算
        float elapsedTime = Time.time - notesMover.startTime;
        float diffTime = Mathf.Abs(elapsedTime - noteTime);

        // 判定評価を返す
        if (diffTime < 0.05f)
        {
            return "Perfect";
        }
        else if (diffTime < 0.1f)
        {
            return "Good";
        }
        else if (diffTime < 0.15f)
        {
            return "Miss";
        }
        else
        {
            return "NonTarget";
        }
    }
}

ノーツのベストタイミングと今の時刻の差を計算し、それをif文で場合分けすることで判定評価ができるようになりました。

今回は、判定幅は以下のようにしました。(1秒 = 1000msです)

判定評価 判定幅 (秒換算)
Perfect 50ms 0.05秒
Good 100ms 0.1秒
Miss 150ms 0.15秒

150msより離れていた場合は、判定評価の対象外としています。これは、ノーツが近くにないときに適当にキーを押した場合に、関係ない遠くのノーツまで判定に巻き込まれてしまうのを防ぐためです。

判定評価の動作確認
#

前回と同様に、Judgeメソッドを書き換えて判定評価をコンソールに表示して動作を確認してみましょう。

JudgementManager.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class JudgementManager : MonoBehaviour
{
    // ...(省略)

    void Judge(int lane)
    {
        // 最も近いノーツを探す
        NoteInfo nearestNote = FindNearestNote(lane);

        // もしノーツが見つからなかったら何もしない
        if (nearestNote == null)
        {
            return;
        }

        // ノーツのベストタイミングを渡して、判定評価を取得する
        string evaluation = GetNoteEvaluation(nearestNote.time);

        // 判定対象外(NonTarget)でなければ、判定結果をコンソールに表示する
        if (evaluation != "NonTarget")
        {
            Debug.Log($"レーン{lane}: {evaluation}!");
        }
    }

    // ...(省略)
}

解説
#

前回はnearestNote.timeをそのまま表示するだけでしたが、今回はGetNoteEvaluation(nearestNote.time)とすることで、ノーツのベストタイミングを渡し、その結果(“Perfect"などの文字列)をevaluationという変数が受け取っています。

そして、GetNoteEvaluationは、ノーツと叩いた時間が150ms以上離れていた場合に対象外として"NonTarget"を返すように記述しました。 そのため、if (evaluation != "NonTarget")として、結果が"NonTarget"ではない(=150ms以内で叩けた)ときだけ画面に判定を表示するようにしています。

動作確認をしてみよう
#

ここまで書けたら、Unityエディタに戻ってゲームを再生してみましょう。

ノーツが落ちてくるタイミングに合わせて対応するキー(D, F, J, K)を押してみてください。Consoleウィンドウに「レーン1: Perfect!」や「レーン2: Miss!」のように、タイミングに応じた判定結果が表示されれば成功です!

情報

現在は判定したノーツを消す処理を入れていないため、タイミング良くキーを連打すると1つのノーツに対して何度も判定されてしまいます。この問題は次回の「判定したノーツを消す」処理を入れることで解決しますので、今は複数回判定されてしまっても問題ありません!

まとめ
#

今回はノーツのタイミングと叩いたタイミングの差を使ってノーツの判定評価をして、コンソール上に表示することができました。

内部の処理はほとんどできてきましたね。まだ実装していない処理は、スルーしたノーツをMissにする処理と、判定されたノーツを消す処理です。

次回はこれらの実装をしていきます!

リズムゲームの作り方 - シリーズ
Part 8: この記事