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.

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)

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()

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()

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)