将游戏与音频和音乐同步¶
介绍¶
在任何应用程序或游戏中,声音和音乐播放都会有轻微的延迟。对于游戏来说,这种延迟通常很小,可以忽略不计。在调用任何play()函数几毫秒后,声音效果就会出现。对于音乐来说,这并不重要,因为在大多数游戏中,它不会与游戏进行交互。
不过,对于某些游戏(主要是节奏游戏),可能需要将玩家的动作与歌曲中发生的事情同步(通常与bpm同步)。为此,有更精确的时间信息来精确播放位置是有用的。
很难达到非常低的播放定时精度。这是因为在音频播放期间有许多因素在播放:
根据所用音频缓冲区的大小(检查项目设置中的延迟),音频以块(而不是连续)的形式混合。
混合的音频块不会立即播放。
图形API延迟显示两到三帧。
在电视上播放时,可能会由于图像处理而增加一些延迟。
减少延迟的最常见方法是缩小音频缓冲区(同样,在项目设置中编辑延迟设置)。问题是,当延迟太小时,混音需要相当多的CPU。这会增加跳过的风险(声音中的裂缝,因为混音回调丢失)。
这是一个常见的权衡,所以Godot提供了合理的违约,不需要修改。
最后,问题不在于这种轻微的延迟,而在于需要它的游戏的图形和音频的同步。从Godot3.2开始,添加了一些助手以获得更精确的播放时间。
使用系统时钟进行同步¶
如前所述,如果您致电 AudioStreamPlayer.play() ,声音不会立即开始,但当音频线程处理下一个块时。
不能避免这种延迟,但可以通过调用 AudioServer.get_time_to_next_mix() .
输出延迟(混合后发生的情况)也可以通过调用 AudioServer.get_output_latency() .
加上这两个,几乎可以准确地猜测扬声器中的声音或音乐何时开始播放:
var actual_play_time = AudioServer.get_time_to_next_mix() + AudioServer.get_output_latency()
$Song.play()
这样,在 _process() 可能:
var time_begin
var time_delay
func _ready()
time_begin = OS.get_ticks_usec()
time_delay = AudioServer.get_time_to_next_mix() + AudioServer.get_output_latency()
$Player.play()
func _process(delta):
# Obtain from ticks.
var time = (OS.get_ticks_usec() - time_begin) / 1000000.0
# Compensate for latency.
time -= time_delay
# May be below 0 (did not being yet).
time = max(0, time)
print("Time is: ", time)
不过,从长远来看,由于声音硬件时钟从来没有与系统时钟完全同步,因此时间信息将缓慢地漂移。
对于一个节奏游戏,一首歌在几分钟后开始和结束,这种方法是好的(这是推荐的方法)。对于播放时间可能更长的游戏,游戏最终会失去同步,需要另一种方法。
使用声音硬件时钟进行同步¶
使用 AudioStreamPlayer.get_playback_position() 要获得歌曲的当前位置听起来很理想,但并不像现在这样有用。这个值将以块的形式递增(每次音频回调混合一个声音块时),因此许多调用可以返回相同的值。此外,由于前面提到的原因,该值也将与扬声器不同步。
为了补偿“分块”输出,有一个函数可以帮助: AudioServer.get_time_since_last_mix() .
将此函数的返回值添加到 get_playback_position() 提高精度:
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
要提高精度,请减去延迟信息(音频混合后需要多少时间才能听到):
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency()
由于多个线程的工作方式,结果可能有点不稳定。只需检查该值是否不小于前一帧中的值(如果不小于,则丢弃它)。这也是一种比以前更不精确的方法,但它适用于任何长度的歌曲,或将任何内容(如音效)与音乐同步。
以下代码与使用此方法之前的代码相同:
func _ready()
$Player.play()
func _process(delta):
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
# Compensate for output latency.
time -= AudioServer.get_output_latency()
print("Time is: ", time)