Sprites en HTML5 Canvas

En este artículo vamos a explicar cómo dibujar sprites en HTML5 Canvas y la API que nos proporciona el navegador para renderizar gráficos. Dibujar sprites es una tarea esencial en la programación de videojuegos y forma parte de los artículos siguientes:

  • Imágenes en HTML5 Canvas: En este artículo aprenderemos a mostrar una imagen ya existente en el lienzo Canvas
  • Tiles en HTML5 Canvas: En este artículo aprenderemos a mostrar sólo una parte de una imagen ya existente en el lienzo Canvas
  • Sprites en HTML5 Canvas: En este artículo aprenderemos a crear y dibujar un Sprite (imagen animada) de una imagen ya existente en el lienzo Canvas
  • Cargador de imágenes JavaScript: En este artículo aprenderemos a cargar de forma dinámica cualquier imagen que necesitemos dibujar en nuestro lienzo Canvas

En este artículo, vamos a explicar cómo dibujar sprites en HTML5 Canvas y sus pequeñas sutilezas, utilizando HTML5 Canvas y JavaScript.

Qué son los sprites

En el mundo de los videojuegos, los sprites son una secuencia de imágenes a modo de fotogramas, que al visualizarse dan la sensación de animación o movimiento. Por ejemplo, el movimiento del fuego en una hoguera o un personaje corriendo. Estas imágenes generalmente se organizan en una cuadrícula, igual que los tilesets, permitiendo a los desarrolladores mostrar cada fotograma de la animación en un bucle continuo.

Es la misma técnica que se utiliza en las imágenes .gif (animados), salvo que en un videojuego debemos poder iniciar, parar y cambiar la animación a voluntad mediante programación.

Sprite con un gif animado
Gif animado

En un videojuego necesitamos algo más que un Gif animado. Debido a que un personaje puede moverse en varias direcciones es más interesante tener todos los fotogramas ordenados en una cuadrícula. Si observamos la siguiente imagen obtenida de https://rgsdev.itch.io/free-cc0-modular-animated-vector-characters-2d se trata de un sprite que he adaptado a una cuadrícula 8×2 tiles de tamaño 50×62 píxels.

Sprites en HTML5 Canvas

Si mostramos fotograma a fotograma la primera fila el sprite camina a la derecha y mostrando la segunda fila el sprite camina a la izquierda. Es importante una buena fase de diseño al crear los sprites para que la animación sea fluida y lógica. Observa que el sprite se «encoje» por arriba, pero siempre mostraremos el recorte de la imagen al mismo tamaño tomando como base sus pies para que el movimiento sea natural.

Ahora que sabemos qué son los sprites en HTML5 Canvas vamos a ver cómo podemos obtenerlos y llevarlos a nuestro lienzo utilizando la misma técnica que en los tiles, pero añadiendo la animación con el Bucle de animación de videojuegos.

Crear un lienzo canvas

Como es habitual, lo primero que necesitamos es configurar nuestro documento HTML con un elemento Canvas, el cual nos proporcionará una área gráfica para dibujar, comúnmente llamado lienzo canvas. Tambien vamos a añadir la imagen del sprite a nuestro HTML y que utilizaremos para mostrar en el Canvas sólo el fotograma que queremos como si fuera un gráfico individual. El ejemplo básico sería el siguiente:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Imagen sprite en HTML5 Canvas</title>
  <style>
    canvas {
      border: 1px solid #000;
    }
  </style>
</head>
<body>
  <canvas id="miCanvas" width="640" height="320">Canvas no soportado</canvas>
  <p><img id="mi_sprite" src="enemy-sprite.png"></p>
</body>
</html>

En este ejemplo hemos creado un lienzo HTML5 Canvas con las etiquetas <canvas></canvas> y justo debajo añadimos nuestra imagen sprite con los siguientes atributos:

  • ID Es un identificador único para referirnos al lienzo desde JavaScript y dibujar en él
  • width, height Son las dimensiones en píxeles de nuestro lienzo o ventana gráfica
  • El texto entre las etiquetas sólo se mostrará si el navegador no soporta la tecnología de dibujo HTML5 Canvas
  • La imagen que añadimos debajo del <canvas> tiene el ID «mi_sprite» que utilizaremos para referirnos a ella en JavaScript

