In questa lezione vedremo come aggiungere al game engine un gestore di risorse, che ci permetterà di caricare immagini, suoni ed effettuare il check di eventuali errori, lasciando comunque la possibilità di espandere il "resource handler" per l'aggiunta futura di feature come il caricamento di modelli 3D, shaders e altri tipi di file.
Definiamo l'oggetto (la funzione) ResourcesHandler
all'interno di un nuovo file resources.js
:
/*
* resources.js
*/
function ResourcesHandler(callback) {
/* dichiarazione proprietà */
this.resNumber = 0;
this.resLoaded = 0;
this.errors = [];
this.status = 0;
this.loading = false;
}
Inizializziamo le proprietà resNumber
e resLoaded
, che terranno il conto delle risorse caricate e di quelle ancora in caricamento, un array errors[]
che conterrà eventuali errori, una variabile status
, che indica la percentuale di caricamento, e una variabile loading
che indica se abbiamo chiamato le funzioni per caricare risorse.
Caricare immagini e sprite
Aggiungiamo poi a ResourcesHandler
una funzione metodo per caricare le immagini e gli sprite:
function ResourcesHandler(callback) {
/* dichiarazione proprietà */
// ...
/* caricamento sprite e immagini */
this.LoadSprite = function(url, frames, funct) {
this.loading = true;
var img = new Image();
img.src = url;
img.rh = this;
this.resNumber++;
img.frames = frames;
this.w = this.width/this.frames;
img.onload = function() {
if(funct != undefined) {
funct();
}
this.w = this.width/this.frames;
this.rh.resLoaded++;
this.rh.CheckLoaded();
};
img.addEventListener("error", function(e) {
this.rh.resNumber--;
this.rh.errors.push([url, e]);
this.rh.CheckLoaded();
});
return img;
}
}
Vediamo gli argomenti:
Argomento | Descrizione |
---|---|
url |
L'URL dell'immagine da caricare (può anche essere un link locale) |
frames |
Il numero di frames presenti nella nostra immagine |
funct |
(opzionale) la funzione da eseguire al termine del caricamento dell'immagine |
In LoadSprite creiamo un'immagine, la assegnamo ad una variabile temporanea img
e impostiamo la sorgente dell'immagine (img.src
) in base all'argomento url
.
Fatto questo aggiungiamo all'oggetto img
la proprietà rh
, che utilizziamo per mantenere all'interno dello scope di img
un riferimento all'istanza attuale dell'oggetto contenitore ResourceHandler
. Ci servirà per modificare le variabili del ResourceHandler
in una funzione globale che utilizzeremo successivamente.
Dopodiché incrementiamo di 1 il numero di risorse da caricare e salviamo il numero di frames
in una variabile locale dell'immagine.
Calcoliamo la larghezza del singolo frame (per gli sprite delle animazioni utilizzeremo strip affiancate, come nella prossima immagine) e la salviamo nella variabile locale img.w
(non possiamo utilizzare img.width
, dato che è già una variabile interna, corrispondente alla lunghezza dell'intera immagine).
Gestiamo ora l'evento onload
di img
, che viene scatenato quando il caricamento dell'immagine è completo: incrementiamo il numero di risorse caricate di 1, ed eseguiamo (se esiste) la funzione passata come parametro, infine controlliamo se il processo di caricamento è terminato, tramite la funzione CheckLoaded
del ResourceHandler
che definiremo tra poco.
Infine gestiamo l'evento error
per dare qualche informazione in più sull'eccezione generata, tipicamente un problema di caricamento dell'immagine. Inseriamo nell'array errors[]
del ResourceHandler
, un array di due elementi, contenente l'URL dell'immagine impossibile da caricare e l'errore ritornato.
Ovviamente il report degli errori può essere migliorato, traducendo l'errore riscontrato ("URL non funzionante", "immagine corrotta" o "formato non supportato") e cercando eventuali soluzioni, ma questo va oltre lo scopo di questa guida.
Infine controlla nuovamente se il caricamento è completato, utilizzando CheckLoaded e ritorna un riferimento a img
.
Nota: Anche se il riferimento all'immagine si ottiene instantaneamente, questa viene caricata in modo asincrono, è quindi necessario attendere una risposta dall'evento load o error prima di utilizzarla.
Loader, gestire il caricamento degli asset
Ora aggiungiamo al ResourceHandler
, la funzione CheckLoaded
(sempre in resources.js
). Questa funzione si occuperà di due cose:
- Disegnare la barra di avanzamento, utilizzando
DrawLoading
(che definiremo successivamente) - Controllare se il numero di risorse caricate + numero di risorse che hanno dato errore sia maggiore o uguale al numero di risorse da caricare, e quindi eseguire la funzione
callback()
passata come argument alResourceHandler
.
function ResourcesHandler(callback) {
/* dichiarazione proprietà */
// ...
/* caricamento sprite e immagini */
// ...
/* controllo caricamento risorse */
this.CheckLoaded = function() {
if(!this.loading) return null;
this.DrawLoading();
if(this.resLoaded + this.errors.length >= this.resNumber) {
callback();
this.resNumber = 0;
this.resLoaded = 0;
this.loading = false;
}
}
}
Disegno del loader
Definiamo quindi, la funzione DrawLoading
, con cui creiamo l'animazione che appare durante il caricamento.
function ResourcesHandler(callback) {
/* dichiarazione proprietà */
// ...
/* caricamento sprite e immagini */
// ...
/* controllo caricamento immagini */
// ...
/* disegno del loader */
this.DrawLoading = function() {
//percentuale di caricamento
this.status = (this.resLoaded) / (this.resNumber + this.errors.length);
//centro del canvas
var cx = game.canvas.width/2;
var cy = game.canvas.height/2;
//imposta il colore di riempimento
game.ctx.fillStyle = "#333";
//disegna un rettangolo grande quanto il canvas
game.ctx.fillRect(0, 0, game.canvas.width, game.canvas.height);
//avvia il path di disegno primitive
game.ctx.beginPath();
game.ctx.strokeStyle = "#222";
//imposta lo spessore della linea da disegnare
game.ctx.lineWidth = 25;
//aggiunge un arco al path (una corona circolare di raggio 80)
game.ctx.arc(cx, cy, 80, 0, Math.PI*2, false);
//disegna il path
game.ctx.stroke();
//calcola i radianti del secondo arco,
var radians = (360 * this.status) * Math.PI / 180;
//disegna il secondo arco
game.ctx.beginPath();
game.ctx.strokeStyle = "#ddd";
game.ctx.lineWidth = 25;
game.ctx.arc(cx, cy, 80, 0 - 90*Math.PI/180, radians - 90*Math.PI/180, false);
game.ctx.stroke();
//Imposta un font e disegna il testo al centro del cerchio di caricamento
game.ctx.font = '22pt Segoe UI Light';
game.ctx.fillStyle = '#ddd';
game.ctx.fillText(Math.floor(this.status*100) + "%",cx-25,cy+10);
}
}
Drawloading
disegnerà una corona circolare grigia, che viene riempita da un'arco di corona bianco con il procedere del caricamento. Il risultato è questo: