Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Giochi e HTML5: come animare gli sprite nel Canvas con EaselJS

Come creare semplici animazioni di elementi nell'elemento Canvas utilizzando le proprietà della libreria EaselJS. Concetti d'uso ed esempi pratici
Come creare semplici animazioni di elementi nell'elemento Canvas utilizzando le proprietà della libreria EaselJS. Concetti d'uso ed esempi pratici
Link copiato negli appunti

Questa è la traduzione dell'articolo HTML5 Gaming: animating sprites in Canvas with EaselJS di David Rousset pubblicato originariamente il 21 Luglio 2011 su MSDN Blog. La traduzione viene qui presentata con il consenso dell'editore.

Quando si creano giochi usando l'elemento Canvas di HTML5 bisogna trovare il modo di gestire le forme (gli sprite). Ci sono diverse librerie che consentono di semplificare la creazione di giochi come ImpactJS, CraftyJS e altre. Io ho deciso di usare EaselJS, utilizzata per creare PiratesLoveDaisies, un eccellente gioco in HTML5 del tipo "difendi la torre". In questo articolo vedremo come usare gli sprite che si hanno a disposizione e come animarli.

Introduzione

Sul sito ufficiale di EaselJS si potranno trovare esempi e documentazione di base. Noi useremo alcuni esempi base di sprite assieme ad altre risorse disponibili nell'esempio XNA 4.0 Platformer.

Se seguite il mio blog, probabilmente sapete che spesso ho utilizzato questi esempi. Li ho usati, ad esempio, in due precedenti articoli:

Il Platformer sample è stato aggiornato dal team XNA ed è disponibile per Xbox 360, PC e Windows Phone 7 (App Hub – platformer). Scaricatelo ed estraete gli sprite da usare con EaselJS.

Userò due file PNG come sequenza di sprite.

Un mostro che corre, suddiviso in 10 sprite.

Figura 1: Mostro in corsa
Omino in corsa

Scarica l'originale da usare negli esempi.

E il mostro in posizioni di riposo, suddiviso  in 11 sprite diverse

Figura 2: Mostro a riposoOmino a riposo

Scarica l'originale da usare negli esempi.

Nota: questi esempi non funzionano a dovere in Firefox 5.0, probabilmente a causa di un bug nell'implementazione del canvas. Sono invece stati testati con successo in IE9, IE10, Chrome 12, Opera 11 e Firefox Aurora 7.0.

Passo 1: gestire gli oggetti SpriteSheet e BitmapSequence

Iniziamo con il far correre il mostro da un lato del canvas all'altro. Il primo passo è caricare la sequenza completa contenuta nel file PNG attraverso questo codice:

var imgMonsterARun = new Image();
function init() {
    //recupera il canvas e carica le immagini
    //attendi fino a quando l'ultima immagine è caricata
    canvas = document.getElementById("testCanvas");
    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";
}

Il codice verrà richiamato all'inizio dell'applicazione per inizializzare il contenuto del nostro gioco. Una volta caricato, il gioco può essere avviato. EaselJS espone un oggetto SpriteSheet per gestire lo sprite. In questo modo usando il codice:

var spriteSheet  = new SpriteSheet(
        imgMonsterARun, //immagine da usare
        64, //larghezza di ogni sprite
        64, //altezza di ogni sprite
        {
            walk_left: [0, 9]
        });

... posso indicare di voler creare una nuova sequenza chiamata walk_left che verrà composta dall'immagine imgMonsterARun. L'immagine sarà suddivisa in 10 frame di dimensioni 64x64 pixel. Questo è l'oggetto principale che ci permetterà di caricare il nostro sprite e di creare la nostra sequenza. Se si desidera si possono avere diverse sequenze create dallo stesso file PNG, come accade nell'esempio dei topolini sul sito di EaselJS.

Dopo aver fatto ciò, si dovrà usare l'oggetto BitmapSequence che aiuta ad animare la sequenza e a posizionare gli sprite sullo schermo.

Vediamo insieme il codice che inizializza l'oggetto.

