节奏游戏中音符的同步
节奏游戏中音符的同步
在本文中,我会分享一些在Unity中开发节奏游戏的重要建议。
1)使用AudioSettings.dspTime 跟踪歌曲的位置,而不是使用Time.timeSinceLevelLoad。
2)使用歌曲的位置来更新移动。
3)不要通过每帧的时差来更新音符。
位置跟踪
在所有节奏游戏中,我们必须跟踪歌曲位置,以确定是否应该生成音符。如下所示:
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
}
我们转换bpm到secPerBeat后面方便使用。secPerBeat将用于计算节拍中的歌曲位置,这对音符生成非常重要。
我们用dsptimesong记录歌曲的开始时间。使用AudioSettings.dspTime 跟踪歌曲的位置,而不是使用Time.timeSinceLevelLoad。因为Time.timeSinceLevelLoad只在每个帧中更新,而AudioSettings.dspTime更新频率更高,因为它是音频系统的定时器。为了保持歌曲的速度,我们必须使用音频系统的定时器来避免帧更新和音频更新之间的时间差引起的延迟。
在Update()函数中,我们通过AudioSettings.dspTime计算歌曲的位置:
void Update()
{
songPosition = (float) (AudioSettings.dspTime - dsptimesong);
songPosInBeats = songPosition / secPerBeat;
}
如下音符:
的位置是1,2,2.5,3,3.5,4.5,节拍持续0.5秒。所以,如果歌曲(aka. songPosition == 1.75)已经过了1.75秒,我们知道我们现在达到了歌曲中的1.75 (songPosition)/ 0.5 (secPerBeat)= 3.5 节奏,而且应该产生3.5节的音符。
歌曲信息
移动到我们记录歌曲信息的字段:
float bpm;
float[] notes;
int nextIndex = 0;
为了简单起见,我演示的歌曲只有一个轨迹的音符。
bpm是一首歌的每分钟的节拍数。正如我们所看到的,secPerBeat为了方便起见被转换了。
notes是一个数组,保持歌曲中音符的位置。例如,notes将初始化为{1f,2f, 2.5f, 3f, 3.5f, 4.5f}如下:
nextIndex只是一个用于遍历数组的整数。它被初始化为0,因为产生的下一个音符是该歌曲的第一个音符。每当一个音符产生时nextIndex会自增。
产生音符
我们确定是否应该在Update()函数中产生一个音符前。但我们应该首先确定显示节拍的数量。
例如,在下面的曲谱中,
当前的歌曲position-in-beats为1,但是节拍3已经产生,意味着提前显示3个节拍。
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()移动我们的音符:
void Update()
{
transform.position = Vector2.Lerp(
SpawnPos,
RemovePos,
(BeatsShownInAdvance - (beatOfThisNote - songPosInBeats)) / BeatsShownInAdvance
);
}