Desarrollo de un videojuego de naves en HTML5 – Parte 4: Colisiones

Finalmente hemos llegado a la última parte de esta serie de artículos sobre programación de videojuegos en HTML5. Por razones de espacio, este último artículo será dividido en dos, sin embargo ambos se referirán a la cuarta etapa de desarrollo.

Recordemos: en la última parte que vimos, ya aparecían naves enemigas, aunque al colisionar con la nave que controlamos o con los proyectiles que le disparamos, no ocurre nada. Ahora llegó el momento de que las cosas se destruyan y exploten.

El código fuente de esta última parte lo pueden encontrar aquí y el ejemplo terminado, en el siguiente enlace: Desarrollo de un videojuego de naves en HTML5 – Parte 4

Objetivos de esta última entrega

Vamos a empezar corrigiendo un error que había quedado de una etapa anterior, vinculado a la posición del proyectil, que en la vista no representaba el lugar que en realidad debía según el modelo. También vamos a eliminar algunas instrucciones que no hacen falta en la vista.

Luego nos vamos a detener en las colisiones. Tengan en cuenta que las colisiones proyectil con alien van a hacer desaparecer a ambos. En cambio, las colisiones alien con nave solo harán desaparecer a la nave para luego finalizar el juego.

Nuevas constantes

Aparecen algunas constantes nuevas, vinculadas al manejo de la animación de la explosión y al menú. Aquí muestro únicamente las que se agregan, por supuesto que las anteriores se mantienen.

La primera constante, T_DELAY sirve para introducir una demora luego de que la nave se destruye, antes de poner el mensaje de fin de juego.

Corrección de la posición del proyectil

En una etapa previa habíamos dicho que había un error en la posición en la cual se crea el proyectil. Este es el momento de arreglarlo, o tendremos problemas a la hora de chequear las colisiones cuando les disparemos a los aliens.

El método disparar en Nave tendrá un pequeño ajuste:

La nueva posición en el eje x hace que el proyectil aparezca en el medio de la nave y no en la esquina izquierda que era donde lo venía haciendo en el modelo.

Sin embargo, en la vista, se veía bien. Es decir, el disparo salía del medio de la nave. Esto era porque hacíamos una corrección al reposicionar el proyectil en el método redibujar de Dibujante.

Pero ahora esto ya no es necesario así que se puede cambiar por:

De esta forma, la vista representa perfectamente lo que ocurre en el modelo.

Eliminar los stage.draw en la vista

Al final de dos métodos de la clase Dibujante se invoca Stage.draw(), una llamada para forzar que el canvas se vuelva a dibujar. Primero se hace esto en initialize, el constructor y luego en dibujarBala.

Pero si pensamos un poco, nos daremos cuenta que esto es innecesario y que está de más. Existe un método, redibujar, que es invocado en periodos regulares de tiempo para mantener actualizada la vista. Dentro de dibujar se llama a Stage.draw, por lo tanto no es necesario que los otros métodos también lo hagan.

Por lo tanto, en esta etapa de desarrollo opté por suprimir esa última instrucción en initialize y en dibujarBala.

Colisiones en el modelo

Vamos a empezar a ver cómo se determina si hay colisión o no entre los objetos que tenemos en este videojuego. Universo contiene todos los elementos que aparecen. Serán, entonces, dos métodos de esta clase los que se ocuparán de realizar esta tarea: chocaProyectilAlien y chocaNaveAlien. El primero determinará si el proyectil disparado impactó en algún alien y el segundo si la nave chocó con algún alien.

Colisión alien – proyectil

El objetivo de este método es determinar si el proyectil ha colisionado con alguno de los aliens. Para ello, se recorre la lista de aliens y se va chequeando la posición del proyectil y la de cada alien. Si en algún momento el impacto ocurre, entonces se anula el proyectil y se devuelve el número de alien involucrado. Si no ocurre nada de esto, se devuelve null.

La variable numero es la que se utilizará para retornar valores. Con el objetivo de no perder la referencia dentro del Array.each, se pasan a variables locales this.bala y this.aliens. Si se usaran estos atributos directamente dentro, se entraría en conflicto respecto a la palabra clave this.

Array.each recorre uno por uno los aliens que existen y analiza si el proyectil está en la misma posición. De ser así, se elimina el alien del array y se guarda el número que lo identifica. Luego, si esta variable tiene un valor definido, se pierde la referencia al proyectil, lo que es equivalente a suprimirlo del modelo. Finalmente se devuelve el número de alien. Esto servirá para posteriormente borrarlo de la vista.

Veamos en detalle la condición por la cual se determina que hay colisión.

Son tres términos vinculados entre sí por ANDs, esto significa que los tres deben ser verdaderos para que la expresión completa sea verdadera. Los primeros dos están vinculados con la coordenada x de los elementos involucrados. El tercero, con la coordenada y.

Analicemos cada uno por separado.

El primer término chequea si el proyectil se encuentra a la derecha del borde izquierdo del alien. Pueden tomar bala.getPosX() + IMG_PROY_ANCHO/2 como la mitad del proyectil en la coordenada x.

proyectil-naves

El segundo término se encarga de comprobar si el proyectil se encuentra a la izquierda del borde derecho del alien. Noten que IMG_PROY_ANCHO/2 aparece del otro lado de la desigualdad restando. Es lo mismo que si estuviera del lado izquierdo sumando.

El tercer término, a su vez, se divide en dos, pero es mucho más fácil de analizar. La primera parte chequea que el proyectil esté por debajo del borde superior del alien. ¡Recuerden que el canvas se incrementa hacia abajo y que el modelo sigue ese mismo sistema de coordenadas! La segunda parte, chequea que el proyectil esté por encima del borde inferior de la nave, por eso es necesario sumar IMG_ALIEN_ALTO.

