> Manuales > Manual de Canvas del HTML 5

Experimentando con el desarrollo de juegos con HTML5: cómo crear animaciones en el Canvas con las librerías EaselJS.

Si quieres pasar el rato haciendo un juego con el elemento Canvas de HTML5, necesitas una solución para manejar las figuras animadas (“sprites”). Existen varias librerías que nos pueden ayudar a crear juegos, como ImpactJS y CraftyJS.

Al final he decidido utilizar EaselJS, que es la que han utilizado para hacer el sitio web PiratesLoveDaisies ("a los piratas les encantan las margaritas") un interesante juego de defensa de una torre hecho con HTML5. Vamos a ver en este tutorial cómo podemos utilizar nuestras propias figuras y cómo podemos darles movimiento.

Introducción

En el sitio oficial de EaselJS vamos a encontrar ejemplos muy interesantes y algo de documentación básica. Vamos a utilizar el ejemplo sprites como punto de partida junto con los recursos que tenemos en el ejemplo XNA 4.0 Platformer.

Si sigues mi blog, ya sabes que me encanta jugar con este ejemplo. Ya lo he utilizado en otros dos artículos anteriores:

El equipo de XNA ha actualizado el ejemplo platformer y ya está disponible para Xbox 360, PC y Windows Phone 7: App Hub – platformer. Descárgala y juega con ella, y después extrae las figuras para utilizarlas con EaselJS.

En este caso voy a utilizar dos archivos PNG como fuente para las secuencias del sprite.

Un monstruo corriendo, que consta de 10 sprites distintos.

Nuestro monstruo, en reposo tiene 11 imágenes diferentes:

Nota: Estos ejemplos no funcionan bien en Firefox 5.0, por lo visto debido a un error en su implementación de canvas. Las pruebas en IE9, IE10, Chrome 12, Opera 11 y Firefox Aurora 7.0 han salido bien.

Tutorial 1: Crear el SpriteSheet y BitmapSequence

Vamos a empezar por hacer correr al monstruo desde una esquina del canvas a la otra.

El primer paso es cargar toda la secuencia completa contenida dentro del archivo PNG con este código:

var imgMonsterARun = new Image();

function init() {
//find canvas and load images, wait for last image to load
canvas = document.getElementById("testCanvas");

imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";
}

Primero haremos una llamada a este código para inicializar el contenido del juego. Después de cargarlo podemos empezar con el juego propiamente.

EaselJS expone un objeto llamado SpriteSheet para manejar el sprite. Así pues, con este código:

var spriteSheet = new SpriteSheet(
imgMonsterARun, //image to use
64, //width of each sprite
64, //height of each sprite
{ 
walk_left: [0, 9]
});

…le estoy indicando que quiero crear una nueva secuencia llamada "walk_left" (ir a la izquierda) que se va a generar con la imagen imgMonsterARun. Esta imagen vamos a dividirla en 10 frames con un tamaño de 64x64 pixels. Este es el objeto primario a la hora de cargar nuestro sprite y crear las secuencias. Podríamos tener varias secuencias creadas a partir del mismo archivo PNG si queremos, como se hace en el ejemplo de sprite llamado "rats" en el sitio de EaselJS.

Lo siguiente que vamos a necesitar es el objeto BitmapSequence, que nos ayuda a animar la secuencia y la posición de los sprites en pantalla.

Echemos un vistazo al código de inicialización de este BitmapSequence:

// create a BitmapSequence instance to display and play back the sprite sheet:
bmpSeq = new BitmapSequence(spriteSheet);

// set the registration point (the point it will be positioned and rotated around)
// to the center of the frame dimensions:
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;

// start playing the first sequence:
bmpSeq.gotoAndPlay("walk_left"); //animate

// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
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;

// have each monster start at a specific frame
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);

El constructor del objeto BitmapSequence solo necesita que le pasemos como parámetro el elemento SpriteSheet. Después le ponemos un nombre a la secuencia, definimos unos cuantos parámetros como la velocidad y la posición inicial del primer frame. Finalmente añadimos esta secuencia a la lista de presentación en pantalla utilizando el objeto Stage y su método addChild().

En el paso siguiente tenemos que ver qué queremos hacer en el bucle de animación. Este bucle de animación se ejecuta cada xxx milisegundos y nos permite actualizar la posición de los sprites. Para esto, EaselJS expone el objeto Ticker que nos proporciona un pulso centralizado, emitiendo un latido cada cierto número de milisegundos.

Todo lo que hay que hacer es suscribirse al evento del pulso e implementar un método .tick() al cual hacer la correspondiente llamada.

