Desarrollo de un videojuego de naves en HTML5 – Parte 1

Con este comienza una serie de (al menos) cuatro artículos sobre cómo desarrollar un videojuego de naves espaciales en HTML5. La idea es ir explicando cómo construirlo por etapas, partiendo de la nada. En esta primera entrega haré una introducción y explicaré cómo será la estructura básica del juego.

naventura

Como sé que necesitan ver para creer aquí tienen una muestra del resultado final:

Compatibilidad

Probando el ejemplo en los navegadores, parece funcionar muy bien con Google Chrome y Opera, más allá de alguna diferencia de velocidades. En Mozilla Firefox hay un problema en la parte 4 vinculada al manejo de eventos. Hay un conflicto en la forma en que Kinetic JS agrega eventos a los elementos del Canvas. En ese caso, verán que no pueden hacer click en el botón Jugar para comenzar la partida.

No probé el ejemplo ni en Safari ni en Internet Explorer, en ninguna de sus versiones. Así que si lo quieren seguir les recomiendo que usen Google Chrome.

Qué se necesita para seguir el código y la explicación

  • Conocimiento medio/avanzado de Javascript.
  • Programación Orientada a Objetos (POO). Aunque sea lo más básico para poder entender el diagrama de clases y cómo funciona todo. Se utilizará el patrón de diseño MVC para separar el código en capas.
  • MooTools. Usaré este framework de Javascript como soporte para simular una estructura orientada a objetos. Es necesario conocer la sintaxis de MooTools para comprender el código. Si utilizan jQuery tal vez algunas partes les sean familiares y otras no.
  • Kinetic JS. Esta es una librería que permite manipular de forma cómoda el elemento Canvas de HTML5. Ya mostré su funcionamiento en dos artículos anteriores. Les recomiendo que los repasen: Introducción a Kinetic JS para manejar el Canvas de HTML5Animaciones y efectos en HTML5 con Kinetic JS.

Aclaraciones previas

En este ejemplo verán un diseño de programación con un código. No es perfecto, tiene errores e incluso cosas para corregirle y mejorarle. La idea de esta serie de artículos es ir mostrando en cada etapa cómo se van cambiando los elementos, a veces añadiendo, otras veces modificando y otras eliminando cosas para llegar al objetivo. La intención es evitar esos ejemplos donde a uno le muestran el código final depurado y refactorizado. A veces uno cree que las cosas salen bien al primer intento, y no es así.

Si el tiempo lo permite y hay entusiasmo, tal vez luego agregue más partes al ejemplo. Puede haber una inclusión de opciones adicionales, añadido de sonido, música y otros elementos al videojuego, que en la parte final quedaron afuera.

También una mejora del diseño de programación, que al ir escribiendo el artículo me dejó disconforme. Pero bueno, también tuve que evitar añadir demasiada complejidad.

Por otro lado, Kinetic JS dispone de un evento onFrame que parece interesante para realizar animaciones. No lo incluí en este ejemplo así que quedará pendiente su uso para otro momento.

Imágenes que se usarán

Todas las imágenes que son parte del videojuego han sido tomadas de internet, a excepción de las últimas dos que son el título del juego y el botón jugar, que las hice especialmente.

Nave espacial nave
Nave alienígena alien
Proyectil proyectil-azul
Explosión explosion-azul
Título del juego
Botón Jugar

Primera parte

Vamos a adentrarnos en la primera etapa de desarrollo donde el objetivo es crear los objetos básicos necesarios para poder visualizar la nave espacial y controlarla mediante el teclado.

Recordemos que vamos a dividir las clases de nuestro código en tres partes según el patrón MVC (Modelo, vista y controlador). El modelo será la representación abstracta de los elementos físicos del videojuego (la nave, el proyectil, las naves enemigas) y también de sus interacciones. El controlador será el encargado de dar inicio al juego, mantenerlo activo, manejar los eventos de teclado y mouse y finalizarlo. La vista será en este caso una clase que servirá de nexo con Kinetic JS. La misma librería externa debe ser considerada como parte de la vista.

