La
DEMO de Last Crown Warriors ha sido publicada recientemente. Desde la última entrada de Imanolea's Games dedicada al juego ha pasado algún tiempo, y mucha funcionalidad ha quedado pendiente de ser explicada. Así que decidí preguntar por Twitter por la característica o sistema de Last Crown Warriors que os gustaría que fuera destripado en el blog.
En base a este anuncio recibí un mail del compañero Jonas proponiendo escribir sobre las colisiones.
No es la primera vez que percibo interés por ver una entrada dedicada a las colisiones. Es algo con lo que tenemos que lidiar en casi cualquier tipo de juego, pero también algo sobre lo que se ha escrito mucho. Así que no me pareció un tema sobre el cual yo pudiera ofrecer algo interesante.
Ahora bien, también es cierto que esta rutina engloba en ocasiones gran parte del procesamiento de cada uno de los frames. Y al igual que en la época de los microordenadores una buena rutina de scroll podía hacer destacar tu juego de entre los demás, un buen procesamiento de colisiones puede permitirte (también a día de hoy) maximizar el número de enemigos, proyectiles, power ups, y demás objetos interactuables que vemos en pantalla.
Y naturalmente, no es lo mismo gestionar una colisión a nivel de píxel en un motor de videojuegos moderno que intentar plantear la colisión de multiples elementos en un sistema como el de la Game Boy. Así que en última instancia me pareció interesante mostrar la manera en la que yo procuro enfrentar este reto. Que si bien se encuentra lejos de ser la mejor, creo que puede inspirar a otros a probar suerte con su propio enfoque.
Concretamente en Last Crown Warriors vemos necesario gestionar colisiones en la siguiente lista de situaciones:
- Colisión de héroe con fondo
- Colisión de los enemigos con el fondo
- Colisión del arma del héroe con los enemigos
- Colisión del arma especial del héroe con los enemigos
- Colisión del héroe con los enemigos
- Colisión del héroe con elementos interactuables (como el corazón de salud)
Todas estas colisiones se producen simultáneamente, y es crucial minimizar en la medida de lo posible las ralentizaciones. Por lo que las rutinas deben ser extremadamente eficientes.
Las colisiones previamente enumeradas se gestionan en esencia a través de dos sistemas, que son los que explicaré a lo largo de esta entrada.
- Colisión de personaje con personaje
- Colisión de personaje con fondo
Y aunque los dos gestionen colisiones tienen un planteamiento completamente distinto, tal y como veréis a continuación.
Como siempre, procuraré centrarme en explicar los conceptos lógicos detrás de los sistemas. De manera que se puedan entender y aplicar sin necesidad de recorrer líneas de código.
Colisión de personaje con personaje
Vamos a tomar como ejemplo la colisión del héroe con los enemigos para esta rutina. Veamos, para poder comprobar si el héroe ha colisionado con un enemigo concreto dispondríamos de los siguientes datos:
- Coordenada X e Y del héroe
- Coordenada X e Y del enemigo
- Ancho y alto del área de colisión del héroe
- Ancho y alto del área de colisión del enemigo
Con esta información podríamos conformar dos áreas (la del héroe y la del enemigo), y comprobar si esas dos áreas se superponen. Y si bien sería una solución válida, no es ni de lejos una solución aceptable. Ya que aunque el área del héroe sólo la calcularíamos al principio de la rutina, el área enemiga debería recalcularse por cada uno de los personajes enemigos.
¿Y qué podemos hacer? A ver, sabemos que todos los enemigos comparten el mismo tamaño de área de colisión, así que, ¿por qué no generar una única área alrededor del héroe que combine las longitudes de área de ambos personajes? Es decir, en lugar de comprobar si dos áreas se superponen, comprobar si la coordenada enemiga se encuentra dentro del área de colisión combinada del héroe.
Morado: Punto de posición de personaje.
Naranja: Área de colisión de personaje.
Azul: Área de colisión combinada del héroe.
Así sólo tendríamos que calcular el área azul de la imagen sumando las dimensiones del área de colisión del héroe y de un enemigo. Y una vez la tengamos, y con un máximo de cuatro comparaciones simples, comprobamos si el punto de posición del enemigo se encuentra dentro de dicha área. De esta forma minimizamos el procesamiento a realizar por cada uno de los enemigos adicionales con los que queramos comprobar si ha colisionado nuestro héroe.
Este extracto de código muestra el núcleo de la comprobación entre el punto de posición del personaje enemigo y el área de colisión combinada.
Podemos ver como esta rutina nos devuelve la dirección en memoria del enemigo sobre el que acabamos de realizar la comprobación. Así que podríamos ubicarla sin problemas dentro de un bucle que recorra la lista de enemigos sobre los que queremos realizar la comprobación de colisión.
Colisión de personaje con fondo
En este caso pasaremos siempre por la misma rutina a la hora de comprobar este tipo de colisión. Ya que únicamente el héroe y los enemigos pueden colisionar con el fondo, y ambos comparten las mismas dimensiones en el área de colisión.
Los datos a tener en cuenta para esta rutina son:
- Coordenada X e Y del personaje
- Desplazamiento horizontal y vertical del personaje
- Posición X e Y del fondo
- Ancho y alto del mapa
Lo primero sería conseguir las coordenadas absolutas del personaje, es decir, las coordenadas del personaje dentro del mapa. Para ello sumamos la posición del fondo a la coordenada del personaje.
Luego necesitamos saber el punto de posición candidato a colisionar. Para ello tenemos en cuenta el desplazamiento del personaje, y obtenemos la posición del extremo del área de colisión más próximo a colisionar. Si el personaje no se está desplazando, interpretaríamos automáticamente que no ha colisionado.
Una vez tenemos la posición candidata, es importante comprobar qué posición relativa ocupa dentro del tile. Me explico, si el personaje se encuentra alineado con la rejilla de tiles (y teniendo en cuenta su área de colisión) verificaremos únicamente la colisión de un tile, mientras que en el resto de casos será necesario pasar por más de uno.
Morado: Punto de posición candidato a colisión
Naranja: Aréa de colisión del personaje
Azul: Tiles colisionables
Rojo: Tiles sólidos
Pues una vez tenemos claras las posiciones absolutas de los tiles debemos ubicarlas dentro del mapa, aquí es donde entra en juego el ancho y el alto de nuestro mapeado.
Y tras haber obtenido el valor relativo a los tiles colisionables, ¿cómo sabemos si estos son sólidos o no? Muy sencillo, para cada mapa organizamos el tileset de manera que los tiles sólidos quedan al final de la lista y los no sólidos al principio. Luego no tenemos más que especificar cuál será la posición de la lista a partir del cuál se considerará que el tile es solido.
Hasta aquí la explicación de cómo funcionan las colisiones de Last Crown Warriors. Espero que os haya resultado interesante y os haya servido para entender cómo se puede plantear un sistema de colisiones en un hardware como el de la Game Boy.
Si queréis saber cómo funciona algún aspecto concreto de juego, comentádmelo e intentaré hablar sobre él en próximas entradas.