09 noviembre 2008

Remake (parcial) de Fruity Frank... 15 - Colisiones por coordenadas

Sabemos cómo comprobar colisiones usando nuestro "mapa" de juego. Es interesante como primera aproximación, pero generalmente querremos algo "más fino": que el movimiento vuelva a a ser suave (algo que haremos pronto) y que las colisiones se puedan comprobar antes, en cuanto un elemento "roce" al otro (eso sí que lo haremos ahora).

Vamos a mejorar las colisiones que se basan exactamente en posiciones del mapa, para ver una forma de comprobar colisiones entre dos imágenes. Aun así, lo que haremos tampoco será perfecto: comprobaremos si el rectángulo de una imagen se solapa con el rectángulo de otra imagen, pero habitualmente esto será totalmente preciso, porque la mayoría de imágenes (personaje, enemigos, frutas) no ocupan por completo su rectángulo, así que puede que consideremos que ha habido una colisión cuando realmente se estén solapando dos "zonas negras" de las imágenes.

Otras alternativas más precisas incluirían el descomponer la imagen en una serie de rectángulos que sí contengan elementos visibles (lo que complicaría la forma de comprobar colisiones), o bien mirar los píxeles que se solapan para ver si son "transparentes", y entonces no dar la colision como válida (lo que también complicaría, además de ralentizar ligeramente). Por ahora no afinaremos tanto, y nos quedaremos con una aproximación más precisa que la de antes, que funcionará cuando un enemigo no se encuentre justo en una casilla del mapa, sino pasando de una a otra, pero que tampoco es totalmente precisa, especialmente con personajes que tengan formas irregulares.

Una forma de comprobar si se solapan dos rectángulos es ver qué ocurre con sus extremos: en horizontal, si el extremo izquierdo de una imagen está más a la derecha que el extremo derecho de la otra, no se solapan; para que se solapen, el extremo derecho de una imagen (x1) debe ser estar a la izquierda del extremo izquierdo de la otra (x1 < x2+ancho2), y ocurrir lo mismo a la inversa (x2 < x1+ancho1), y algo similar debe ocurrir en vertical:

if ((x2+an2 > x1)
&& (y2+al2 > y1)
&& (x1+an1 > x2)
&& (y1+al1 > y2))
colision = true;
else
colision = false;


Pero en nuestro caso, parte del trabajo ya está hecho: esa comprobación forma parte de la clase "SdlN":

public bool Colision(int x1, int y1, int an1, int al1,
int x2, int y2, int an2, int al2)
{
if ((x2+an2 > x1)
&& (y2+al2 > y1)
&& (x1+an1 > x2)
&& (y1+al1 > y2))
return true;
else
return false;
}


De modo que si nuestro juego se apoya en SdlN, basta con que comprobemos las colisiones así:

if (Colision(enemigos[i].x, enemigos[i].y, 
enemigos[i].ancho, enemigos[i].alto,
posXPersonaje, posYPersonaje,
anchoPersonaje, altoPersonaje) )
enemigos[i].activo = false;


En este caso, como primera aproximación, pero que todavía no sigue una lógica de juego real, cada vez que tocamos un enemigo, éste desaparece (activo = false). En un juego real, nuestro enemigo perdería una vida y volveríamos a comenzar la partida en el nivel actual. Si la colisión fuera entre un disparo y un enemigo, deberíamos dibujar una explosión antes de hacerlo desaparecer. Esos detalles pertenecen a la lógica de cada juego, y nos iremos acercando a ellos poco a poco.


De paso, vamos a hacer otra pequeña mejora en esta versión: la línea superior, que muestra la puntuación, el record y la cantidad de vidas que nos quedan, va a dejar de ser "predibujada". En vez de cargar la imagen que contiene casi todo eso fijo, vamos a ser nosotros los que mostremos los puntos (ya lo hacíamos), el record (que era fijo), la cantidad de vidas (aunque todavía no nos puedan matar nuestros enemigos) y un recuadro alrededor de todo ello:

// Tabla de records y vidas restantes
RectanguloRGBA(15,45,625,80, // Marco para records
255, 109, 8, // Naranja
255); // Opaco
EscribirTextoOculta("Puntos",20,60,
0x88, 0xFF, 0xFF, fuenteSans14);
EscribirTextoOculta(puntos.ToString("000000"),70,60,
0xFF, 0xFF, 0x88, fuenteSans14);
EscribirTextoOculta("Mejor puntuac",200,60,
0x88, 0xFF, 0xFF, fuenteSans14);
EscribirTextoOculta(mejorPunt.ToString("000000"),300,60,
0xFF, 0xFF, 0x88, fuenteSans14);
EscribirTextoOculta("Vidas",450,60,
0x88, 0xFF, 0xFF, fuenteSans14);
for (i=0; i<vidas-1; i++)
DibujarImagenOculta(personajeD, (short)(490+i*30),48);


(Por supuesto, esto supone algunos pequeños cambios en el juego, como añadir la variable "vidas" o la variable "mejorPunt").


Para más detalles, puedes ver todo el proyecto en: code.google.com/p/fruityfrank