El HTML base

Empecemos por el principio. Si hablamos de videojuegos en HTML5, entonces necesitaremos una estructura sobre este lenguaje de marcado.

Como ven, el doctype de HTML5 abre el archivo. La etiqueta meta para definir el charset la incluí por una advertencia que emitía Firefox. Luego viene la carga de librerías. El núcleo de MooTools y las funciones adicionales de MooTools More que pueden seleccionarse antes de descargar. Después de eso, la carga de Kinetic JS y luego una extensión de esta librería que se llama Quantum Image y que recién tendrá sentido en la última entrega de la explicación. Finalmente, el archivo naves.js tiene el código del videojuego propiamente.

Dentro de los estilos en cascada, lo más importante es darle al elemento canvas un borde y un fondo negro. Ese fondo representará el espacio. Recuerden que por defecto, el canvas viene en color blanco.

Dentro del cuerpo del documento HTML5 lo único que van a encontrar es un div con el atributo id definido. Kinetic JS lo utiliza para incluir dentro el elemento canvas. Es decir que no tenemos que hacerlo nosotros mismos.

Carga del documento e inicio del juego

El código fuente completo de esta primera parte la pueden encontrar aquí. Al principio encontrarán algunas constantes. Traté de poner todas las posibles, sin embargo en las entregas siguientes verán que de todas formas quedaron algunos valores hardcodeados.

¿Cómo empieza esto? La clase que maneja todo es Juego, pero antes de darle el control, hay que esperar que cargue el DOM y también las imágenes. Es por eso que lo primero que vamos a analizar es la función de carga.

El evento ‘domready‘ se refiere, en MooTools, a la carga completa del documento HTML. Se lo agrega al elemento window. La función anónima asociada es la que se ejecuta cuando el evento ocurre. Aquí lo que se hace es pre cargar las imágenes porque, por ejemplo, Google Chrome no las muestras si no las tiene cargadas de forma anticipada.

imgs es un array, ahí se pondrán todas las imágenes que se usen. Para esta primera parte sólo hay una, la nave espacial. Luego, se utiliza Asset que es parte de las herramientas More de MooTools. Esta función se encarga de realizar la precarga. Noten que se le pasa como parámetro imgs. Asociado a Asset está el evento onComplete, que es con el que se lo suele aprovechar mejor. En ese lugar se pone una función que será la que se ejecutará en cuando la precarga de imágenes finalice.

Como ven, en este caso, se instancia la clase Juego y luego se ejecuta el método jugar. Es decir que en cuanto carguen el documento y las imágenes, la clase Juego tomará el control de todo.

El modelo

Antes de seguir con el controlador, vamos a echarle un vistazo al modelo. El modelo tiene las entidades que pertenecen al mundo bidimensional del videojuego y que interactúan entre sí. Por ahora, tendremos dos solamente. Por un lado, la clase Nave que representa la nave espacial que flota en el espacio y que podremos controlar y también Universo, que incluirá a otros elementos. Empecemos con la primera.

Tiene tres atributos. La posición en x y en y donde se encuentra y la imagen que lo representa. En esta clase utilizo options que es una característica que ofrece MooTools para manejar de forma más simple los atributos. El método initialize es equivalente al constructor de clase y lo único que hace es recibir los parámetros y definirlos. De la forma en que está el código, en este caso cada nueva nave creada le pondrá a los atributos los valores predefinidos entre llaves en options, que son los iniciales. Ahora, si se pasaran parámetros en la instanciación, entonces esos sobreescribirían a los predefinidos.

Los métodos movDer y movIzq sirven para mover la nave hacia la derecha y hacia la izquierda, respectivamente. Verán que la nave no realiza un desplazamiento de a un lugar, sino que salta de a diez. Esto es para que se mueva más rápido. Los if que aparecen en cada caso es para evitar que la nave salga de la pantalla. Se chequea la posición en la que se encuentra antes de permitir que se mueva. Para este ejemplo, permití que parte de la nave cruce el borde de la pantalla, esto está vinculado con el disparo del proyectil, que veremos en el próximo artículo.

