lunes, 5 de mayo de 2008

Colisiones

El siguiente paso en la programación de nuestro juego no puede ser otro que el de no salirnos de él. Es decir, nuestro héroe debe andar seguro por las sendas y caminos del mundo que le hemos creado, sin salirse. Este tema es el conocido como colisiones y PALib lo resuelve de un modo muy cómodo.

Pero antes de nada vamos a cambiar el mundo. Quiero decir pongamos otro fondo algo más interesante, uno con un lago y un poco más grande, ¿Qué tal este?
Es algo más grande que el anterior y tiene un par de lagos. ¿Rosas? es para jugar con las transparencias. Crearemos otra imagen igual pero de color azul y así tenemos el agua.
Para integrar este nuevo fondo (lo llamaremos fondo.png) haremos los siguientes pasos:

  1. lo guardamos en la carpeta gfx (dentro de source),
  2. ejecutaremos el 'PAGC Frontend',
  3. cargaremos el ini (load ini), así no perdemos el resto de cosas (a nuestro héroe)
  4. Si hemos borrado el fondo anterior y el nuevo se llama fondo con hacer cargar ini ya se cargará en la pestaña background, por lo que únicamente quedaría hacer 'save and convert'.
Ya está tenemos nuevo fondo. Para cargar el fondo de agua será algo parecido. Yo he credo una imagen del mismo tamaño qe la anterior de color azul (agua.png). Así es como debería quedar la pestaña de backgrounds:
ahora veamos los cambios en el código:

En primer lugar la carga de imágenes. Como ya comentamos en le capítulo de los fondos Palib pone a nuestra disposición cuatro niveles para poder superponer fondos, en nuestro caso actual cargaremos el fondo en el nivel 2 y el agua en el tres (el dos se superpone al tres)


PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_TWO, fondo);
PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_THREE, agua);
PA_InitParallaxX(SCREEN_TOP, 0, 0, 256, 0);
PA_InitParallaxY(SCREEN_TOP, 0, 0, 256, 0);


Otro cambio está a la hora de mostrar a nuestro héroe, deberá hacerlo sobre la capa dos y no sobre la tres.

PA_SetSpritePrio(SCREEN_TOP, HEROE_SPRITE, BACKGROUND_TWO);


Antes del while vamos a colocar la pantalla en su sitio:

PA_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);

Y por último antes del waitForVBL pondremos estas líneas:

if (estadoAnimacion != ANIMACION_PAUSA) {
PA_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);//cambio
}


Ya tenemos preparado todo para realizar las colisiones. Como hemos comentado antes PaLib nos permite hacerlo de una forma más o menos sencilla. Debemos crear una imagen con las zonas que queremos que sean de libre acceso y las que no.

PaLib permite a partir de una imagen crear un array de números, para ello divide la imagen en cuadrados de 8x8, a modo de baldosas, y a cada cuadrado distinto le asigna un número. Por ejemplo si el primer cuadrado (la esquina superior izquierda) es rosa, a todo cuadrado de ese color lo asignará un 0.
Pongamos un ejemplo, el mapa para nuestro mundo será este:

Si te fijas bien, en la esquina superior izquierda hay un cuadrado rosa, así me aseguro que el 0 es el espacio rosa. ¿Pero como he hecho este mapa? pues usando el PhotoShop.

En la siguiente imagen se puede ver que he creado una imagen con tres capas. La más profunda tiene nuestro mundo. La del medio es donde he creado la zona rosa (con una transparencia del 30%, para ver como va quedando) y por último una capa con una trama de 8x8 (nuestras baldosas).

Al cargar el fichero, mapa.png, en el PAGC Frontend, nos habrá creado el array que deberemos leer para saber si podemos pisar en esa zona o no. El array es lineal por lo que a partir de las corrdenadas X,Y del muñeco (no de la pantalla) deberemos hallar el cuadrante de la imagen dividida en cuadrados de 8x8 y a continuación calcular el índice de ese cuadrante en nuestro array.



NOTA. No hace falta cargar la imagen mapa en el juego, sólo queremos el array.
Y OTRA COSA. Este es el sistema que he seguido yo, seguro que se puede hacer de otras formas.

¿Parece complicado? Pues es más difícil explicarlo que hacerlo.
La siguiente función es capaz de decirnos, a partir de nuestra posición en pantalla, de que tipo es la baldosa que pisamos.

