Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
게임 플레이를 오디오 및 음악과 동기화
소개
모든 응용 프로그램이나 게임에서 사운드 및 음악 재생이 약간 지연됩니다. 게임의 경우 이러한 지연은 무시할 수 있을 정도로 매우 작은 경우가 많습니다. play() 함수가 호출된 후 몇 밀리초 후에 사운드 효과가 나옵니다. 음악의 경우 대부분의 게임에서 게임플레이와 상호작용하지 않으므로 이는 중요하지 않습니다.
그러나 일부 게임(주로 리듬 게임)의 경우 플레이어 동작을 노래에서 일어나는 일과 동기화해야 할 수도 있습니다(보통 BPM과 동기화). 이를 위해서는 정확한 재생 위치에 대한 보다 정확한 타이밍 정보를 갖는 것이 유용합니다.
매우 낮은 재생 타이밍 정밀도를 달성하는 것은 어렵습니다. 이는 오디오 재생 중에 많은 요인이 작용하기 때문입니다.
오디오는 사용된 오디오 버퍼의 크기에 따라 (연속이 아닌) 청크 단위로 믹싱됩니다(프로젝트 설정에서 대기 시간 확인).
혼합된 오디오 청크는 즉시 재생되지 않습니다.
그래픽 API는 2~3개의 프레임을 늦게 표시합니다.
TV에서 재생할 때 이미지 처리로 인해 약간의 지연이 추가될 수 있습니다.
지연 시간을 줄이는 가장 일반적인 방법은 오디오 버퍼를 줄이는 것입니다(역시 프로젝트 설정에서 지연 시간 설정을 편집하여). 문제는 대기 시간이 너무 짧으면 사운드 믹싱에 훨씬 더 많은 CPU가 필요하다는 것입니다. 이렇게 하면 건너뛰는 위험(믹스 콜백이 손실되어 소리가 깨지는 현상)이 증가합니다.
이는 일반적인 절충안이므로 Godot는 변경할 필요가 없는 합리적인 기본값을 제공합니다.
결국 문제는 이 약간의 지연이 아니라 이를 필요로 하는 게임에 맞게 그래픽과 오디오를 동기화하는 것입니다. 보다 정확한 재생 타이밍을 얻기 위해 일부 도우미를 사용할 수 있습니다.
시스템 시계를 사용하여 동기화
앞서 언급했듯이 :ref:`AudioStreamPlayer.play()<class_AudioStreamPlayer_method_play>`를 호출하면 사운드가 즉시 시작되지 않고 오디오 스레드가 다음 청크를 처리할 때 시작됩니다.
이 지연은 피할 수 없지만 :ref:`AudioServer.get_time_to_next_mix()<class_AudioServer_method_get_time_to_next_mix>`를 호출하여 추정할 수 있습니다.
출력 대기 시간(믹스 후 발생하는 현상)은 :ref:`AudioServer.get_output_latency()<class_AudioServer_method_get_output_latency>`를 호출하여 추정할 수도 있습니다.
이 두 가지를 추가하면 _process() 중에 스피커에서 사운드나 음악이 재생되기 시작할 시기를 거의 정확하게 추측할 수 있습니다.
var time_begin
var time_delay
func _ready():
time_begin = Time.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 = (Time.get_ticks_usec() - time_begin) / 1000000.0
# Compensate for latency.
time -= time_delay
# May be below 0 (did not begin yet).
time = max(0, time)
print("Time is: ", time)
private double _timeBegin;
private double _timeDelay;
public override void _Ready()
{
_timeBegin = Time.GetTicksUsec();
_timeDelay = AudioServer.GetTimeToNextMix() + AudioServer.GetOutputLatency();
GetNode<AudioStreamPlayer>("Player").Play();
}
public override void _Process(double delta)
{
double time = (Time.GetTicksUsec() - _timeBegin) / 1000000.0d;
time = Math.Max(0.0d, time - _timeDelay);
GD.Print(string.Format("Time is: {0}", time));
}
하지만 장기적으로는 사운드 하드웨어 시계가 시스템 시계와 정확히 동기화되지 않으므로 타이밍 정보가 서서히 멀어지게 됩니다.
노래가 시작되고 몇 분 후에 끝나는 리듬 게임의 경우 이 접근 방식이 적합하며 권장되는 접근 방식입니다. 재생 시간이 훨씬 더 오래 지속될 수 있는 게임의 경우 결국 게임이 동기화되지 않게 되므로 다른 접근 방식이 필요합니다.
사운드 하드웨어 시계를 사용하여 동기화
:ref:`AudioStreamPlayer.get_playback_position()<class_AudioStreamPlayer_method_get_playback_position>`를 사용하여 노래의 현재 위치를 얻는 것이 이상적으로 들리지만 현재로서는 그다지 유용하지 않습니다. 이 값은 청크 단위로 증가하므로(오디오 콜백이 사운드 블록을 혼합할 때마다) 많은 호출이 동일한 값을 반환할 수 있습니다. 여기에 더해, 이전에 언급한 이유로 인해 값도 스피커와 동기화되지 않습니다.
"청크된" 출력을 보상하기 위해 도움이 될 수 있는 기능이 있습니다: AudioServer.get_time_since_last_mix().
이 함수의 반환 값을 *get_playback_position()*에 추가하면 정밀도가 높아집니다.
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
정밀도를 높이려면 대기 시간 정보(오디오가 믹싱된 후 들리는 데 걸리는 시간)를 뺍니다.
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix() - AudioServer.get_output_latency()
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix() - AudioServer.GetOutputLatency();
여러 스레드가 작동하는 방식으로 인해 결과가 약간 불안할 수 있습니다. 값이 이전 프레임보다 작지 않은지 확인하세요(그렇다면 폐기하세요). 이 접근 방식은 이전 접근 방식보다 덜 정확하지만 모든 길이의 노래에 적용되거나 모든 것(예: 음향 효과)을 음악에 동기화할 수 있습니다.
다음은 이 접근 방식을 사용하기 전과 동일한 코드입니다.
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)
public override void _Ready()
{
GetNode<AudioStreamPlayer>("Player").Play();
}
public override void _Process(double delta)
{
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
// Compensate for output latency.
time -= AudioServer.GetOutputLatency();
GD.Print(string.Format("Time is: {0}", time));
}