Aprende a crear juegos en HTML5 Canvas

lunes, 16 de abril de 2012

Animaciones en el juego

Recientemente, uno de los seguidores del blog preguntó el como hacer animaciones dentro del juego. Esta es una tarea un poco avanzada, pero no muy complicada. Quizá la forma más fácil de hacerlo, es a través de una Sprite Sheet como esta:



Esta imagen tiene un tamaño de 10x40px, en la cual se muestran cuatro imágenes de 10x10 que conformarían la animación deseada. Podemos usar una versión avanzada de la función drawImage para dibujar una sección en específico de nuestra imagen, con lo que podemos hacer uso de Sprite Sheets (Una imagen que contiene varios gráficos, como el ejemplo arriba), y esta nos permitirá hacer una animación.

La forma más avanzada de la función drawImage se compone de 9 variables, las cuales analizaremos a través del siguiente ejemplo:
ctx.drawImage(iAnim,0,0,10,10,x,y,10,10);
La primer variable, es nuestra imagen. Las siguientes cuatro variables son la sección de nuestra imagen que se dibujará, a partir de X,Y,Ancho,Alto. Las ultimas cuatro variables, serán el área en el Canvas donde se dibujará la imagen extraída en X,Y,Ancho,Alto. Si se especifican ancho y alto diferentes en esta última, se escalará la imagen al tamaño indicado. Incluso, si se especifican valores negativos, la imagen se dibujará volteada.

Con esta base, podemos programar ya nuestro código. Además de la imagen que contendrá nuestra animación, también crearemos una variable timer que se aumentará de uno en uno cada ciclo, y será la que nos indique que parte de la animación debe ser dibujada:
var timer=0;
var iAnim=new Image()
iAnim.src='media/anim.png';
Dentro de nuestra función game, indicaremos que se sume timer en uno cada vez que el ciclo pase por ahí:
  timer++;
Por último, dibujaremos la parte de la animación correspondiente. El valor que nos interesa cambiar es el primer cero, a los correspondientes 10, 20 y 30. Esto lo haremos obteniendo el residuo de timer entre la cantidad de imágenes (4), a través del operador %, y lo multiplicaremos por el ancho de la imagen (10), quedando la línea para dibujar el fragmento de la imagen de esta forma:
 ctx.drawImage(iAnim,timer%4*10,0,10,10,x,y,10,10);
De esta forma, podremos dibujar animaciones en nuestro juego. Para el ejemplo final, dibujaré la imagen escalada a cuatro veces su tamaño original:

 ctx.drawImage(iAnim,timer%4*10,0,10,10,x,y,40,40);

¡Se ve borrosa!