Dibujo de sprites en HTML5 Canvas

Para dibujar nuestro sprite en cada fotograma debemos proceder de la misma manera que al obtener un tile y que ya se ha explicado en un artículo anterior, mediante la instrucción:

  ctx.drawImage(imagen, sx, sy, sw, sh, x, y, width, height);

Recordemos que en este caso cada fotograma mide 50×62 píxels y que disponemos de dos filas de fotogramas que representan la animación a derecha e izquierda, respectivamente. La técnica para crear la animación es sencilla:

  • Establecemos la fila a animar en la variable sy
  • Establecemos el fotograma a animar en la variable sx
  • Conocemos las dimensiones (sw, sh) de cada fotograma (50, 62)
  • Mostraremos cada fotograma en la misma posición (x, y) => (300, 150)
  • Mostraremos cada fotograma con el mismo tamaño (width, height) => (50, 62)

Dibujo de sprites en HTML5 Canvas con JavaScript

Ahora vamos a crear el código JavaScript para dibujar la animación de nuestro sprite a partir del conjunto de tiles y mostrarla en el lienzo Canvas. Este código JavaScript vamos a insertarlo debajo de la etiqueta <img>:

  <script>
    window.onload = function () {    
      // Obtenemos datos del Canvas
      let canvas = document.getElementById("miCanvas");
      let ctx = canvas.getContext("2d");

      // Obtenemos el tileset
      let sprite = document.getElementById("mi_sprite"); 

      // Variables
      let delay = 0; // Retardo entre fotogramas  
      let sx = 0; // Inicio recorte (x) del tile
      let sy = 0; // Inicio recorte (y) del tile          
      let cont = 0; // Contador de ciclos bucle animación
    
      // Bucle universal de animación
      function animateScene() {	
        // Contador de ciclos del bucle      
        cont--;
        if (cont < 0) {
          cont = 300;
          sy += 62; 
        }

        // Retardo entre fotogramas
        delay--;        
        if (delay < 0) {
          delay = 6;
          // Borramos el canvas
          ctx.clearRect(0, 0, canvas.width, canvas.height);    
          // Dibujamos el fotograma actual
          if (sx >= sprite.naturalWidth) sx = 0; 
          if (sy >= sprite.naturalHeight) sy = 0; 
          ctx.drawImage(sprite, sx, sy, 50, 62, 300, 150, 50, 62);     
          sx += 50;
        }
        requestAnimationFrame(animateScene);
      }
      // Activamos la animación
      animateScene();	
    }
  </script>

Animación de sprites con JavaScript

Como explicamos en el artículo sobre los tiles, este código tiene un evento «onload» muy feo pero que en este momento es necesario, vamos a explicar cómo funciona:

  • Carga de la página: La función window.onload se ejecuta cuando la página web se ha cargado completamente. Ahora mismo esto es necesario para asegurar que la imagen se ha cargado completamente antes de proceder a trabajar con ella en el Canvas.
  • Obtención del canvas y contexto de dibujo: Obtenemos el elemento <canvas> y su contexto de dibujo 2D.
  • Obtención de la imagen: Obtenemos el elemento imagen mediante su ID y que representa el sprite que se animará en el canvas.
  • Definición de Variables:
    • delay: Retardo entre fotogramas de la animación.
    • sx: Posición inicial de recorte (x) del sprite.
    • sy: Posición inicial de recorte (y) del sprite.
    • cont: Contador de ciclos del bucle de animación.
  • Bucle de Animación: En cada ciclo del bucle se realizan las siguientes acciones
    • Se disminuye el contador (cont), cuando es menor que 0 se reinicia a 300 y se aumenta la posición de recorte (sy) en 62 píxeles. Esto permite cambiar al siguiente fotograma verticalmente en el sprite.
    • Se disminuye el contador (delay). Esto permite ajustar la velocidad de animación del sprite aunque el bucle se ejecute a 60fps constantes.
      • Cuando (delay) es menor que 0 se reinicia a 6 y se borra el canvas para limpiar el contenido anterior.
      • Comprobamos que las variables (sx, sy) de recorte del sprite se mantengan dentro de los límites de la imagen y en caso contrario reiniciamos los valores de recorte.
      • Se dibuja el fotograma actual del sprite con ctx.drawImage() con el recorte (sx, sy, 50, 62) y se dibuja en (300, 150) a un tamaño de (50, 62) en el canvas.
      • Se aumenta la posición de recorte (sx) en 50 píxeles para pasar al siguiente fotograma horizontalmente en el sprite.
    • Se ejecuta la función requestAnimationFrame(animateScene()) para crear una animación continua.
  • Fuera del bucle se llama a la función animateScene() para iniciar la animación una vez que la ventana se haya cargado completamente.

