Una caratteristica che accomuna tutti i game engine, è il Game Loop: una funzione che gestisce il ciclo del gioco: la fisica (l'aggiornamento di posizione dei nostri elementi di gioco, il controllo delle collisioni), e il Draw, ovvero il rendering degli oggetti che fanno parte del gioco (i cosiddetti GameObject).
requestAnimationFrame, gestire il refresh della scena
In HTML5 è stata inserita la funzione requestAnimationFrame che rimpiazza il vecchio metodo di animazione, che utilizzava un timer (setTimeout
) per renderizzare un frame ogni tot millisecondi.
Con requestAnimationFrame
possiamo indicare al motore JavaScript quale funzione lanciare la prossima volta che scatterà un frame. Ecco la sintassi:
(request ID) = requestAnimationFrame(callback)
Grazie a questa funzione nativa invece, il browser può ottimizzare il rendering nativamente mantenendo un framerate più stabile e fluido (con un massimo di 60fps), riducendo drasticamente i fastidiosi "scatti" che si notavano utilizzando i Timer (setTimeout, setInterval).
Nel caso si passasse a un'altra scheda del browser, la funzione disabiliterebbe temporaneamente il rendering , a differenza di setTimeout, che invece continuerebbe a funzionare sfruttando CPU, GPU e memoria: un utilizzo maggiore di prestazioni hardware, che nei portatili e dispositivi mobili, si traduce in uno spreco di batteria.
Un polyfill per requestAnimationFrame
Poiché HTML5 non è ancora standard (lo sarà entro il 2014), requestAnimationFrame
è stato implementato temporaneamente con un prefisso negli identifier (webkit
, moz
, o
, ms
), che si riferisce ai nomi dei vari browser: definiamo quindi una funzione generica, un polyfill, che ritorni l'id
della funzione corretta.
Per non dover ogni volta richiamare tutte le possibili funzioni creiamo quel che si suol dire un Polyfill, ovvero una funzione che si occuperà di richiamare la funzione giusta per ogni client.
Creiamo un nuovo file JavaScript (utils.js
), in cui inserire tutte le funzioni di comodo che utilizzeremo del nostro framework. Iniziamo con questo polyfill:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
Anche se la probabilità è remota, potremmo avere un browser che non supporta requestAnimationFrame
, in questo caso la nostra funzione utilizzerà il vecchio metodo setTimeout
.
Già che ci siamo, spostiamo l'event listener load
da main.js a utils.js
window.addEventListener('load', function() {
StartGame();
}, true);
GameLoop
Possiamo finalmente definire il GameLoop, ovvero quella funzione che ci permette di creare un ciclo infinito in cui gestire animazioni ed eventi frame dopo frame. Definiamo quindi la funzione GameLoop
, all'interno dell'oggetto Game
:
function Game() {
// ... cose definite nella lezione precedente
this.GameLoop = function() {
if(!this.paused) {
// aggiorna tutti gli oggetti
this.Update();
}
//disegna l'intera scena a schermo
this.Draw();
window.requestAnimFrame(function() {
// rilancia la funzione GameLoop ad ogni frame
game.GameLoop();
});
}
}
Il cuore del loop sta nella chiamata a requestAnimFrame
, in cui chiamiamo nuovamente la funzione GameLoop dell'istanza di Game, in modo che si inneschi un meccanismo ricorsivo, in cui vengono eseguiti Update()
e Draw()
ogni frame.
La maggior parte dei framework per videogiochi, prevede che gli oggetti nel gioco (GameObject, che stiamo per vedere) siano dotati delle funzioni Update e Draw, ciò fa di esse due tasselli fondamentali dell'engine e anche se non sono ancora definite ci impegnamo a definirle a breve.
Definiamo inoltre la variabile paused, che bloccherà la funzione Update
quando sarà impostata a true
.
Nota: Draw()
rimane sempre attiva, così da renderizzare comunque le immagini. Nei prossimi capitoli vedremo come ottimizzare le prestazioni durante la pausa utilizzando i canvas OffScreen
.