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...
Sincronizza il gameplay con l'audio e la musica
Introduzione
In qualsiasi applicazione o gioco, la riproduzione dei suoni e della musica avrà un leggero ritardo. Nei giochi, questo ritardo è spesso così piccolo da essere trascurabile. Gli effetti sonori saranno riprodotti qualche millisecondo dopo la chiamata di qualsiasi funzione play(). Per la musica non importa, poiché nella maggior parte dei giochi non interagisce con il gameplay.
Eppure, per alcuni giochi (principalmente giochi di ritmo), potrebbe essere necessario sincronizzare le azioni del giocatore con qualcosa che accade in una canzone (solitamente in sincronia con i BPM). Per questo, è utile avere informazioni più precise del tempo per una posizione esatta di riproduzione.
È difficile ottenere una precisione temporale di riproduzione molto bassa. Questo perché durante la riproduzione audio entrano in gioco molti fattori:
L'audio è mixato in frammenti (non in maniera continua), a seconda delle dimensioni dei buffer audio utilizzati (controllare la latenza nelle impostazioni del progetto).
I frammenti audio mixati non sono riprodotti immediatamente.
Le API grafiche visualizzano due o tre fotogrammi in ritardo.
Giocando su una televisione, potrebbe occorrere un ritardo dovuto all'elaborazione dell'immagine.
Il modo più comune per ridurre la latenza è rimpicciolire i buffer audio (anche in questo caso, modificando l'impostazione della latenza nelle impostazioni del progetto). Il problema è che quando la latenza è troppo bassa, il mixaggio audio richiederà notevolmente più CPU. Questo aumenta il rischio di salti (una crepa nel suono dovuta alla perdita di un callback di mixaggio).
È un compromesso comune, per cui Godot è fornito con impostazioni predefinite ragionevoli che non dovrebbero richiedere aggiustamenti.
Il problema, in fondo, non è questo leggero ritardo, ma la sincronizzazione tra la grafica e l'audio per i giochi che la richiedono. Sono disponibili alcuni mezzi per ottenere tempi di riproduzione più precisi.
Utilizzare l'orologio del sistema per la sincronizzazione
Come accennato in precedenza, se si chiama AudioStreamPlayer.play(), il suono non comincerà immediatamente, ma quando il thread audio elabora il frammento successivo.
Questo ritardo è inevitabile, ma si può stimare chiamando AudioServer.get_time_to_next_mix().
La latenza di uscita (cosa succede dopo il mix) si può stimare anche chiamando AudioServer.get_output_latency().
Combinando questi due è possibile prevedere quasi esattamente quando il suono o la musica inizieranno a essere riprodotti dagli altoparlanti 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 lungo andare, però, poiché l'orologio hardware del suono non è mai esattamente sincronizzato con l'orologio del sistema, le informazioni sulla temporizzazione tenderanno lentamente a deviare.
Per un gioco di ritmo in cui una canzone inizia e finisce dopo pochi minuti, questo approccio va bene (ed è quello consigliato). Per un gioco in cui la riproduzione può durare molto più a lungo, il gioco finirà per andare fuori sincronia e sarà necessario un approccio diverso.
Utilizzare l'orologio hardware audio per la sincronizzazione
Usare AudioStreamPlayer.get_playback_position() per ottenere la posizione attuale del brano sembra ideale, ma non è così utile così com'è. Questo valore aumenterà gradualmente (ogni volta che il callback audio mixa un frammento di suono), quindi più chiamate possono restituire lo stesso valore. Inoltre, il valore non sarà sincronizzato con gli altoparlanti per i motivi menzionati in precedenza.
Per compensare l'uscita "frammentata", esiste una funzione che può essere d'aiuto: AudioServer.get_time_since_last_mix().
Aggiungendo il valore restituito da questa funzione a get_playback_position() si aumenta la precisione:
var time = $Player.get_playback_position() + AudioServer.get_time_since_last_mix()
double time = GetNode<AudioStreamPlayer>("Player").GetPlaybackPosition() + AudioServer.GetTimeSinceLastMix();
Per aumentare la precisione, sottrai le informazioni sulla latenza (il tempo impiegato affinché l'audio sia ascoltato dopo essere stato mixato):
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();
Il risultato potrebbe essere un po' instabile a causa di come funzionano più thread. Verificare che il valore non sia inferiore a quello del frame precedente (in tal caso, scartarlo). Anche questo è un approccio meno preciso del precedente, ma funzionerà per brani di qualsiasi lunghezza o per sincronizzare qualsiasi cosa (ad esempio, effetti sonori) con la musica.
Ecco lo stesso codice di prima, utilizzando questo approccio:
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));
}