// crea un'istanza BitmapSequence per visualizzare e riprodurre la striscia di sprite
bmpSeq = new BitmapSequence(spriteSheet);
// imposta il punto di registrazione al centro delle dimensioni del frame
// (il punto in cui si trova l'oggetto e intorno al quale ruota)
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;
// avvia la riproduzione della prima sequenza
bmpSeq.gotoAndPlay("walk_left");     // animazione
// imposta un'ombreggiatura. Va detto che le ombre sono avide di risorse. Evitando le ombre è possibile mostrare centinaia di topolini animati.
bmpSeq.shadow = new Shadow("#454", 0, 5, 4);
bmpSeq.name = "monster1";
bmpSeq.speed = 1;
bmpSeq.direction = 90;
bmpSeq.vX = 3|0.5;
bmpSeq.vY = 0;
bmpSeq.x = 16;
bmpSeq.y = 32;
// ogni mostro viene avviato in uno specifico momento
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);

Il costruttore dell'oggetto BitmapSequence ha bisogno solo dell'elemento SpriteSheet come parametro. Dovremo poi dare un nome alla sequenza e impostare alcuni parametri come la velocità e la posizione iniziale del nostro primo frame. Alla fine aggiungeremo la sequenza per la visualizzazione usando l'oggetto Stage e il suo metodo addChild().

Successivamente è necessario decidere cosa si intende fare all'interno del loop di animazione. Il loop è richiamato ogni xxx millisecondi e permette di aggiornare la posizione degli sprite. A questo scopo, EaselJS espone l'oggetto Ticker che invia un "tic" (un segnale) ad un intervallo prestabilito a tutti gli elementi in ascolto.

Tutto ciò che c'è da fare è "sottoscrivere" l'evento tick e implementare un metodo .tick() di callback.

Con questo codice registriamo l'evento:

Ticker.addListener(window);
// Impostiamo il miglior tasso di immagini al secondo (60 FPS)
Ticker.setInterval(17);

E questo è il codice che verrà richiamato ogni 17 ms (se possibile) per aggiornare la posizione del nostro mostro.

function tick() {
    // Verifichiamo l'ampiezza dello schermo altrimenti rischiamo che lo sprite scompaia
    if (bmpSeq.x >= screen_width - 16) {
        // Abbiamo raggiunto il lato destro dello schermo
        // Dobbiamo tornare indietro verso sinistra per raggiungere la posizione iniziale
        bmpSeq.direction = -90;
    }
    if (bmpSeq.x < 16) {
        // Abbiamo raggiunto il lato sinistro dello schermo
        // Adesso dobbiamo camminare verso destra
        bmpSeq.direction = 90;
    }
    // Muovere lo sprite in base alla direzione e alla velocità
    if (bmpSeq.direction == 90) {
        bmpSeq.x += bmpSeq.vX;
        bmpSeq.y += bmpSeq.vY;
    }
    else {
        bmpSeq.x -= bmpSeq.vX;
        bmpSeq.y -= bmpSeq.vY;
    }
    // aggiorniamo lo stage
    stage.update();
}

Potete verificare il risultato finale qui:

Il codice completo si trova su easelJSSpritesTutorial01.

Ma attenzione! Ci sono due problemi con questa animazione!

  1. I passi dell'animazione sono incoerenti poiché l'omino esegue i cicli di movimento troppo velocemente.
  2. L'omino cammina normalmente solo da destra verso sinistra; nell'altro senso sembra invece che stia facendo il moonwalk!

Correggiamo i problemi.

Passo 2: controllare la velocità dell'animazione e invertire dello sprite

Il metodo più semplice che ho trovato per correggere la velocità dell'animazione è quello di usare un "operatore modulo" (modulus operator) per evitare di disegnare/aggiornare la sequenza dopo ogni tic. C'è inoltre un problema aperto su questo punto per EaselJS 0.3.2: https://github.com/gskinner/EaselJS/issues/60

Invece, per fare camminare regolarmente l'omino da sinistra verso destra doppiamo semplicemente invertire specularmente ogni frame. EaselJS espone a questo riguardo un oggetto SpriteSheetUtils e un metodo flip(). Ecco il codice che permette di utilizzarlo

// {nameOfFlippedSequence:["derivativeSequence", flipHorizontally, flipVertically, optionNameOfNextSequence]}
spriteSheet = SpriteSheetUtils.flip(
spriteSheet,
{
	walk_right: ["walk_left", true, false, null]
});

Ciò che stiamo facendo è essenzialmente creare una sequenza derivata chiamata walk_right basata sulla sequenza walk_left che abbiamo invertito lungo l'asse orizzontale. Alla fine, ecco il codice che ci permette di rallentare l'animazione e gestire quale sequenza riprodurre in base alla posizione dell'omino.