proyectil-alien

Noten que las áreas grises son las posiciones donde el proyectil no puede estar ubicado para cumplir con la condición. No olvidemos que cumplir con ella de forma individual no implica colisión con el alien. Por eso aparecen varios proyectiles indicando las posibles ubicaciones que serían válidas para cumplir la condición.

Sumadas las tres condiciones vemos que la única posibilidad se trata de una colisión con el área rectangular que representa el alien.

3-condiciones

Es decir que consideramos al alien cuadrado a la hora de las colisiones. Como el juego es rápido, el proyectil pequeño y su movimiento es salteado (es decir, que no recorre todos los puntos al moverse) no llega a notarse el momento del impacto, que puede llegar a ser en un área transparente del PNG que estamos usando. Si el juego fuera más lento, podría parecer que el alien se destruye antes de que el proyectil lo toque.

En este caso, el alien es un triángulo y se podría considerar plantear las restricciones del if en forma triangular para ganar precisión. Esto añadiría algo de complejidad matemática. Habría que plantear las desigualdades de forma distinta; entrarían en juego las rectas oblicuas.

condicion triangular

También existen librerías que se encargan de determinar si el contacto o colisión lo hace parte de la imagen opaca o la transparente de un PNG.

Colisión nave – alien

Este método determinará si el alien, al llegar a la parte de abajo del recuadro del juego, ha colisionado con la nave que controlamos. Veamos el código.

Se empieza definiendo una variable en false. Luego, se recorren todos los aliens usando Array.each y se chequea si alguno está en contacto con la nave. De ser así, la variable que estaba en false se convierte en true. Finalmente, se la devuelve. La comprobación previa de si la nave es distinta de null, tiene que ver con las reiteradas veces que se ejecutará este método.

Ahora llega el momento de desmenuzar y analizar detalladamente el if que comprueba si hubo o no colisión. Como es muy grande, es preferible acomodarlo un poco para entenderlo mejor.

Separé el código del if en tres bloques claramente distinguibles. Las dos primeras partes están relacionadas con el eje x y la última, con el eje y. El primero chequea que el borde izquierdo de la nave esté antes que el borde derecho del alien, al mismo tiempo que el borde derecho de la nave esté después que el borde derecho del alien.

El segundo bloque se encarga de comprobar que el borde izquierdo del alien esté antes que el borde derecho de la nave y que el borde derecho del alien, esté después.

Por último, la tercera condición chequea que el alien esté por debajo de la nave.

nave-alien-colision

En línea continua pueden ver las posiciones en x y en y que se están comparando en cada condición. Al igual que antes, al juntar las tres condiciones, obtenemos las posibilidades de colisión, que en este caso son dos.

2-colisiones

Si se fijan, la tercera condición, que chequea el eje y, tiene dos detalles a tener en cuenta. Por un lado, noten que siempre comprobamos dos posiciones, generalmente se busca que la nave / proyectil / alien o el elemento que sea, se encuentre entre dos coordenadas. Pero en este caso, los aliens vienen desde arriba y se mueven en forma recta, la única posibilidad de colisión en el eje y (suponiendo que hay alineación en el x) es que la posición y del alien sea mayor que el de la posición y de la nave. Como la nave está siempre estática en el eje y, y como los aliens siguen una trayectoria recta, esta condición se simplifica.

Por otro lado, aparece un 25 suelto que está restando. Es un ajuste que introduje debido a que la forma del alien es un triángulo y en los vértices del área en realidad no se ve ninguna imagen. Si suprimen este ajuste, verán que la colisión ocurrirá en cuanto las áreas de ambos elementos se toquen, quitándole realismo al juego.

Chequeo de colisiones en el controlador

Los métodos en Universo sirven para ser invocados y, a través de ellos, determinar si hubo o no colisiones. Sin embargo, queda pendiente saber cuándo van a ser accedidos.

Está claro que las colisiones se pueden dar en cualquier momento del juego, así que será necesario estar chequeando esto de forma continua en el bucle de juego, es decir, en el método actualizar de la clase Juego.

Empecemos viendo la comprobación del choque entre el proyectil y los aliens.

Primero se verifica si el proyectil es distinto de null. Esto se hace para saber si ha sido disparado. En caso de no ser así, entonces no se chequean colisiones vinculadas al proyectil. De lo contrario, se llama a chocaProyectilAlien y la variable numero recibe null si no se destruyó ninguno o el número de alien destruido.

Más abajo, si numero tiene valor distinto de null, es decir, si algún alien ha sido impactado por un proyectil, se llama a borrarAlien de Dibujante para quitarlo de la vista. Luego se hace lo mismo con el proyectil a través de borrarBala. Finalmente, se crea una explosión, pero lo vinculado a esto lo veremos en la segunda parte de este artículo.

Ahora veamos la colisión entre la nave y los aliens.

Se comprueba si chocaNaveAlien devuelve verdadero. De ocurrir esto, entonces la nave a colisionado con un alien. Dejamos una vez más de lado las explosiones. Lo siguiente es destruir la nave. Esto es lo que hace el método destruirNave en Universo.

Simplemente pierde la referencia a la nave creada anteriormente.

Luego, se borra la nave de la vista. A continuación, el código del método borrarNave en Dibujante.

Se quita el objeto Kinetic.Image de la capa layer.

La llamada al método finalizar la analizaremos a fondo en la segunda parte del artículo. Pero les puedo adelantar que lo que hace es demorar unos segundos para luego dar por terminado el juego.

Soy programador web y me desempeño como Líder Técnico 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 necesarios están marcados *


*