Aparecen unos métodos getters con los cuales se obtendrá el valor de los atributos. Es la interfaz de la clase y está para evitar acceder de forma directa a los atributos que deben ser siempre privados.

Luego tenemos la clase Universo. En esta parte del ejemplo podría haber sido omitida, pero como será será fundamental en las etapas siguientes, ya es necesario incluirla. Universo tendrá en su interior la nave, el proyectil y todas las naves alienígenas que vayan apareciendo. Por ahora, sólo la nave. Además, sus métodos proveerán ciertas reglas físicas que afectarán el transcurso del juego. Por ejemplo, nos dirá cuándo un proyectil choca contra una nave alienígena.

 

Mirando el código vemos que ya preparamos los atributos para lo que vendrá. Habrá una nave, una bala o proyectil y una variable aliens que ya veremos cómo hará para manejar hordas de naves enemigas. Por cada objeto que guarda Universo tendremos un método get a través del cual podremos obtener la instancia correspondiente. El constructor, por ahora, sólo crea un nuevo objeto Nave.

El bucle de juego y los eventos de teclado

Volvamos sobre el controlador, es decir, la clase Juego. Analicemos la primera parte:

Tenemos tres atributos. universo tendrá una instancia de Universo. dibujante estará vinculado a la vista y lo veremos luego. intervaloJuego guardará el identificador de repetición periódica que servirá para realizar el bucle de juego.

En el constructor, como ven, se instancia el universo del juego (que a su vez creará la nave) y el objeto para poder dibujar por pantalla.

Como ya comenté, jugar es el método que se ejecuta en cuanto todo el documento y las imágenes ya han sido cargadas. Lo primero que se hace es agregarle al elemento window el evento keydown. Este evento sirve para disparar una acción cuando el usuario presiona una tecla. Como ven, cuando esto ocurra se llamará al método ejecutar pasándole como parámetro la tecla que se presionó (event.key).

Con respecto al .bind(this) que cierra esa porción del código, vale recordar que en Javascript se suele dar con frecuencia la pérdida de referencia. Cuando hacemos:

El código que pongamos dentro de las llaves se olvida de dónde está. Es decir que this, que dos líneas antes se refería a la misma clase Juego, ahora ya no tiene sentido en este nuevo entorno. Esto es un problema porque justamente queremos hacer referencia al método ejecutar de esta clase.

MooTools y los frameworks OOP de Javascript como jQuery, tienen una forma de salvar este problema: la función bind. En este caso, se pone un punto luego de la llave que cierra y la palabra bind. Entre paréntesis, se pone el objeto que se quiere que sea el this dentro de las llaves. En este caso, el this que le pasamos se refiere a Juego. Entonces, cuando el intérprete esté dentro de las llaves, sabrá que  this, se refiere a Juego.

Si se le hubiera pasado .bind(this.universo), entendería que dentro de las llaves un this equivale al objeto Universo. Si se le hubiera pasado .bind(this.universo.getNave()), entonces hubiera entendido que el this se refiere al objeto nave.

Si les resulta confuso, les recomiendo que busquen documentación de Javascript al respecto y de cómo MooTools y los otros frameworks aportan una solución a este problema.

Finalmente, la línea:

Se encarga de hacer ejecutar el método actualizar cada intervalo T_JUEGO, por medio de la función periodical de MooTools. Es el equivalente a utilizar setInterval, de Javascript.

Miremos el resto de la clase Juego:

Cada vez que se presiona una tecla, se interrumpe la ejecución del código y se pasa al método ejecutar que recibe el código la tecla presionada. El switch determina la acción a realizar. Si se presionó la flecha izquierda, se hará mover la nave hacia la izquierda. Si se presionó la derecha, hacia esa otra dirección.

