Desarrollo de un videojuego de naves en HTML5 – Parte 3: Aparición de las naves enemigas

Hemos llegado a la tercera parte de esta serie de artículos sobre desarrollo de videojuegos en HTML5. Ha llegado el momento de que aparezcan las naves enemigas, a las que llamaré simplemente “aliens”, porque es más corto.

Recordemos dónde nos habíamos quedado: Resultado nave disparando
Y veamos hasta dónde vamos a llegar en este nuevo artículo: Resultado enemigos apareciendo

Por ahora, no ocurre nada cuando hay colisiones entre los objetos. Eso lo veremos recién en el siguiente y ¿último? artículo vinculado a este ejemplo. Pueden encontrar el código fuente completo aquí.

Las naves enemigas

Los aliens que aparecerán para destruir a la nave que controlamos tienen su propia clase. No van a disparar, al menos en estos primeros cuatro artículos. Veremos que la idea es destruirlos o esquivarlos. Aparecerán en la parte superior de la pantalla a intervalos regulares y a comenzar a bajar. Cuando lleguen a la parte inferior desaparecerán.

Empecemos viendo algunas constantes asociadas a los aliens

Vamos a necesitar una imagen para representarlos en el canvas. Todos los aliens usarán la misma figura para la cual también establecemos las constantes vinculadas a sus dimensiones. El T_ALIEN que aparece estará vinculado al intervalo que marcará el tiempo en que aparezcan uno a uno los enemigos.

Veamos la función que se ejecuta cuando carga el documento.

Es igual que antes, pero se agrega la nueva imagen al array imgs para que se realice la carga previa.

La clase Alien

Tiene algunas similitudes con las clases Nave y Proyectil. Quizás, en una etapa futura de desarrollo convenga crear una clase más general de la cual cada una de estas pueda heredar. Vamos a ver como es:

Una vez más opté por usar Options para el manejo de los atributos. Hay dos para definir la posición del alien y estos son posx y posy. El atributo imagen tendrá asociada la figura que corresponde, al igual que ocurría con la nave y el proyectil.

Los métodos getPosX y getPosY devuelven la posición en x y en y del alien. avanzar se encarga de cambiar la posición y hacerlo avanzar. El movimiento del alien será únicamente hacia abajo y en línea recta.

Hordas de aliens

No vamos a manejar un único alien. Vamos a manejar hordas de ellos. Esto quiere decir que podemos llegar a tener varios enemigos en pantalla al mismo tiempo. Eso dependerá de qué tan rápido vayan apareciendo. Así que tenemos que plantear una estructura que nos permita almacenar no uno, sino varios aliens.

Si recuerdan, la clase Universo tenía todas las referencias a los objetos del juego. Hasta aquí, eran solamente la nave que controlamos con el teclado y el proyectil. Ahora, deberá almacenar las referencias a los aliens que se crearán. Utilizaremos un array como nuevo atributo de Juego para cumplir esta función. Además, se hará necesario tener otro array de Kinetic.Images en Dibujante, para mantener las referencias a los objetos que se van agregando al canvas.

Pero miremos primero lo que se agrega a la clase Universo

Como ven en el constructor initialize, se crea el nuevo array al que llamaremos aliens.

Veamos qué hace el método nuevoAlien. Su función es crear un nuevo alien en una posición al azar, agregarlo al array de aliens y devolver la nueva instancia recién creada.

Debemos crear una instancia nueva de Alien, pero para ello debemos pasarle los parámetros que necesita, que son la posición en x y en y.

Primero, para la coordenada x, se obtiene un número al azar utilizando el método random, de la clase Number de Javascript. Los parámetros que necesita esta función son el inicio y el fin de un intervalo. Si por ejemplo queremos un número entre 10 y 20, le debemos pasar esos dos valores. En este caso, vamos a necesitar un número entre 0 y el ancho del canvas. Pero hay un detalle: si el valor al azar devuelto coincide con el ancho del canvas, entonces el alien se situará allí, pero la imagen se dibujará a partir de ese punto, hacia la derecha y hacia abajo, por lo que la figura no llegará a visualizarse porque aparecerá por fuera del canvas. Se ve mejor explicado en la siguiente imagen.

limite-canvas

Para corregir este problema, se le resta al ancho del canvas el ancho de la imagen. De esta forma, la nave enemiga podrá verse siempre de forma completa respecto de la coordenada x.

Para la coordenada y aparece un problema similar. No se necesita un número al azar, ya que todos los aliens aparecerán en la parte superior del canvas. ¿Pero dónde exactamente? Si los ubicamos en la coordenada 0, entonces aparecerán de golpe, porque se dibujan desde ese punto hacia abajo. Necesitamos que vaya entrando en la pantalla, es decir, que debe crearse fuera del canvas y luego comenzar a ingresar. Pueden verlo en la imagen que aparece a continuación.

limite-superior

Para poder hacer ese efecto en el cual la nave ingresa al canvas, lo mejor es crear el alien en la coordenada 0 menos el alto de la imagen del alien. Es decir, -IMG_ALIEN_ALTO.

