書籍資訊

  • 書名:《Unityで作るリズムゲーム》
  • 作者:長崎大学マルチメディア研究会

這一篇在講什麼

前一篇把資料結構與控制器先搭起來了,但在真正導入 BMS 之前,還有一個很重要的驗證步驟:

先不用譜面檔,只靠手動建立的測試資料,看看這套結構能不能把 Note 正常生出來。

這一篇的重點就是:

  • 手動建立測試用 BeatMap
  • 驗證 Single Note 的生成
  • 再擴充成 Single Note 與 Long Note 混合生成

為什麼要先做手動生成測試

如果在資料模型剛建好時就直接接 BMS 解析,後面一旦畫面不對,很難立刻判斷問題到底出在:

  • 譜面資料讀取錯了
  • 拍子換算錯了
  • 還是 Note Prefab / 控制器本身就有問題

所以先用手動資料測一次,等於先把「生成流程」和「譜面解析」這兩層拆開驗證。

Single Note 生成測試

預想畫面

測試方式

這裡先直接在 PlayerController 裡手動建立一份 BeatMap,把單點 Note 與 BPM 變化直接寫死:

[SerializeField] GameObject prefabSingleNote;

private void Awake()
{
    currentSec = 0f;
    currentBeat = 0f;

    beatMap = new BeatMap();
    beatMap.noteDatas = new List<NoteProperty>()
    {
        new NoteProperty(0, 0, 0, NoteType.Single),
        new NoteProperty(1, 1, 1, NoteType.Single),
        new NoteProperty(2, 2, 2, NoteType.Single),
        new NoteProperty(3, 3, 1, NoteType.Single),
        new NoteProperty(4, 4, 0, NoteType.Single),
        new NoteProperty(4, 4, 4, NoteType.Single),
        new NoteProperty(5, 5, 3, NoteType.Single),
        new NoteProperty(6, 6, 2, NoteType.Single),
        new NoteProperty(7, 7, 3, NoteType.Single),
        new NoteProperty(8, 8, 4, NoteType.Single),
    };

    beatMap.tempoChangeDatas = new List<TempoChange>()
    {
        new TempoChange(0, 60f),
        new TempoChange(2, 120f),
        new TempoChange(4, 60f),
        new TempoChange(6, 120f)
    };

    foreach (NoteProperty noteProperty in beatMap.noteDatas)
    {
        Instantiate(prefabSingleNote)
            .GetComponent<NoteControllerBase>()
            .noteProperty = noteProperty;
    }
}

這段程式碼的目的很單純:

  • 手動準備測試譜面
  • 手動準備測試 BPM 段
  • 確認每顆 Single Note 都能依據資料正常生成與移動

在 Unity 編輯器裡怎麼接

1. 把控制器掛到 Single Note 預製體

2. 建立 Player 物件並指定 Prefab

PlayerController 掛到一個空物件上,然後把 SingleNote 預製體指定給欄位。

3. 播放觀察結果

如果畫面能正常跑起來,代表目前:

  • 拍點資料有被正確灌進 Prefab
  • 位置更新邏輯能正常工作
  • BPM 變速對畫面流動也有產生作用

Long Note 生成測試

Single Note 驗證通過後,下一步就是把 Long Note 也加進同一套生成流程。

預想畫面

測試方式

這次同時準備兩種 Prefab:

[SerializeField] GameObject singleNotePrefab = null;
[SerializeField] GameObject longNotePrefab = null;

private void Awake()
{
    currentSec = 0f;
    currentBeat = 0f;

    beatMap = new BeatMap();
    beatMap.noteDatas = new List<NoteProperty>()
    {
        new NoteProperty(0, 0, 0, NoteType.Single),
        new NoteProperty(1, 1, 1, NoteType.Single),
        new NoteProperty(2, 3, 2, NoteType.Long),
        new NoteProperty(3, 4, 1, NoteType.Long),
        new NoteProperty(4, 8, 0, NoteType.Long),
        new NoteProperty(4, 5, 4, NoteType.Long),
        new NoteProperty(5, 6, 3, NoteType.Long),
        new NoteProperty(6, 7, 2, NoteType.Single),
        new NoteProperty(7, 8, 3, NoteType.Single),
        new NoteProperty(8, 9, 4, NoteType.Single),
    };

    beatMap.tempoChangeDatas = new List<TempoChange>()
    {
        new TempoChange(0, 60f),
        new TempoChange(2, 120f),
        new TempoChange(4, 60f),
        new TempoChange(6, 120f)
    };

    foreach (NoteProperty noteProperty in beatMap.noteDatas)
    {
        Instantiate(noteProperty.noteType == NoteType.Single ?
                    singleNotePrefab : longNotePrefab)
            .GetComponent<NoteControllerBase>()
            .noteProperty = noteProperty;
    }
}

這裡真正重要的,是 noteType 開始不只存在於資料裡,而是直接決定場上生成哪一種 Note 物件。

在 Unity 編輯器裡怎麼接

1. 把控制器掛到 Long Note 預製體

2. 指定 Long Note Prefab

3. 播放觀察結果

這時候如果畫面能正常出現長押結構,就代表:

  • Long Note 的起點與終點資料有被正確解讀
  • LongNoteController 的位置更新與中段拉伸都正常
  • 同一份譜面資料已經能分流生成不同型別的 Note

我自己的整理

這一篇最有價值的地方,是它沒有急著一步到位去接 BMS,而是先把「生成」這個核心動作單獨拿出來驗證。

這種做法對節奏遊戲特別有用,因為畫面一旦不對,問題來源可能很多。先用手動資料測試,等於先證明:

  • 控制器沒壞
  • Prefab 沒接錯
  • BPM 與拍點邏輯至少能驅動畫面

後面真的去讀檔時,排錯範圍就會小很多。

先記住的重點

  • 在正式接譜面檔前,先用手動資料驗證生成流程是很划算的做法。
  • Single Note 驗證的是基本位置更新與生成流程。
  • Long Note 驗證的是起點、終點與中段拉伸是否正確。
  • noteType 不只是資料欄位,它也能直接成為生成分流的依據。