function tick() {
    // Per rallentare l'animazione non dobbiamo ridisegnarla ad ogni tic
    // Con un modulo 4, dividiamo la velocità per 4
    var speedControl = Ticker.getTicks() % 4;
    if (speedControl == 0) {
        // Verifichiamo l'ampiezza dello schermo altrimenti rischiamo che lo sprite scompaia
        if (bmpSeq.x >= screen_width - 16) {
            // Abbiamo raggiunto il lato destro dello schermo
            // Dobbiamo tornare indietro verso sinistra per raggiungere la posizione iniziale
            bmpSeq.direction = -90;
            bmpSeq.gotoAndPlay("walk_left")
        }
        if (bmpSeq.x < 16) {
            // Abbiamo raggiunto il lato sinistro dello schermo
            // Adesso dobbiamo camminare verso destra
            bmpSeq.direction = 90;
            bmpSeq.gotoAndPlay("walk_right");
        }
        // Muovere lo sprite in base alla direzione e alla velocità
        if (bmpSeq.direction == 90) {
            bmpSeq.x += bmpSeq.vX;
            bmpSeq.y += bmpSeq.vY;
        }
        else {
            bmpSeq.x -= bmpSeq.vX;
            bmpSeq.y -= bmpSeq.vY;
        }
        // aggiornare lo stage:
        stage.update();
    }
}

Ecco il risultato finale:

Codice completo su easelJSSpritesTutorial02

Passo 3: caricare più sprite e utilizzare più animazioni

È tempo di caricare il mostro nella sua posizione di riposo. L'idea è quella di far fare al mostro un singolo viaggio (da destra a sinistra e ritorno) e poi fermarlo. Dobbiamo, in questo caso, caricare più di un file PNG dal server. È molto importante attendere che tutte le risorse siano caricate, altrimenti si potrebbero disegnare elementi incompleti.

Ecco un modo semplice per farlo:

var numberOfImagesLoaded = 0;
var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();
function init() {
    // recupera il canvas e carica le immagini
    // attende fino a quando l'ultima immagine è caricata
    canvas = document.getElementById("testCanvas");
    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";
    imgMonsterAIdle.onload = handleImageLoad;
    imgMonsterAIdle.onerror = handleImageError;
    imgMonsterAIdle.src = "img/MonsterAIdle.png";
}
function handleImageLoad(e) {
    numberOfImagesLoaded++;
    // Non facciamo partire il gioco fino a quando l'ultima immagine è caricata
    // Altrimenti si potrebbe cominciare a disegnare senza la risorse e incorrere in  questa eccezione del DOM: INVALID_STATE_ERR (11) per il metodo drawImage
    if (numberOfImagesLoaded == 2) {
        numberOfImagesLoaded = 0;
        startGame();
    }
}

Il codice è molto semplificato. Per esempio, non gestisce gli errori in modo accurato cercando di ricaricare la risorsa una seconda volta dopo un tentativo fallito. Naturalmente non stiamo creando un gioco vero e proprio, dovrete dunque progettare un download manager specifico se la libreria JavaScript che state usando non lo ha già. Per aggiungere la sequenza con l'omino a riposo e impostare i parametri di posizionamento, dovrete semplicemente usare il codice visto in precedenza:

var spriteSheetIdle = new SpriteSheet(
        imgMonsterAIdle, // immagine da usare
        64, //larghezza dello sprite
        64, //altezza dello sprite
        {   
            idle: [0, 10]
        });
bmpSeqIdle = new BitmapSequence(spriteSheetIdle);
bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;

Ora, nel metodo tick(), dobbiamo fermare l'omino che cammina quando ha raggiunto il lato sinistro dello schermo e riprodurre l'animazione di riposo. Ecco il codice per fermare il mostro:

if (bmpSeq.x < 16) {
	// Abbiamo raggiunto il lato sinistro dello schermo
	// Adesso dobbiamo camminare verso destra
	bmpSeq.direction = 90;
	bmpSeq.gotoAndStop("walk_left");
	stage.removeChild(bmpSeq);
	bmpSeqIdle.gotoAndPlay("idle");
	stage.addChild(bmpSeqIdle);
}

Ecco il risultato finale:

Qui il codice completo: easelJSSpritesTutorial03.

L'autore

Foto David RoussetDavid Rousset è developer evangelist di Microsoft, specializzato in HTML5 e sviluppo per il Web. Leggete il suo blog su MSDN o seguitelo su Twitter con l'account @Silversurfeur.

Ti consigliamo anche