Luego viene la creación del nuevo alien. Para ello, se le pasan los dos parámetros que corresponden a sus coordenadas. Luego, agregamos la instancia al array mediante la función push, que lo ubica al final. Finalmente devolvemos la instancia.

El avance de los aliens

La función Array.each de MooTools permite iterar un array. El primer parámetro que se pasa es el array que se quiere recorrer. Y el segundo es una función, que a su vez tiene un primer parámetro que tomará el valor de cada elemento del array a medida que vaya ciclando. El segundo parámetro es un contador y se incrementará en cada pasada. En este caso no lo usamos, pero esta estructura para iterar arrays la usaremos varias veces más en el ejemplo así que es una buena ocasión para presentarla de forma completa. ¿Cuántas veces se ejecutará este ciclo? Tantas veces como elementos tenga el array dentro.

Como recién expliqué, dentro de las llaves, alien se refiere a cada uno de los valores del array. Y lo que hacemos con cada uno de ellos es llamar al método avanzar.

El método alienAbajo se encarga de chequear si los aliens llegan a la parte inferior del canvas y en caso de que esto ocurra, los va eliminando.

Al igual que vimos en los artículos anteriores, aquí entra en juego nuevamente la pérdida de referencia dentro de la iteración que hacemos con Array.each. Cuando accedemos a this dentro de este bloque de código, apuntará al objeto window. Antes lo solucionábamos con la función bind de MooTools. En este caso, el bind viene incluido como parte de Array.each y se agrega como segundo parámetro. Es la siguiente línea:

Esto le dice al Array.each que el this que aparece dentro se refiere a this.aliens.

Ya dentro del bucle iterativo, se chequea la posición en y de cada alien, si es mayor que el límite inferior del canvas, entonces se procede a eliminarlo del array. Noten como se hace esto. Primero se utiliza la función erase a la cual se le pasa por parámetro el valor a eliminar, en este caso la referencia al alien. Esto le deja al array un valor null o indefinido en esa posición. Para poder sacarlo de forma definitiva de ahí es necesario utilizar la función clean, que justamente lo que hace es limpiar el array de todos los elementos que son null o undefined.

La variable numero sirve para marcar qué posición del array se está eliminando. Ese será también el valor de retorno. En caso de que no se borre ningún elemento, entonces se devuelve null.

Veamos el último método que se agrega: hayAliens

No hay mucho para analizar. Esta función devuelve false si el array aliens está vacío y de lo contrario, devuelve true.

Generación de aliens

Ya sabemos cómo son, sabemos cómo avanzan en pantalla y cómo desaparecen. Pero falta saber cómo es que los aliens empiezan a aparecer y en qué momento se les da la orden de avanzar o de eliminarse. Para eso, debemos volver sobre la clase Juego.

Aparecen dos nuevos atributos: intervaloAlien y contAlien.

contAlien servirá para hacer avanzar a los aliens cada cierto tiempo. Es simplemente un contador que permite manejar la velocidad de estos objetos. En principio, es necesario inicializarlo en 0.

intervaloAlien estará asociado a una función que se ejecutará de forma periódica con el objetivo de generar nuevos aliens. Al igual que como hicimos con intervaloJuego, vamos a utilizar la función periodical de MooTools para programar una operación que debe repetirse a intervalos regulares. El método que debe ejecutarse es generarAlien y que será el encargado de disparar la llamada a nuevoAlien de Universo.

Este método es parte de Juego y su función es hacer aparecer un nuevo alien y luego crear la imagen correspondiente en el canvas. La primera instrucción se encarga de lo primero y la otra, de lo segundo. Noten que se crea el nuevo alien y se obtiene la instancia que luego se pasa como parámetro al método dibujarAlien, que ya veremos cómo hace para representarlo en pantalla.

Cada T_ALIEN milisegundos, se ejecutará generarAlien creando así un nuevo enemigo en la pantalla.

Modificaciones en la actualización de los elementos del juego

Falta ver cómo se llama a los métodos que hacen avanzar o destruir los aliens. Esto se llevará a cabo desde el método actualizar de Juego. Recuerden que este método se ejecuta a intervalos regulares y que se encarga de actualizar el modelo y redibujar la vista. Es lo que comúnmente se llama “bucle de juego”.

Habíamos visto que contAlien se inicializaba en 0. Ahora vemos que en cada ejecución de actualizar este atributo se incrementa en una unidad. El if que aparece a continuación restringe su acceso sólo a cuando vale 7 y noten que cuando la condición es válida, lo primero que se hace dentro de ese bloque es volver a poner el valor en 0. Esto quiere decir que contAlien contará de 0 a 7 una y otra vez, entrando al if únicamente cuando tenga este último valor. O también podemos pensarlo como que contAlien hace que lo que está dentro del if se ejecute una de cada veces que se ejecuta actualizar.

¿No sería esto un intervalo de tiempo? Sí. Como ven, esto es similar a utilizar periodical, como habíamos hecho antes antes.