Un problema al escalar una imagen pequeña, es que se puede ver borrosa y muy mal. Por defecto, las imágenes son escaladas con un efecto que permite que las imágenes no se vean tan mal al ser escaladas, pero en el caso del pixel art, el efecto es el opuesto al deseado. Para desactivarlo, solo hay que agregar al canvas una propiedad CSS de esta forma:
<style type="text/css">
canvas{
image-rendering: optimize-contrast;
}
</style>
Desafortunadamente, esta propiedad no es un estándar aún, por lo que deberemos agregar además, sus versiones propietarias para cada navegador:
<style type="text/css">
canvas{
image-rendering: -webkit-optimize-contrast;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
</style>
Ese último es, claramente, la versión no deseada de Internet Explorer. Con estas líneas en tus códigos, al escalar las imágenes de pixel art, se verán con el efecto deseado.

Código final:

[Canvas not supported by your browser]
'use strict';
window.addEventListener('load',init,false);
var canvas=null,ctx=null;
var x=50,y=50;
var lastPress=null;
var PAUSE=true;
var timer=0;
var PRESSING=[];
var iAnim=new Image();

iAnim.src='media/anim.png';

function init(){
 canvas=document.getElementById('canvas');
 ctx=canvas.getContext('2d');
 run();
}

function run(){
 setTimeout(run,50);
 game();
 paint(ctx);
}

function game(){
 if(!PAUSE){
  // Move Rect
  if(PRESSING[38]) //UP
   y-=10;
  if(PRESSING[39]) //RIGHT
   x+=10;
  if(PRESSING[40]) //DOWN
   y+=10;
  if(PRESSING[37]) //LEFT
   x-=10;

  // Out Screen
  if(x>canvas.width)
   x=0;
  if(y>canvas.height)
   y=0;
  if(x<0)
   x=canvas.width;
  if(y<0)
   y=canvas.height;
  
  //timer
  timer++;
 }
 // Pause/Unpause
 if(lastPress==13){
  PAUSE=!PAUSE;
  lastPress=null;
 }
}

function paint(ctx){
 ctx.fillStyle='#000';
 ctx.fillRect(0,0,canvas.width,canvas.height);
 //ctx.fillStyle='#0f0';
 //ctx.fillRect(x,y,10,10);
 ctx.drawImage(iAnim,timer%4*10,0,10,10,x,y,40,40);
 ctx.fillStyle='#fff';
 //ctx.fillText('Last Press: '+lastPress,0,20);
 if(PAUSE)
  ctx.fillText('PAUSE',140,75);
}

document.addEventListener('keydown',function(evt){
 lastPress=evt.keyCode;
 PRESSING[evt.keyCode]=true;
},false);

document.addEventListener('keyup',function(evt){
 PRESSING[evt.keyCode]=false;
},false);

12 comentarios:

  1. Que buen material muy claro ya estare creando mi juego gracias este manual.

    ResponderEliminar
  2. Estimado te falto inicializar la varliable timer, y ponerle ";" en la linea de codigo 7 del game.js
    Por lo demás está OK.

    ResponderEliminar
    Respuestas
    1. A veces al editar los códigos, olvido copiar unos detalles o los copio mal. ¡Gracias por el dato! Ya le he corregido.

      Eliminar
  3. A mi no me funciona el codigo. En el !Anim src tengo que poner la url de la imagen?

    ResponderEliminar
    Respuestas
    1. Asi es, iAnim.src debe tener el enlace de tu imagen, ya sea relativo a la página donde muestra el juego, o su dirección absoluta. Avisame si aun tienes problemas con ello.

      Eliminar
    2. pues la verdad es que no me aparece la animacion de tu ejemplo y tampoco me aparece si pongo la imagen que yo quiero usar. Podrias revisar el ejemplo para ver si todo esta correctamente desarrollado?

      Eliminar
    3. ¿Estás seguro que pones la imagen en la carpeta correcta? El ejemplo me funciona sin problemas. He copiado y pegado el código en un jsFiddle para que puedas verlo directo en acción y juegues con él si es necesario: http://jsfiddle.net/Eydcd/1/

      Si aun tienes problemas, muéstrame tu código para verificar el problema.

      Eliminar
  4. como podria cambiar la serie de sprites si mi personaje camina??

    lo he hecho asi:

    function paint(ctx){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.drawImage(iAnim,timer%4*59,0,58,58,x,y,40,40);
    ctx.fillStyle='#fff';
    if(PRESSING[39]) //RIGHT
    ctx.drawImage(iAnim2,timer%6*71,0,59,59,x,y,40,40);
    ctx.fillStyle='#fff';
    if(PAUSE)
    ctx.fillText('PAUSE',140,75);
    }

    pero lo unico que logro es que mi sprite para caminar se inserte encima del sprite de mi personaje cuando esta parado, lo que deseo es hacer invisible mi primer sprite y que quede el que hace la funcion de caminar cuando presiono el botón

    ResponderEliminar
    Respuestas
    1. El error está en que primero dibujas el personaje quieto encima del personaje en movimiento, cuando deberías condicionar con un "else" el que se dibuje parado, si no está caminando, de esta forma:

      if(PRESSING[39]) //RIGHT
      ctx.drawImage(iAnim2,timer%6*71,0,59,59,x,y,40,40);
      else
      ctx.drawImage(iAnim,timer%4*59,0,58,58,x,y,40,40);

      PD: Creo que tu código tiene un fillStyle extra que no ocupas...

      Eliminar
  5. gracias funcionó bien y si habia un fillStyle demás

    ResponderEliminar
  6. funciona perfecto, lastima que no se como parar o cambiar entre las animaciones

    ResponderEliminar
    Respuestas
    1. Eso se hace a través de condicionales IF, donde dibujas la animación o un dibujo estático dependiendo la condición de alguna variable.

      Eliminar