El método actualizar será ejecutado a intervalos regulares. Lo único que hace es utilizar el objeto que maneja la vista para pasarle la instancia de la nave para que la pueda redibujar. Cómo y dónde dibujarla no es asunto de Juego, esa tarea la realiza la clase Dibujante.

La vista y el manejo del Canvas de HTML5

Pasemos a la parte que se encarga de mostrar el modelo por pantalla. Está formada por una clase Dibujante, que utiliza la librería Kinetic JS. Entre sus atributos encontrarán un objeto Kinetic.Stage, que podríamos considerar como un canvas y luego un Kinetic.Layer que es una capa, así como hay capas en los programas de diseño. Esta librería permite trabajar con varias de ellas, pero para este ejemplo utilizaré una sola. Podría ser interesante en el futuro agregar otra que sirva para crear un fondo animado.

Aparecen otros tres elementos como atributos. kinNave y kinBala son instancias de la clase Kinetic.Image, una clase de la librería que sirve para ubicar imágenes en pantalla. La primera servirá para representar la nave y la segunda, el proyectil que dispara.  kinAliens será un array de Kinetic.Image y almacenará a los enemigos. Por ahora sólo nos ocuparemos de la nave que controlamos.

Cuando se crea el objeto Dibujante, se ejecuta el constructor initialize. Primero se instancia el objeto Kinetic.Stage, al cual se le pasan las dimensiones del escenario y también el id del canvas. Luego se crea el objeto Kinetic.Layer que no necesita ningún parámetro.

El constructor recibe un objeto Nave que servirá para obtener las coordenadas de posición de la misma. Como la nave es una imagen, primero habrá que crear el objeto Image clásico de Javascript e indicar de dónde leer el archivo asociado. Para eso se utiliza el atributo src en imagen.src.

A continuación, se crea un objeto Kinetic.Image que es también una imagen, pero propia de la librería Kinetic JS. Entre los datos que necesita se encuentran el objeto Image recién creado, la posición que tendrá en el Stage, el ancho y alto que ocupará y opcionalmente un nombre.

La nave creada queda guardada en el atributo de la clase Dibujante así cuando volvamos a ingresar esté disponible. Se incluye la nave en el layer y luego el layer en el escenario. Finalmente, se dibuja el Stage con this.stage.draw(). Con esa última instrucción, se visualizará la nave en pantalla y cualquier cosa que hayamos metido dentro de la capa.

Cuando esto ocurra, deberían tener presente lo siguiente ya que más adelante será importante.

nave-canvas

El escenario tiene la cordenada y invertida. Es decir que el cero está arriba. Como ven en la imagen, la posición de la nave es un punto en realidad. La imagen se ubica a partir de él. Cuando tengamos que empezar a analizar las colisiones con otros objetos, esto será vital.

El otro método que tiene esta clase se llama redibujar y recibe también la nave como parámetro. Es el que veíamos que se ejecuta cada cierto período de tiempo para mantener actualizada la pantalla del juego. No vuelve a crear la nave, que ya existe, sino que solamente la reposiciona mediante el método setPosition que tiene Kinetic.Image. Luego, vuelve a dibujar el Stage, cambiando (si es que debe hacerlo) la posición de la nave.

Resumen

Hasta aquí hemos visto como crear las clases básicas del modelo, hacer un bucle de juego que actualice la salida por pantalla y  gestionar los eventos de teclado. En el próximo artículo, vamos a hacer que la nave dispare proyectiles presionando la flecha de arriba del teclado.

Referencias

Soy programador web y me desempeño como Líder Técnico y de Proyectos en Polar Bear Development. Trabajo con tecnologías como PHP, Javascript, MySQL y HTML5 para el desarrollo de sitios y sistemas web. Me especializo en Zend Framework 2 y otros frameworks MVC, como también en WordPress y otros CMS. Lidero equipos de desarrolladores trabajando con Scrum. Vivo en Buenos Aires, Argentina.
 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*