In attesa del terzo ed ultimo articolo sui Behavior Tree ecco qui un articolo dettagliato sui Game Loop. È un articolo lunghetto ma in italiano non c’è molta informazione al riguardo e dovevo colmare la lacuna. Data la mole ho deciso di spezzarlo in pagine.
Nel cuore di ogni gioco, dal più semplice al più complesso, dal più vecchio al più nuovo, esiste una struttura basilare che li accomuna. Questa struttura è il cuore pulsante di ogni videogioco: il game loop.
A prima vista non c’è nulla di complesso nel game loop: si tratta solo di ripetere fino alla terminazione del gioco il ciclo input-aggiornamento-rendering, ovvero intercettazione dell’input dell’utente, aggiornamento delle posizioni e degli stati degli oggetti di gioco e infine il rendering della scena sullo schermo. Insomma, all’atto pratico non sembra nulla di più complesso di queste poche righe di codice.
while running :
update()
rendering()
In questo piccolo esempio abbiamo inglobato la gestione dell’input in update()
poiché solitamente le due fasi vengono sempre gestite in sequenza.
Quale è il problema con questo tipo di game loop? Semplicissimo. Supponete che la vostra funzione update()
sia programmata per far avanzare lo stato del mondo di 1/10 di secondo e supponete che nella vostra scena ci sia il buon vecchio Mario. Adesso immaginate che, mentre programmate, di aver tarato la velocità del salto in modo che ci metta circa mezzo secondo per raggiungere il blocco sopra la sua testa. Dato il passo di avanzamento della funzione update()
, questo significa che ci vogliono circa 5 frame (e quindi 5 iterazioni) perché Mario raggiunga il blocco una volta premuto il pulsante “salto”.
Ed è proprio questa la parola chiave: il blocco dista 5 frame. Cosa sono 5 frame in termini temporali? Sul mio PC 5 frame possono essere correttamente circa 0.5 secondi ma su computer più veloci o più lenti? Su computer prestanti 5 iterazioni possono essere 0.05 secondi accelerando tremendamente il gioco fino al punto da essere ingiocabile. Analogamente su PC lenti 5 iterazioni possono essere eseguite in un secondo o più, rallentando il gioco. In questi casi si dice che i frame per secondo (FPS) guidano la velocità del gioco (Game Speed o UPS) e questa è sempre una caratteristica non desiderabile poiché rende la velocità di gioco dipendente dalla piattaforma sulla quale essa viene eseguita.
Update Variabili
La soluzione a questo problema sembra naturale: invece rendiamo un parametro l’incremento della funzione update()
e ad ogni iterazione gli passiamo il tempo impiegato ad eseguire una iterazione. In questo modo:
t_start=0
t_end=GetTicks()
while running :
t_start = t_end
t_end = GetTicks() # Restituisce i millisecondi correnti
update(t_end-t_start)
rendering()
Perfetto. Ora l’avanzamento dipende dal tempo effettivamente impiegato in ogni iterazione mantenendo coerente il tempo di esecuzione dal tempo fisico della simulazione. Abbiamo trovato il loop perfetto? Assolutamente NO!.
Questo loop, sebbene molto affascinante e teoricamente perfetto è il peggiore in assoluto poiché causa problemi non indifferenti sia su PC lenti che su PC veloci e la cosa peggiore è che è talmente ammaliante che fa cadere in tentazione tutti gli sviluppatori almeno una volta nella vita (almeno fino a quando non ci si accorge in prima persona dei danni causati e del tempo che fa perdere).
Onde evitare che ci caschiate anche voi sarò chiaro nello spiegarvi i problemi di suddetto loop. Partiamo dai PC lenti.
Come abbiamo visto in un articolo precedente gran parte dei metodi di integrazione numerica offrono una precisione strettamente dipendente dall’intervallo di integrazione. Come se non bastasse, poiché nei videogiochi non stiamo mandando Curiosity su Marte e possiamo accontentarci di una simulazione poco precisa, si preferisce puntare sulla velocità dell’integrazione euleriana la quale però, come ben sapete, è la più sensibile al passo di integrazione.
Cosa c’entra tutto ciò? Semplice. Nei computer lenti c’è un rischio molto alto che il tempo di iterazione (passato poi alla funzione di update) diventi troppo alto. Un temporaneo rallentamento del PC può portare il tempo di iterazione a valori prossimi al secondo con conseguenti intollerabili errori di simulazione. In molti casi addirittura ci si può imbattere nel fenomeno della esplosione della simulazione in cui l’errore di integrazione di un iterazione particolarmente problematica si ripercuote a valanga sui valori futuri sparando qualunque valore (posizione, velocità, ecc…) verso infinito.