← 筆記

《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();
              }
              //...
          }