miércoles, 8 de agosto de 2012

HTML5 - Sprite

Buenas a todos!
En este tutorial vamos a crear una clase Sprite animado, que nos permitirá incluir objetos animados sobre  nuestro canvas.
Un sprite es un mapa de bits 2D, que contiene un conjunto de elementos que queremos pintar. Estos elementos pueden ser por ejemplo, todos los fotogramas de una animación en concreto, texto estático que queremos mostrar, objetos que queremos ir repitiendo en un escenario, backgrounds, etc.
Para vuestros sprites, os recomiendo que uséis el formato .PNG, ya que os permite tener transparencias, son ficheros que ocupan relativamente poco y sobretodo porque es ampliamente soportado por la mayoría de librerías, plataformas y software de edición gráfica.

La imagen que vamos a emplear para el tutorial es la siguiente: "spriteRun.png"

El resultado que obtendremos es el siguiente:

Creación de la clase Sprite Animado

Para los que no estéis familiarizados con la creación de clases en JavaScript os recomiendo que os leáis mi tutorial "Classes in javascript", si tenéis cualquier duda no dudéis en preguntar!

Para tener el código más ordenado, os recomiendo que guardéis el siguiente código en un fichero .js, en el ejemplo, "SpriteAnimated.js". Si tenéis dudas de como vincular este fichero a vuestro html podéis ver el tutorial "Vincular un archivo javascript externo".

function SpriteAnimated(img, frameArray, loop, x, y){ 
  this.img = img; 
  this.frameArray = frameArray;
  this.currentFrame = 0;
  this.loop = loop;
  this.x = x;
  this.y = y;
  this.width = this.frameArray[this.currentFrame][2];
  this.height = this.frameArray[this.currentFrame][3];
}