u8 obtenerBaldosa(s32 x, s32 y) {
return mapa_Map[((y>>3) * 92) + (x>>3)]; //92 es el número de cuadrados de 8x8 (736 pixeles de ancho / 8 =92
}

Finalmente, como nuestro personaje, ocupa más de una baldosa pues hay que llamar a obtenerBaldosa cuatro veces. Aquí os dejo todo el código:


#include <PA9.h>

#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

#include "devnintendods.h"

#define BACKGROUND_X 80
#define BACKGROUND_Y 132

#define HEROE_PALLETE 0
#define HEROE_SPRITE 0
#define HEROE_X 120
#define HEROE_Y 88
#define HEROE_VELOCIDAD 5

#define ANIMACION_PAUSA 0
#define ANIMACION_ABAJO 1
#define ANIMACION_ARRIBA 2
#define ANIMACION_IZQUIERDA 3
#define ANIMACION_DERECHA 4

#define ESPACIO_LIBRE 0

u8 obtenerBaldosa(s32 x, s32 y);

int main(int argc, char ** argv) {
    PA_Init();
    PA_InitVBL();

    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_TWO, fondo);
    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_THREE, agua);

    PA_LoadSpritePal(SCREEN_TOP, HEROE_PALLETE,(void*)heroe_Pal);
    PA_CreateSprite(SCREEN_TOP, HEROE_SPRITE, (void*)heroe_Sprite, OBJ_SIZE_16X32, COLOR_MODE_256, HEROE_PALLETE, HEROE_X, HEROE_Y);
    PA_SetSpritePrio(SCREEN_TOP, HEROE_SPRITE, BACKGROUND_TWO);

    s32 fondoX = BACKGROUND_X;
    s32 fondoY = BACKGROUND_Y;
    s32 estadoAnimacion = ANIMACION_PAUSA;

    PA_InitParallaxX(SCREEN_TOP, 0, 0, 256, 0);
    PA_InitParallaxY(SCREEN_TOP, 0, 0, 256, 0);

    PA_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);
    PA_WaitForVBL();
    //PA_InitText(SCREEN_BOTTOM, BACKGROUND_ZERO); // para debugear!!
    while  (1) {
        if (Pad.Held.Up) {
            if (estadoAnimacion != ANIMACION_ARRIBA) {
                estadoAnimacion = ANIMACION_ARRIBA;
                PA_StartSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 3, 5, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0);
            }
        } else if (Pad.Held.Down) {
            if (estadoAnimacion != ANIMACION_ABAJO) {
                estadoAnimacion = ANIMACION_ABAJO;
                PA_StartSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 0, 2, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0);
            }
        } else if (Pad.Held.Left) {
            if (estadoAnimacion != ANIMACION_IZQUIERDA) {
                estadoAnimacion = ANIMACION_IZQUIERDA;
                PA_StartSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 6, 8, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 1);
            }
        } else if (Pad.Held.Right) {
            if (estadoAnimacion != ANIMACION_DERECHA) {
                estadoAnimacion = ANIMACION_DERECHA;
                PA_StartSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 6, 8, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0);
            }
        } else {
            estadoAnimacion = ANIMACION_PAUSA;
            PA_StopSpriteAnim(SCREEN_TOP, HEROE_SPRITE);
        }
        if (estadoAnimacion != ANIMACION_PAUSA) {
            s32 x = fondoX + Pad.Held.Right - Pad.Held.Left;
            s32 y = fondoY + Pad.Held.Down - Pad.Held.Up;
            u8 baldosa = obtenerBaldosa(x + HEROE_X + 8, y + HEROE_Y + 16);
            baldosa |= obtenerBaldosa(x + HEROE_X, y + HEROE_Y + 16);
            baldosa |= obtenerBaldosa(x + HEROE_X + 8, y + HEROE_Y + 8);
            baldosa |= obtenerBaldosa(x + HEROE_X, y + HEROE_Y + 8);
            //PA_OutputText(SCREEN_BOTTOM, 0, 6, "baldosa: %d      ", baldosa); // para debugear!!
            if (baldosa == ESPACIO_LIBRE) {
                fondoX = x;
                fondoY = y;
                PA_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);
            } else {
                estadoAnimacion = ANIMACION_PAUSA;
            }
        }
        PA_WaitForVBL();
    }
    return 0;
}

u8 obtenerBaldosa(s32 x, s32 y) {
    return mapa_Map[((y>>3) * 92) + (x>>3)];
}


En la próxima entrega, veremos como hacer que no se suba a lo árboles.

Saludos