miércoles, 27 de diciembre de 2017

Last Crown Warriors #3: Collisions

The Last Crown Warriors DEMO has been published recently. Since the last entry on Imanolea's Games about the game has been some time, and much functionality is pending for explanation. So I decided to ask on Twitter for the feature or system of Last Crown Warriors that you would like to be clarified on the blog.
From this announce I received an email from Jonas proposing to write about the collisions.

It is not the first time that I perceive interest in reading an entry dedicated to collisions. It is something present in almost any type of game, but a lot has been written about it. So I did not have the impression that I could offer something interesting on the subject.

Now, it is also true that this routine sometimes involves a lot of processing in each of the frames. And just as in the times of the classic microcomputers a good scroll routine could make your game stand out from the others, a good collision processing can allow you (also nowadays) to maximize the number of enemies, projectiles, power ups, and other interactive objects that we see on the screen.

And naturally, managing a pixel-level collision in a modern video game engine is not the same as trying to set up the collision of multiple elements in a system like the Game Boy. So in the end I found that it could be interesting to show the way in which I try to face this challenge. My system is far from being the best, but I think it can inspire others to try to work on their own approach.

In Last Crown Warriors it is necessary to manage collisions in the following list of situations:
  • Collision of the hero with background
  • Collision of the enemies with the background
  • Collision of the hero's weapon with the enemies
  • Collision of the hero's special weapon with the enemies
  • Collision of the hero with the enemies
  • Collision of the hero with interactive elements (such as the health heart)
All these collisions occur simultaneously, and it is crucial to minimize slowdowns as much as possible. So the routines must be extremely efficient.

The collisions previously enumerated are essentially managed through two systems, which I will explain throughout this entry.
  1. Collision of a character with another character
  2. Collision of a character with the background
And although they both manage collisions they have a completely different approach, as you will see below.

As always, I will try to focus on explaining the logical concepts behind the systems. So that they can be understood and applied without having to go through lines of code.

Collision of a character with another character

Let's take as an example the collision of the hero with the enemies for this routine. Let's see, to be able to verify if the hero has collided with an specific enemy we would have the following data:
  1. X and Y coordinate of the hero
  2. X and Y coordinate of the enemy
  3. Width and height of the collision area of the hero
  4. Width and height of the collision area of the enemy
With this information we could shape two areas (the hero's and the enemy's), and check if those two areas overlap. And while it would be a valid solution, it is not by far an acceptable solution. Because although the area of the hero would only be calculated at the beginning of the routine, the enemy area should be recalculated by each of the enemy characters.

And what can we do? Let's see, we know that all enemies share the same size collision area, so why not generate a single area around the hero that combines the area lengths of both characters? That is, instead of checking if two areas overlap, check if the enemy coordinate is within the combined collision area of the hero.

Purple: Point of character position.
Orange: Character collision area.
Blue: Combined collision area of the hero.

So we would only have to calculate the blue area of the image by adding the dimensions of the collision area of the hero and the enemy's. And once we have it, and with a maximum of four simple comparisons, we check if the enemy's position point is within that area. In this way we minimize the processing to be performed by each of the additional enemies with which we want to check if our hero has collided.

This code shows the core of the check between the enemy character's position point and the combined collision area.



We can see how this routine returns the address in memory of the enemy that we have just checked. So we could place it without problems inside a loop that goes through the list of enemies on which we want to perform the collision check.

Collision of a character with the background

In this case we will always go through the same routine when checking this type of collision. Since only the hero and the enemies can collide with the background, and both share the same dimensions in the collision area.

The data to keep in mind for this routine is:
  1. X and Y coordinate of the character
  2. Horizontal and vertical movement of the character
  3. X and Y position of the background
  4. Width and height of the map
The first thing would be to get the absolute coordinates of the character, that is, the coordinates of the character within the map. For this we add the position of the background to the coordinate of the character.

Then we need to know the candidate position point to collide. For this we take into account the movement of the character, and we obtain the position of the edge of the collision area closest to colliding. If the character is not moving, we would automatically conclude that he has not collided.

Once we have the candidate position, it is important to check its relative position within the tile. I mean, if the character is aligned with the tile grid (and taking into account its collision area) we will only check the collision of a single tile, while in the rest of the cases it will be necessary to go through more than one tile.

Purple: Collision candidate position point
Orange: Character collision area
Blue: Collisionable Tiles
Red: Solid tiles

Once we have clear the absolute positions of the tiles we must place them within the map, this is where the width and height of our map come into play.

And after having obtained the value relative to the collisionable tiles, how do we know if these are solid or not? Very simple, for each map we organize the tileset so that the solid tiles are at the end of the list and the non-solid ones at the beginning. Then we have only to specify what will be the position of the list from which the tile will be considered solid.


And this would be the explanation of how Last Crown Warriors collisions work. I hope that it has been interesting and has helped you to understand how a collision system can be set up in a limited hardware.

If you want to know how a particular aspect of the game works, tell me about it and I will try to talk about it in future posts.

Last Crown Warriors #3: Colisiones

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.
  1. Colisión de personaje con personaje
  2. 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:
  1. Coordenada X e Y del héroe
  2. Coordenada X e Y del enemigo
  3. Ancho y alto del área de colisión del héroe
  4. 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:
  1. Coordenada X e Y del personaje
  2. Desplazamiento horizontal y vertical del personaje
  3. Posición X e Y del fondo
  4. 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.