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...
Sincronize a jogabilidade com áudio e música
Introdução
Em qualquer aplicativo ou jogo, a reprodução de som e música terá um pequeno atraso. Para jogos, esse atraso geralmente é tão pequeno que é insignificante. Os efeitos sonoros aparecerão alguns milissegundos depois que qualquer função play() for chamada. Para a música, isso não importa, pois na maioria dos jogos ela não interage com a jogabilidade.
Ainda assim, para alguns jogos (principalmente jogos de ritmo), pode ser necessário sincronizar as ações do jogador com algo que está acontecendo em uma música (geralmente em sincronia com o BPM). Para isso, é útil ter informações de tempo mais precisas para uma posição de reprodução exata.
Alcançar uma precisão de tempo de reprodução muito baixa é difícil. Isso ocorre porque muitos fatores estão em jogo durante a reprodução de áudio:
O áudio é mixado em pedaços (não continuamente), dependendo do tamanho dos buffers de áudio usados (verifique a latência nas configurações do projeto).
Pedaços mistos de áudio não são reproduzidos imediatamente.
As APIs gráficas exibem dois ou três quadros atrasados.
Ao jogar em TVs, algum atraso pode ser adicionado devido ao processamento de imagem.
A maneira mais comum de reduzir a latência é diminuir os buffers de áudio (novamente, editando a configuração de latência nas configurações do projeto). O problema é que quando a latência é muito pequena, a mixagem de som exigirá consideravelmente mais CPU. Isso aumenta o risco de pular (uma rachadura no som porque uma chamada de retorno -callback- de mixagem foi perdida).
Esta é uma compensação comum, então Godot vem com padrões sensatos que não precisam ser alterados.
The problem, in the end, is not this slight delay but synchronizing graphics and audio for games that require it. Some helpers are available to obtain more precise playback timing.
Usando o relógio do sistema para sincronizar
Como mencionado anteriormente, se você chamar AudioStreamPlayer.play(), o som não começará imediatamente, mas quando o thread de áudio processar o próximo trecho.
Este atraso não pode ser evitado, mas pode ser estimado chamando AudioServer.get_time_to_next_mix().
A latência de saída (o que acontece após a mixagem) também pode ser estimada chamando AudioServer.get_output_latency().
Adicione esses dois e é possível adivinhar quase exatamente quando o som ou a música começará a tocar nos alto-falantes durante _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));
}
A longo prazo, porém, como o relógio do hardware de som nunca está exatamente sincronizado com o relógio do sistema, as informações de temporização lentamente se afastarão.
Para um jogo de ritmo em que uma música começa e termina após alguns minutos, essa abordagem é adequada (e é a abordagem recomendada). Para um jogo em que a reprodução pode durar muito mais tempo, o jogo eventualmente ficará fora de sincronia e uma abordagem diferente será necessária.
Usando o relógio de hardware de som para sincronizar
Usar AudioStreamPlayer.get_playback_position() para obter a posição atual da música parece ideal, mas não é tão útil assim. Este valor será incrementado em blocos (toda vez que a chamada de retorno -callback- de áudio mixar um bloco de som), então muitas chamadas podem retornar o mesmo valor. Somado a isso, o valor também ficará dessincronizado com os alto-falantes pelos motivos citados anteriormente.
Para compensar a saída "fragmentada", existe uma função que pode ajudar: AudioServer.get_time_since_last_mix().
Adicionar o valor de retorno desta função a get_playback_position() aumenta a precisão:
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
Para aumentar a precisão, subtraia a informação de latência (quanto leva para o áudio ser ouvido depois de mixado):
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();
O resultado pode ser um pouco instável devido ao funcionamento de várias partes_paralelizáveis. Apenas verifique se o valor não é menor do que no quadro anterior (descarte-o se for o caso). Essa também é uma abordagem menos precisa do que a anterior, mas funcionará para músicas de qualquer duração ou para sincronizar qualquer coisa (efeitos sonoros, por exemplo) com a música.
Aqui está o mesmo código de antes usando essa abordagem:
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));
}