Consideraciones al animar sprites con JavaScript

Al abrir el archivo HTML en un navegador deberías ver la imagen original que hemos añadido al HTML y en el lienzo canvas la animación en bucle continuo del sprite:

Sprites en HTML5 Canvas con JavaScript
Sprites en HTML5 Canvas con JavaScript

Sin embargo hay varias consideraciones a tener en cuenta y que se entremezclan con las técnicas descritas en la generación de tiles y el bucle de animación:

  • El bucle de animación se ejecuta a una velocidad constante de 60 fotogramas por segundo. Esto hay que tenerlo en cuenta respecto a la velocidad de nuestros sprites, que igual funcionan mejor a una velocidad de 10 fps. En este caso hemos añadido un retardo de 6 ciclos para ejecutar cada fotograma del sprite, lo que significa que se ejecuta a una velocidad de 60/6 = 10fps.
  • Para cambiar la dirección (derecha/izquierda) de la animación hemos utilizado otro contador que modifica la variable (sy) de recorte en nuestro sprite. Esto significa que cada (300/60 = 5) segundos cambia la fila de animación del sprite.
  • Las variables (sx, sy) se van incrementando al tamaño exacto que está dibujado cada fotograma y debemos implementar una forma de detectar los límites antes de recortar el fotograma o no se mostrará nada. Por suerte disponemos de las instrucciones (naturalWidth, naturalHeight) en JavaScripts para detectar esto.

En este caso hemos implementado todas las técnicas conjuntamente en el mismo bucle de animación para mostrar cómo funciona. Pero en un videojuego cada tipo de sprite puede requerir una animación a unos fps independientemente de otros sprites y la cosa se complica.

Resumen de sprites en HTML5 Canvas

El uso de sprites en HTML5 Canvas forma parte del proceso creativo en el diseño de un videojuego. Además de poder cargar varios tipos de animaciones mediante una sola imagen, nos permite, escalar, rotar y posicionar cada fotograma de la animación como si fuera una imagen independiente y manteniendo el modelo original.

Es importante en este punto tener en cuenta que el tamaño de todos los fotogramas de un sprite deben ser idénticos. Una animación como un personaje andando puede cambiar de altura pero manteniendo los pies en el suelo se ve «natural». En cambio una explosión puede ser más conveniente mantenerla centrada para que lo «natural» sea ver como crece en todas direcciones. Es un proceso que puede ser diferente para cada tipo de animación y que forma parte del diseño de un videojuego, para que nuestro JavaScript sólo se tenga que preocupar de mostrar un fotograma cada vez.

Otro aspecto interesante es saber que un tilseset puede tener varias animaciones como andar (izquierda, derecha, arriba, abajo) y que simplemente cambiando el varlo de la variable (sy) iniciamos cada tipo de animación. Esto forma parte de la programación del videojuego junto con la detección de las acciones del usuario.

En este sentido la función ctx.drawImage() del Canvas continua siendo extremadamente potente y versatil permitiendo crear múltiples animaciones con poca demanda gráfica, al dibujar sólo una parte de la imagen que queremos. En otros post explicaremos la forma de crear animaciones con más particularidades como crear retardos de fps personalizados, invertir la animación, etc. Cosa que puede ser útil en videojuegos.

¡ Espero que este artículo sea de vuestro interés !

Deja un comentario