节奏游戏开发时的万般阻难

施工中……

音符同步

senator

你需要[RequireComponent(typeof(AudioSource))]来规定该元件可被作为音游元件。

  1. 使用AudioSettings.dspTime 跟踪歌曲的位置,而不是使用 Time.timeSinceLevelLoad

  2. 使用歌曲的位置来更新移动。

  3. 不要通过每帧的时差来更新音符。

位置跟踪

在所有节奏游戏中,我们必须跟踪歌曲位置,以确定是否应该生成音符。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
float songPosition;
// 节拍当前位置
float songPosInBeats;
// 节拍的持续时间
float secPerBeat;
float dsptimesong;
// We initialize these fields in the Start() function:
void Start() {
secPerBeat = 60f / bpm;
dsptimesong = (float) AudioSettings.dspTime;
// 开始播放音乐
GetComponent<AudioSource>().Play();
}

我们转换bpmsecPerBeat后面方便使用。secPerBeat将用于计算节拍中的歌曲位置,这对音符生成非常重要。

我们用dsptimesong记录歌曲的开始时间。使用AudioSettings.dspTime跟踪歌曲的位置,而不是使用Time.timeSinceLevelLoad。因为Time.timeSinceLevelLoad只在每个帧中更新,而AudioSettings.dspTime更新频率更高,因为它是音频系统的定时器。为了保持歌曲的速度,我们必须使用音频系统的定时器来避免帧更新和音频更新之间的时间差引起的延迟。

Update()函数中,我们通过AudioSettings.dspTime计算歌曲的位置:

1
2
3
4
5
void Update()
{
songPosition = (float) (AudioSettings.dspTime - dsptimesong);
songPosInBeats = songPosition / secPerBeat;
}

歌曲信息

移动到我们记录歌曲信息的字段:

1
2
3
float bpm;
float [] notes;
int nextIndex = 0;

为了简单起见,我演示的歌曲只有一个轨迹的音符。

bpm是一首歌的每分钟的节拍数。正如我们所看到的,secPerBeat为了方便起见被转换了。

notes是一个数组,保持歌曲中音符的位置。例如,notes将初始化为{1f,2f, 2.5f, 3f,3.5f, 4.5f}

产生音符

我们确定是否应该在Update()函数中产生一个音符前。但我们应该首先确定显示节拍的数量。

例如曲谱中,当前的歌曲position-in-beats为1,但是节拍3已经产生,意味着提前显示3个节拍。

1
2
3
4
5
6
songPosInBeats = songPosition / secPerBeat;, add the following lines:
if (nextIndex < notes.Length && notes[nextIndex] < songPosInBeats beatsShownInAdvance)
{
Instantiate( /* Music Note Prefab */ );
nextIndex ;
}

我们首先检查歌曲(nextIndex < notes.Length)中是否已经没有音符,如果还有音符,那么我们再看看歌曲是否到达要播放下一个音符的节奏(notes[nextIndex] < songPosInBeats + beatsShownInAdvance)。如果满足条件,产生音符,nextIndex自增,以便nextIndex保持下一个音符的产生。

移动音符

最后,如何按照这首歌来移动我们产生的音符。不要通过帧的时差来移动它们。

始终按照歌曲的位置更新移动,因为

  1. 音频定时器与帧定时器有时差

  2. 节拍可能在两帧中间

那么,怎么移动音符呢?插入法!为了简单起见,我将把所有的代码放在MusicNote类上,并且只在Update()移动我们的音符:

1
2
3
4
5
6
7
void Update() {
transform.position = Vector2.Lerp(
SpawnPos,
RemovePos,
(BeatsShownInAdvance - (beatOfThisNote - songPosInBeats) ) / BeatsShownInAdvance
);
}

判定提前

姑且定义以下几种判定时间点:

  • CHECK_PERFECT
  • CHECK_GREAT
  • CHECK_MISS

每个音符到达dspTime - CHECK_MISS时触发miss判定
南梦宫所提出的判定方案是

视觉反馈

额外音效

processing