Hace 4 meses publiqué una entrada titulada Primeras impresiones de Coffeescript donde probé un poco por encima como funcionaba Coffeescript, con un pequeño ejemplo que dibujaba un muñecote en la pantalla, y permitía que se moviera con el teclado.

Poco después, a @jotadrilo y a mí se nos ocurrió que quizás no era tan mala idea indagar más sobre el tema e intentar diseñar un videojuego programado en Javascript, evolucionando a partir del poco código que escribí en esa entrada. Al poco tiempo le contamos la idea a @Hugorria, se unió al equipo y entre los tres decidimos las bases del proyecto: lo tomaríamos como algo orientado únicamente a aprender algo nuevo, y lo realizaríamos en nuestro tiempo libre.

Herramientas y librerías

  • NodeJS, para la gestión de dependencias con npm.
  • Coffeescript, para facilitarnos un poco la tarea de escribir Javascript.
  • Grunt, para automatizar la compilación de coffee a js y el proceso de minificación de los archivos JS
  • Git con una cuenta en BitBucket para gestionar todo el código
  • Keyboard.JS, para manejar toda la entrada de eventos por el teclado
  • Underscore.JS, que aporta algunas funciones útiles para Javascript

Arquitectura del sistema

El esquema de la aplicación ha ido evolucionando, y finalmente hemos adoptado un esquema llamado Entity, Attributes, Behaviors (Entidades, Atributos y Comportamientos).

  • Una entidad es un elemento que aparece en el videojuego: el personaje, un enemigo, un objeto del suelo, un árbol, una nube…

  • Los atributos son los datos de las entidades. La posición, la vida o la imagen de una entidad son los ejemplos más sencillos.

  • Los comportamientos, por otra parte, son los que describen que acciones puede realizar o recibir una entidad. Por ejemplo: ser dibujado en pantalla, volar, colisionar, morir, ser recogido…

Las entidades, en realidad, son únicamente contenedores de atributos y comportamientos. Cada entidad estará compuesta por atributos y por comportamientos diferentes. Esta diferencia es la que hace a cada entidad diferente de las demás.

Por ejemplo: probablemente todas las entidades tengan el atributo “posición”, porque todas las entidades deben estar en alguna parte. Sin embargo el atributo “vida” tiene sentido en la entidad de personaje, pero no en la entidad “árbol” o en la entidad “nube”. Respecto a los comportamientos, probablemente las nubes tendrán el comportamiento “volar”, mientras que tanto los enemigos como el personaje serán los únicos que tengan el comportamiento “morir”.

Clases auxiliares

Además, hay un par de clases que sirven de ayuda al sistema de Entidades, Atributos y Comportamientos que nosotros denominamos “Auxiliares”.

El mapa

Convierte un array bidimensional numérico en un montón de entidades llamadas Tiles, que son cada uno de los cuadrados que compone el mapa. Algunas tiles son traspasables, mientras que otras no. En este caso la nube, los arbustos y las dos últimas tiles (que están vacías) son las únicas tiles traspasables.

El mapa está compuesto por a repetición de estas pequeñas imágenes

El despachador de eventos (EventDispatcher)

Los comportamientos de una entidad se deben comunicar entre sí. Por ejemplo, supongamos que dos entidades tienen asociado un comportamiento llamado Colisionable, que permite que puedan colisionar entre sí, y un comportamiento llamado Morir, que hace que cuando colisionen con otra entidad el personaje pierda una vida y vuelva a aparecer.

La pieza que falta en este puzzle es el método de comunicación entre Colisionable (que detecta la colisión) y Morir (que produce una acción a partir de una colisión). Este método de comunicación son los eventos, y el EventDispatcher es la clase que los maneja.

A la hora de crear las entidades, el comportamiento Morir pide al EventDispatcher ser notificado cuando un evento de colisión ocurra. En el momento que colisionan, el comportamiento Colisionable, al detectar la colisión envía un evento de colisión al EventDispatcher, que finalmente notifica al comportamiento Morir, produciendo la acción de restar una vida y hacer que el personaje vuelva a aparecer.

El detector de colisiones

Básicamente se encarga de ofrecer el soporte matemático necesario para detectar si hay una colisión entre dos entidades. Nosotros lo aprovechamos también para que nos envíe información sobre qué Tiles están siendo ocupadas en ese momento por cada entidad y sobre en qué tiles están ocurriendo las colisiones.

juego3

En este ejemplo todo lo que está tintado de rojo son tiles que están siendo ocupadas por las entidades y que por lo tanto son tiles donde puede ocurrir una colisión. Lo que está tintado de rojo más oscuro son las tiles donde nuestro personaje está produciendo una colisión a la hora de moverse. En este caso, donde estamos intentando mover a Mario hacia la izquierda, no nos permite hacerlo porque está colisionando con una tile sólida que forma parte de la “montaña”.

Pronto más y mejor…

Me gustaría enseñar lo que tenemos, pero vamos a terminar un par de asuntos antes de subirlo. Nuestros siguientes son objetivos son, en primer lugar, crear un editor de mapas que nos permita modificar el mapa fácilmente y de una manera más visual, sin tener que estar toqueteando un array bidimensional constantemente, y posteriormente comenzar a introducir entidades enemigas, con inteligencia artificial, o un desplazamiento del mapa según el personaje se vaya desplazando por los límites de la pantalla.