Entonces,  si entra una vez de cada 7, la siguiente pregunta lógica que deberíamos hacernos es:, ¿qué hace cuando entra? Hace avanzar los aliens, siempre y cuando haya algún alien ya creado. Es por eso que primero lo chequea utilizando el método hayAliens, que vimos antes.

¿Qué pasaría si en vez de 7 pusiéramos un 20? Se entraría en el if una de cada 20 veces. Es decir que el intervalo de tiempo entre avances sería más largo. Por ende, los aliens avanzarían más lento.

¿Qué pasaría si en vez de 7 pusiéramos un 4? Justamente lo contrario. El intervalo de tiempo entre avances sería más corto y veríamos a los aliens moverse más rápido.

Pueden hacer la prueba en cualquiera de los dos casos. Incluso, en una etapa posterior de desarrollo, este valor que aquí aparece hardcodeado, podría ser una variable que controle la velocidad de los aliens y que se incremente a medida que el jugador va avanzando en el juego, con el objetivo de hacerlo cada vez más difícil. Suponiendo, claro, que si los aliens se mueven más rápido, entonces es más difícil matarlos o más probable que choquen con la nave.

Luego viene el chequeo para ver si algún alien se fue de la pantalla por la parte inferior. Si esto ocurre, la variable nro tomará el valor que corresponde a la posición del array en la que está el alien que ha salido de la pantalla. Si ninguno ha salido, quedará en null, por lo tanto, no ocurrirá nada. En caso contrario, se entrará al if y se quitará el alien de la pantalla.

La vista y el dibujo de los aliens

Ahora vamos a ver cómo se dibujan en pantalla los enemigos. En el constructor initialize de Dibujante simplemente se agrega una línea que crea un nuevo array.

En Dibujante aparece el método dibujarAlien que se encarga de crear la nueva imagen del tipo Kinetic.Image e insertarla en el layer.

dibujarAlien: function(alien) {

Se crea la imagen alienIMG que es un objeto del tipo Image. Se le define la ruta al archivo que corresponde estableciendo el valor del atributo src. Luego se crea la imagen Kinetic.Image a la cual se le especifica la imagen recién creada, el ancho, el alto y la posición en x y en y del nuevo alien, que se obtienen del objeto que pasa como parámetro al método.

El ancho y el alto aquí son constantes. Quizás convenga, más adeñante, agregarlos como atributos no sólo para el alien sino para la nave, el proyectil y todos los objetos del Universo.

La imagen kinAlien se inserta al final en el array kinAliens que guardará todas las instancias Kinetic.Image de aliens que se vayan creando. Finalmente sí, se agrega el objeto al layer o capa.

Quizás notaron que no hace falta llamar a stage.draw(). Esto es porque en realidad el método redibujar está siendo llamado a intervalos regulares por medio del método actualizar de Juego. Es decir que, aunque no forcemos el dibujo, en apenas décimas de segundos el método redibujar se va a estar ejecutando y el canvas va a lucir actualizado con el nuevo alien en él.

Veamos ahora cómo se elimina el alien del canvas

El parámetro que llega es un número. Es el valor que corresponde a la posición del array donde se encuentra el alien que se va a eliminar. En este ejemplo he mantenido la correlación entre el array aliens de Universo y el array kinAliens de Dibujante.

Primero se obtiene la instancia del tipo Kinetic.Image que habíamos guardado en el paso anterior. Luego la eliminamos del array de la forma que ya aprendimos: primero con erase pasando como parámetro el valor a eliminar y luego con clean para limpiar los valores null y undefined. Finalmente, quitamos la instancia del layer con el método remove de Kinetic.Layer.

Resta ver cómo se actualiza la posición de los aliens a medida que avanzan. Eso lo vemos en el método redibujar de Dibujante.

Se suma un nuevo parámetro a los que ya recibía: un array de aliens. Luego de reubicar el proyectil y la nave, hay que reubicar los aliens. Para ello, primero se chequea si el array viene vacío. Si no es así, se procede a iterar. ¡Pero cuidado! No se está iterando el array que viene como parámetro, sino el array kinAliens que tiene las imágenes Kinetic.Image. Para cada una de ellas, se le cambia la posición y ahora sí, se obtiene el valor en x y en y del array que pasa como parámetro con objetos del tipo Alien. Se hace uso del índice i que se va incrementando en cada iteración para obtener estos valores.

Resumiendo

Hemos visto cómo es la clase Alien y de qué manera es posible manejar varios objetos de este tipo mediante un array. En esta etapa de desarrollo entró un poco más en juego que antes las funciones y la sintaxis de MooTools, el ejemplo más claro es la forma de utilizar Array.each para iterar un array.

Luego vimos cómo se generan nuevos enemigos, de qué manera posicionarlos en la pantalla y cómo hacerlos avanzar. En la última parte, volvimos sobre la vista para agregar los métodos que se encargan de dibujar y borrar los aliens y el que se encarga de redibuja los objetos.

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 *

*