Este código es para la instancia de registro del evento:

Ticker.addListener(window);
// Best Framerate targeted (60 FPS)
Ticker.setInterval(17);
Y aquí tenemos el código al que se llamará cada 17 ms (si es posible) para actualizar la posición de nuestro monstruo:
function tick() {
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpSeq.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpSeq.direction = -90;
}

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
}

// Moving the sprite based on the direction & the speed
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}

// update the stage:
stage.update();
}

El resultado final lo podemos ver aquí: easelJSSpritesTutorial01 donde tenemos el código fuente completo.

Pero ¡espera! ¡Tenemos dos problemas en esta animación!

  1. Los pasos de la animación no son muy buenos, porque el personaje se mueve a través de cada una de sus imágenes demasiado rápido
  2. El personaje solo va normalmente de derecha a izquierda, porque cuando va a l revés camina de espaldas!

Vamos a arreglarlo.

Tutorial 2: Cómo controlar la velocidad de la animación y rotar de las figuras

La forma más sencilla que he encontrado de ajustar la velocidad de la animación es utilizar un operador de módulo para evitar dibujar/actualizar la secuencia a cada pulso.

En estos momentos hay una incidencia abierta sobre esto en EaselJS 0.3.2. La podéis ver en: https://github.com/gskinner/EaselJS/issues/60

Para que nuestro personaje camine con normalidad de izquierda a derecha solo tenemos que darle la vuelta a cada frame.

EaselJS expone un objeto SpriteSheetUtils para hacer esto, y el método flip().

function tick() {
// To slow down the animation loop of the sprite, we're not redrawing during each tick
// With a Modulo 4, we're dividing the speed by 4
var speedControl = Ticker.getTicks() % 4;

if (speedControl == 0) {
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpSeq.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpSeq.direction = -90;
bmpSeq.gotoAndPlay("walk_left")
}

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
bmpSeq.gotoAndPlay("walk_right");
}

// Moving the sprite based on the direction & the speed
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}

// update the stage:
stage.update();
}
}

El resultado final se puede ver en: easelJSSpritesTutorial02

Tutorial 3: Cómo cargar múltiples sprites y jugar con múltiples animaciones

Ahora es el momento de cargar el estado “tranquilo” de nuestro monstruo.

Aquí la idea consiste en hacer que el monstruo se dé un pequeño paseo y después se quede quieto.

Tendremos que cargar múltiples archivos PNG desde el servidor web. Es muy importante esperar a que todos los recursos estén cargados, porque de lo contrario podríamos estar tratando de dibujar en pantalla imágenes que aún no se han descargado.

Una manera sencilla de hacerlo es esta:

var numberOfImagesLoaded = 0;

var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();

function init() {
//find canvas and load images, wait for last image to load
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++;

// We're not starting the game until all images are loaded
// Otherwise, you may start to draw without the resource and raise 
// this DOM Exception: INVALID_STATE_ERR (11) on the drawImage method
if (numberOfImagesLoaded == 2) {
numberOfImagesLoaded = 0;
startGame();
}
}

Este código es bien sencillo. Por ejemplo, no controla los errores adecuadamente tratando de volver a descargar la imagen en caso de que falle el primer intento..

Cuando vayamos a crear un juego, tendremos que escribir nuestros propios gestores de descarga de contenidos si la librería de JS que utilizamos no dispone de uno propio.

Para añadir la secuencia de reposo del personaje y establecer los parámetros de posición, necesitamos el mismo tipo de código que ya hemos visto antes:

var spriteSheetIdle = new SpriteSheet(
imgMonsterAIdle, //image to use
64, //width of each sprite
64, //height of each sprite
{ 
idle: [0, 10]
});

bmpSeqIdle = new BitmapSequence(spriteSheetIdle);

bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;

Ahora, en el método tick(), lo que tenemos que hacer es parar la animación una vez que hayamos llegado al lado izquierdo de la pantalla, y poner en marcha en su lugar la animación de reposo.

Este sería el código que para los pies a nuestro monstruo:

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
bmpSeq.gotoAndStop("walk_left");
stage.removeChild(bmpSeq);
bmpSeqIdle.gotoAndPlay("idle");
stage.addChild(bmpSeqIdle);
}

El resultado final lo podemos ver aquí: easelJSSpritesTutorial03.

¡Y esto es todo amigos! Si os sentís capaces de avanzar más, os recomiendo Juegos con HTML5; creación de los objetos básicos y gestión de colisiones con with EaselJS.

David Rousset

Desarrollador Evangelista de Microsoft, especialista en HTML5 y desarrollo Web.

Manual