SpriteAnimated.prototype.update = function(){ 
  ctx.drawImage(
      this.img
      this.frameArray[this.currentFrame][0],
      this.frameArray[this.currentFrame][1],      
      this.frameArray[this.currentFrame][2],
      this.frameArray[this.currentFrame][3], 
      this.x
      this.y
      this.width,
      this.height); 

  if(this.currentFrame == (this.frameArray.length-1)){ 
    if(this.loop) this.currentFrame = 0; 
  }else{ 
    this.currentFrame++; 
  } 


El código anterior hace lo siguiente:
Define una clase llamada SpriteAnimated, los parámetros que recibe a través de su constructor son:
- img : objeto de tipo Image.
- frameArray : array bidimensional que contiene las coordenadas de cada uno de los fotogramas que componen la animación, dentro de la imagen "img".
      Por ejemplo:
                                   [ [0,0,32,41] , [32,0,32,41] , [64,0,32,41] ]
            Este frame array contiene 3 fotogramas,
            cada fotograma es representado por un array que indica la sección de la imagen
            que vamos a recortar, indicando las coordenadas 'x' e 'y',
            y el ancho y el alto a recortar.
            El formato para definir un fograma es el siguiente: [ x, y, ancho, alto ]
- loop: tipo booleano, true o false, nos indica si la animación se repetirá al finalizar o el sprite se quedará parado en el último fotograma.
- x: indica la coordenada x del canvas donde se pintará el sprite. En píxeles.
- y: indica la coordenada y del canvas donde se pintará el sprite. En píxeles.

Además de estos parámetros he añadido las propiedades width y height, que indican el tamaño con el que se pintará el sprite.

Una vez definida la clase Sprite, he añadido a su prototipo el método 'update()', este método es el que utilizaremos para pintar el sprite.
Lo primero que hace la función 'update()' es llamar al método del context 'drawImage' pasándole por parámetro la imagen del Sprite y los parámetros que determinan que sección de la imagen debe pintarse, y con qué posición y tamaño debe mostrarse esa sección de imagen.
Los parámetros que determinan la sección de imagen correspondiente al fotograma que queremos mostrar,  se encuentran almacenados como un array de 4 elementos, dentro del frameArray que contiene todos los fotogramas.
Los parámetros que indican la posición en la que se pintará la imagen, son las propiedades 'x' e 'y' del SpriteAnimated. Y los parámetros que indican el tamaño con el que se mostrará el sprite, son las propiedades 'width' y 'height'.


  ctx.drawImage(
      this.img
      this.frameArray[this.currentFrame][0],
      this.frameArray[this.currentFrame][1],      
      this.frameArray[this.currentFrame][2],
      this.frameArray[this.currentFrame][3], 
      this.x
      this.y
      this.width,
      this.height); 


El fragmento de código que sigue al pintado, primero comprueba si hemos llegado al último fotograma de la animación, es decir, si hemos llegado al último elemento del frameArray. En caso afirmativo, comprueba si la variable loop es true, en caso de que sea true reinicia el currentFrame a 0 para que la animación se repita indefinidamente.
En el caso de que no estemos en el fotograma final de la animación, el currentFrame se incrementa, para que en la siguiente iteración se muestre el siguiente fotograma almacenado en el frameArray.


  if(this.currentFrame == (this.frameArray.length-1)){ 
    if(this.loop) this.currentFrame = 0; 
  }else{ 
    this.currentFrame++; 
  }


Utilización de la clase Sprite Animado

Ha llegado la hora de empezar a ver como se mueve esto!! Recordad que debéis incluir en la cabecera de vuestro html, el SpriteAnimated.js que contiene la declaración de la clase!

<script type="text/javascript" src="SpriteAnimated.js"></script>


El código del tutorial es como sigue a continuación:


<canvas id="c" style="position: relative;"></canvas>
<script languaje="JavaScript">
  var WIDTH = 300;
  var HEIGHT = 50;
  var can;
  var ctx; 

  var man_img = new Image();
  man_img.src = "spriteRun.png";
  var man_fa = [[0,0,32,41],[32,0,32,41],[64,0,32,41],[96,0,32,41],[128,0,32,41], 
        [160,0,32,41],[192,0,32,41],[224,0,32,41],[256,0,32,41],[288,0,32,41],
        [320,0,32,41],[352,0,32,41],[384,0,32,41],[416,0,32,41]];
  var man_sp;

  function init() {
     can    = document.getElementById("c");
     ctx    = can.getContext("2d");
     can.width   = WIDTH; 
     can.height   = HEIGHT;
     man_sp = new SpriteAnimated(man_imgman_fatrue16,5);
     man_sp.width = 32;
     man_sp.height = 41;
     setInterval(gameLoop, 1000/15);
  } 

  function gameLoop(){
     ctx.clearRect(0, 0, WIDTH, HEIGHT);
     man_sp.update();
  }

  window.onload = init;

</script>


El fragmento de código anterior incluye la creación de un objeto de tipo Image, que contiene la imagen de nuestro sprite. Seguidamente declaramos el frameArray, que contiene las coordenadas de recorte de los fotogramas del sprite.
En la función init, invocamos el constructor de nuestra clase SpriteAnimated, pasándole la imagen, el frameArray, el comportamiento tipo bucle (true), y la posición del canvas donde queremos que se pinte.

man_sp = new SpriteAnimated(man_imgman_fatrue16,5);

Como podéis ver, las propiedades de los objetos SpriteAnimated que vais creando, pueden ser modificadas en cualquier momento, en este ejemplo modifico el tamaño del sprite.


 man_sp.width = 32;
 man_sp.height = 41;


Para que la animación no sea excesívamente rápida he programado que se ejecute el gameLoop, a 15 fotogramas por segundo, 15fps, mediante la siguiente instrucción:

setInterval(gameLoop, 1000/15);

Finalmente en cada iteración del gameLoop se ejecuta la función update del objeto "man_sp" de tipo SpriteAnimated, que es la encargada de pintar en pantalla el sprite.

man_sp.update();

La función anterior al pintado del sprite, pinta sobre el fotograma anterior un rectángulo blanco, para limpiar el canvas y que no se muestren los fotogramas del sprite uno sobre otro. Os invito a quitarla y ver lo que ocurre!!

ctx.clearRect(0, 0, WIDTH, HEIGHT);

Hasta aquí el tutorial! Espero que os sirva de gran ayuda y que os animéis a añadir mejoras a vuestra clase SpriteAnimated, como métodos que gestionen pausar la animación, dar soporte a múltiples animaciones, normalizar el tamaño y coordenadas, etc.  La clase que hemos programado es completamente funcional y os servirá como base para hacer clases más complejas de este tipo.

Voy a ir posteando nuevos tutoriales sobre la creación de videojuegos en HTML5, si estáis interesados será un placer que os suscribáis al blog! Y cualquier duda no dudéis en preguntar!
Un saludo a todos!!

2 comentarios:

  1. Muy interezantes, todavia em enredo un poco siguiendote, ya que se me dificulta mucho el uso de canvas :-S, pero aqui estoy sin rendirme

    ResponderEliminar
  2. Hola Ardilla!
    Me alegro de que no te rindas! Si tienes cualquier duda pregunta! Este es el tutorial más complicado, así que tómatelo con calma.
    Un saludo!

    ResponderEliminar