《Unityで作るリズムゲーム》學習筆記(九):「分數與Combo」-完結

書名:《Unityで作るリズムゲーム》

作者:長崎大学マルチメディア研究会

計算與邏輯

  • 對於遊戲分數和Combo的計算,不同音遊有不同的計分方法

    • 最大得分

      • 與note數量無關

      • 取決於note數量

    • Combo對得分的影響

      • 不影響得分

      • Combo數越高,每個note的得分越高

    • Long Note分數/Combo處理

      • 起/終點各自撃中增加分數和Combo數

      • 分數和Combo數僅在起點/終點增加

      • Long Note進入處理狀態後,Combo和分數會持續增加

  • 在本案例中,會使用以下標準

    • 最大得分取決於notes數量

    • Combo不影響得分

    • 起點和終點被分別撃中時都有加分和Combo

  • 評價管理器(EvaluationManager)

    • 在該管理器中,主要需要處理分數的增加和Combo數的計算,為了進行這兩者的計算,首先要準備一些字段。

      • //分數上限
        public static readonly float theoreticalScore = 1000000;
        //評價得分倍率
        static readonly Dictionary<JudgementType, float> scoreAddRates = new Dictionary<JudgementType, float>
        {
          {JudgementType.PERFECT, 1.0f },
          {JudgementType.GOOD, 0.5f },
          {JudgementType.BAD, 0.2f },
          {JudgementType.MISS, 0.0f }
        };

        public static float score; //當局得分
        public static int combo; //當局連撃數
        public static int maxCombo; //譜面遊玩歷史最大連撃數
        public static int theoreticalCombo; //該譜面的可能最大連撃數
        //每種評價的數量
        public static Dictionary<JudgementType, int> judgementCounts = new Dictionary<JudgementType, int>();
    • 要知道每一個note佔多少分,首先要計算當前譜面有多少個note。

      • //值初始化
        private void Start()
        {
           score = 0f;
           combo = 0;
           maxCombo = 0;
           judgementCounts[JudgementType.PERFECT] = 0;
           judgementCounts[JudgementType.GOOD] = 0;
           judgementCounts[JudgementType.BAD] = 0;
           judgementCounts[JudgementType.MISS] = 0;

           //最大可能連撃數
           theoreticalCombo =
               PlayerController.beatMap.noteDatas
              .Count(note => note.noteType == NoteType.Single) +
               PlayerController.beatMap.noteDatas
              .Count(note => note.noteType == NoteType.Long) * 2;
        }

        public static void OnHit(JudgementType judgementType)
        {
           combo++;
           //每個note的分數
           float addValue = theoreticalScore / theoreticalCombo;
           //每個note的分數 * 得分倍率
           score += addValue * scoreAddRates[judgementType];
           judgementCounts[judgementType]++;
        }
    • Combo的判定在Note的撃中和Miss時都要記錄(增加/斷連)

      • private void Update()
        {
           //持續更新最大Combo數
           maxCombo = Mathf.Max(combo, maxCombo);
        }
        //斷連
        public static void OnMiss()
        {
           combo = 0;
           judgementCounts[JudgementType.MISS]++;
        }
    • 總體代碼

      • using System.Collections.Generic;
        using System.Linq;
        using UnityEngine;

        public class EvaluationManager : MonoBehaviour
        {
           //分數上限
           public static readonly float theoreticalScore = 1000000;
           //評價得分倍率
           static readonly Dictionary<JudgementType, float> scoreAddRates = new Dictionary<JudgementType, float>
          {
              {JudgementType.PERFECT, 1.0f },
              {JudgementType.GOOD, 0.5f },
              {JudgementType.BAD, 0.2f },
              {JudgementType.MISS, 0.0f }
          };

           public static float score; //當局得分
           public static int combo; //當局連撃數
           public static int maxCombo; //譜面遊玩歷史最大連撃數
           public static int theoreticalCombo; //該譜面的可能最大連撃數
           //每種評價的數量
           public static Dictionary<JudgementType, int> judgementCounts = new Dictionary<JudgementType, int>
          {
              { JudgementType.PERFECT, 0 },
              { JudgementType.GOOD, 0 },
              { JudgementType.BAD, 0 },
              { JudgementType.MISS, 0 },
          };

           //值初始化
           private void Start()
          {
               score = 0f;
               combo = 0;
               maxCombo = 0;
               judgementCounts[JudgementType.PERFECT] = 0;
               judgementCounts[JudgementType.GOOD] = 0;
               judgementCounts[JudgementType.BAD] = 0;
               judgementCounts[JudgementType.MISS] = 0;

               //最大可能連撃數
               theoreticalCombo =
                   PlayerController.beatMap.noteDatas
                  .Count(note => note.noteType == NoteType.Single) +
                   PlayerController.beatMap.noteDatas
                  .Count(note => note.noteType == NoteType.Long) * 2;
          }

           private void Update()
          {
               //持續更新最大Combo數
               maxCombo = Mathf.Max(combo, maxCombo);
          }

           public static void OnHit(JudgementType judgementType)
          {
               combo++;
               //每個note的分數
               float addValue = theoreticalScore / theoreticalCombo;
               //每個note的分數 * 得分倍率
               score += addValue * scoreAddRates[judgementType];
               judgementCounts[judgementType]++;
          }

           //斷連
           public static void OnMiss()
          {
               combo = 0;
               judgementCounts[JudgementType.MISS]++;
          }
        }
  • 在SingleNoteController和LongNoteController的判定後方法中調用評價管理器中對應的處理

    • SingleNoteController

      • void CheckMiss()
        {    
           if (noteProperty.secBegin - PlayerController.currentSec
               < -JudgementManager.judgementWidth[JudgementType.BAD])
          {
               //斷連
               EvaluationManager.OnMiss();
               //...
          }
        }

        //只有非Miss的Note才會被被撃中並消除
        public override void OnKeyDown(JudgementType judgementType)
        {
           if(judgementType != JudgementType.MISS)
          {
               //加分加Combo
               EvaluationManager.OnHit(judgementType);
               //...
          }
        }
    • LongNoteController

      • void CheckMiss()
        {
           //沒有進入處理狀態的起點通過判定線且超過了BAD判定範圍(斷頭押)
           if(!isProcessed &&
              noteProperty.secBegin - PlayerController.currentSec <
              -JudgementManager.judgementWidth[JudgementType.BAD])
          {
               //起點Miss,終點也Miss
               EvaluationManager.OnMiss();
               EvaluationManager.OnMiss();
               //...
          }

           //進入了處理狀態的終點通過了判定線且超過了BAD判定範圍(Hold太久斷尾押)
           if(isProcessed &&
              noteProperty.secEnd - PlayerController.currentSec <
              -JudgementManager.judgementWidth[JudgementType.BAD])
          {
               EvaluationManager.OnMiss();
               //...
          }
        }


        public override void OnKeyDown(JudgementType judgementType)
        {
           //按下時,LongNote在BAD判定範圍內
           if(judgementType != JudgementType.MISS)
          {
               EvaluationManager.OnHit(judgementType);
               //...
          }
        }

        //抬手
        public override void OnKeyUp(JudgementType judgementType)
        {
           //尾判評價
           if(judgementType != JudgementType.MISS)
          {
               EvaluationManager.OnHit(judgementType);
          }
           else
          {
               EvaluationManager.OnMiss();
          }
           //...
        }