martes, 13 de enero de 2009

Tetris para la Nintendo DS

Buenas

Después del primer tutorial de este blog, un juego de Pokemon vamos a programar otro juego más conocido, el Tetris.

Seguiremos utilizando las librerías PALib, aunque vamos a utilizar otros conceptos distintos a los hasta ahora vistos, que luego nos servirán para crear más juegos. Si el primer programa se utilizaron los Sprites a tope, en este vamos a utilizar los Tiles.

Entre los comentarios que aparecen en el blog, los cuales agradezco muchísimo, había uno donde se me animaba a hacer un Super Mario Bros. Lo haremos, primero el Tetris, y con los conceptos que vamos a introducir con él, podremos construir este juego de plataformas. He encontrado un tutorial donde explican un juego de plataformas, pero creo que podemos hacerlo algo más profesional.

Un par de comentarios me indicaban que el tutorial de Pokemon ha quedado bien como tutorial y algo cojo como juego. Pues bien, estoy pensando una serie de modificaciones para hacerlo, también, más 'profesional'.

Por mi parte, intentaré ser más constante con las publicaciones. Me gusta tener el juego más o menos acabado antes de empezar con los tutoriales, y eso me lleva tiempo.

Pero cada cosa a su tiempo, primero el Tetris, que vereís es sencillísimo.

Como siempre, lo primero es copiar el template de PALib (C:\devkitPro\PAlibTemplate) a la ubicación que deseemos, en mi caso C:\devDS\TetrisDS, y ya podemos empezar.

En primer lugar las estructuras que vamos a usar.

El tetris es un juego de tablero, por lo que haremos un array con las casillas del tablero, es decir una matriz de 10x20, 10 columnas por 20 filas.

#define TABLERO_ANCHO 10
#define TABLERO_ALTO 20

u8 tablero[TABLERO_ANCHO][TABLERO_ALTO];

Y las fichas del tetris son fichas de 4x4 casillas. La verdad es que todas las fichas son de 3x3 menos dos: el cuadrado que es de 2x2 y la ficha larga que es de 4x1 (o 1x4).
En resumen, todas entrarán en una estructura de 4x4, pero a la hora de hacerlas girar, cada una deberá hacerlo dentro de su tamaño, es decir, necesitaremos un campo que denominaremos radio.

#define FICHA_ANCHO 4
#define FICHA_ALTO 4

typedef struct {
  u8 x, y, radio;
  u8 ficha[FICHA_ANCHO][FICHA_ALTO];
} tipo_ficha;

tipo_ficha ficha;

Cómo podéis observar, también he metido las coordenadas del tablero por donde andará la ficha (x, y).

Bueno, por ahora no hemos visto mucho de las PALib. Esto es por que el Tetris tiene mucho trabajo de arrays, y hasta que acabemos con todas estas funciones no podremos empezar ha programar el resto del juego.

Por ahora vamos a presentar alguna función más, por ejemplo la de crear algún tipo de ficha:

#define CASILLA_VACIA 1

void crear_ficha_z(u8 color) {
  ficha.radio = 3;
  ficha.ficha[0][0] = color;
  ficha.ficha[1][0] = color;
  ficha.ficha[2][0] = CASILLA_VACIA;
  ficha.ficha[3][0] = CASILLA_VACIA;
  ficha.ficha[0][1] = CASILLA_VACIA;
  ficha.ficha[1][1] = color;
  ficha.ficha[2][1] = color
  ficha.ficha[3][1] = CASILLA_VACIA;
  ficha.ficha[0][2] = CASILLA_VACIA;
  ficha.ficha[1][2] = CASILLA_VACIA;
  ficha.ficha[2][2] = CASILLA_VACIA;
  ficha.ficha[3][2] = CASILLA_VACIA;
  ficha.ficha[0][3] = CASILLA_VACIA;
  ficha.ficha[1][3] = CASILLA_VACIA;
  ficha.ficha[2][3] = CASILLA_VACIA;
  ficha.ficha[3][3] = CASILLA_VACIA;
}

Seguiremos. El juego es muy sencillo, pero tiene un montón de funciones de manejo de arrays.

Saludos

lunes, 5 de enero de 2009

Fundido de pantalla

Vamos a realizar un último efecto gráfico en el juego, un Fundido de pantalla. En este caso es simplemente el oscurecimiento de la pantalla. Esto se consigue con la función PA_SetBrightness.

De esta forma cargaremos un par de portadas al juego y lo dejaremos acabado.

Las portadas serán estas:
la portada:
y la subportada:

Crearemos una función que las cargue y las descargue. Usaremos el Stylus para pulsar sobre la pantalla y poder así empezar el juego.

Deberemos añadir a nuestro código la definición de la función y la función cargarPortadas.

#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_REFLEJO_SPRITE 1
#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);
void cargarPortadas();

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

    cargarPortadas();

    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_TWO, fondo);
    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_THREE, agua);
    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, sobre_fondo);
    
    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);

    PA_CreateSprite(SCREEN_TOP, HEROE_REFLEJO_SPRITE, (void*)heroe_Sprite, OBJ_SIZE_16X32, COLOR_MODE_256, HEROE_PALLETE, HEROE_X, HEROE_Y + 8);
    PA_SetSpritePrio(SCREEN_TOP, HEROE_REFLEJO_SPRITE, BACKGROUND_THREE);
    PA_SetSpriteVflip(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
    PA_SetSpriteMode(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
    PA_EnableSpecialFx(SCREEN_TOP, SFX_ALPHA, 0, SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3 | SFX_BD);
    PA_SetSFXAlpha(SCREEN_TOP, 12, 15);

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

    PA_InitParallaxX(SCREEN_TOP, 0, 256, 256, 0);
    PA_InitParallaxY(SCREEN_TOP, 0, 256, 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);

                PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 3, 5, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);
                
                PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 0, 2, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);

                PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 6, 8, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);

                PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 6, 8, HEROE_VELOCIDAD);
                PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 0);
            }
        } else {
            estadoAnimacion = ANIMACION_PAUSA;
            PA_StopSpriteAnim(SCREEN_TOP, HEROE_SPRITE);
            PA_StopSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_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)];
}

void cargarPortadas() {
    PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ZERO, portada);
    PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ZERO, subportada);
    bool salir = false;
    while  (!salir) {
        salir = Stylus.Held;
        PA_WaitForVBL();
    }
    u8 i = 0;
    for (i = 0; i < 32; i++) {
        PA_SetBrightness(SCREEN_TOP, -i);
        PA_SetBrightness(SCREEN_BOTTOM, -i);
        PA_WaitForVBL();
    }
    PA_DeleteBg(SCREEN_BOTTOM, BACKGROUND_ZERO);
    PA_DeleteBg(SCREEN_TOP, BACKGROUND_ZERO);
    PA_SetBrightness(SCREEN_TOP, 0);
    PA_SetBrightness(SCREEN_BOTTOM, 0);
    PA_EasyBgLoad(SCREEN_BOTTOM, BACKGROUND_ZERO, portada);
    return;
}


Este juego por ahora lo dejamos acabado.

Saludos y Feliz año.

Enlaces patrocinados:

miércoles, 24 de diciembre de 2008

Instalar el kit de desarrollo (II)

Hoy es 24 de Diciembre de 2008, y he tenido que volver a instalar el kit de desarrollo DevkitPro. Por supuesto las versiones han cambiado de cuando presenté este blog por primera vez y existe algún cambio. El problema radica en que PALib no es 100% compatible con el entorno de desarrollo, por lo que debemos hacer los siguientes pasos (siempre que queramos instalar el entorno por primer vez):
  1. Descargaremos el kit de desarrollo DevkitPro Updater y lo instalamos en la carpeta C:\devkitPro,
  2. Borramos la carpeta “devkitARM” de C:\devkitPro y lo reemplazamos por devkitARM r21,
  3. Poner el contenido de la librería libfat en C:\devkitPro\libnds,
  4. Descargar el instalador de PAlib e instalarlo en C:\devkitPro.
Y, a disfrutar programando....

Saludos y Feliz Navidad.

jueves, 13 de noviembre de 2008

El reflejo sobre el lago

Por fin llegamos al reflejo de nuestro personaje en el lago. Lo prometimos en la primera entrega de este blog. Este efecto es una de las cosas que dará calidad a nuestro juego. Parece difícil, pero como todo en esto del software, es muy sencillo, sólo hay que conocer la API de PALib y echarle imaginación.

PALib contiene funciones para que un Sprite se visualice con cierta transparencia, es decir, un reflejo en un lago no va a ser una imagen tan nítida como el original. ¡¡¡Esta función la usaremos!!!

También tenemos una función que posibilita girar un Sprite, (que ya la hemos utilizado), y resulta que el reflejo no es más que nuestro Sprite girado verticalmente. ¡¡¡ Esta también la usaremos!!!

Y Por último, el reflejo se verá sobre el agua, no sobre la hierba. Así que el Sprite andará sobre la capa del agua, por debajo del fondo, y alehop!!!, ya tenemos nuestro reflejo.

¡Parecía imposible!

1. PA_CreateSprite(SCREEN_TOP, HEROE_REFLEJO_SPRITE, (void*)heroe_Sprite, OBJ_SIZE_16X32, COLOR_MODE_256, HEROE_PALLETE, HEROE_X, HEROE_Y + 8);
2. PA_SetSpritePrio(SCREEN_TOP, HEROE_REFLEJO_SPRITE, BACKGROUND_THREE);
3. PA_SetSpriteVflip(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
4. PA_SetSpriteMode(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
5. PA_EnableSpecialFx(SCREEN_TOP, SFX_ALPHA, 0, SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3 | SFX_BD);
6. PA_SetSFXAlpha(SCREEN_TOP, 12, 15);

Vamos a explicar paso a paso:

En 1 cargamos a nuestro héroe pero con el identificador HEROE_REFLEJO_SPRITE.
En 2 mandamos el reflejo a la capa sobre el agua.
En 3 giramos verticalmente el Sprite.
En 4 - 6 habilitamos la transparencia.
Y luego no olvidarnos de que al mover a nuestro héroe movamos también a su reflejo.



Aquí os dejo el código (Como siempre, esta es la forma que me he inventado. No creo que exista una función en PALib que cree una sombra de forma automática, pero...):

#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_REFLEJO_SPRITE 1
#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_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, sobre_fondo);

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);

PA_CreateSprite(SCREEN_TOP, HEROE_REFLEJO_SPRITE, (void*)heroe_Sprite, OBJ_SIZE_16X32, COLOR_MODE_256, HEROE_PALLETE, HEROE_X, HEROE_Y + 8);
PA_SetSpritePrio(SCREEN_TOP, HEROE_REFLEJO_SPRITE, BACKGROUND_THREE);
PA_SetSpriteVflip(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
PA_SetSpriteMode(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 1);
PA_EnableSpecialFx(SCREEN_TOP, SFX_ALPHA, 0, SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3 | SFX_BD);
PA_SetSFXAlpha(SCREEN_TOP, 12, 15);

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

PA_InitParallaxX(SCREEN_TOP, 0, 256, 256, 0);
PA_InitParallaxY(SCREEN_TOP, 0, 256, 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);

PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 3, 5, HEROE_VELOCIDAD);
PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);

PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 0, 2, HEROE_VELOCIDAD);
PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);

PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 6, 8, HEROE_VELOCIDAD);
PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_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);

PA_StartSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 6, 8, HEROE_VELOCIDAD);
PA_SetSpriteHflip(SCREEN_TOP, HEROE_REFLEJO_SPRITE, 0);
}
} else {
estadoAnimacion = ANIMACION_PAUSA;
PA_StopSpriteAnim(SCREEN_TOP, HEROE_SPRITE);
PA_StopSpriteAnim(SCREEN_TOP, HEROE_REFLEJO_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)];
}


Este mismo efecto se puede hacer sobre otro tipo de suelo y da un aspecto de suelo brillante muy chulo.

En la próxima entrega crearemos algún efecto más para la portada del juego y así vamos dando forma a los últimos detalles de nuestro juego.

Saludos

lunes, 10 de noviembre de 2008

Ultimos efectos gráficos

En estos momentos el juego está ya muy avanzado, únicamente tenemos el pequeño problemilla, de que nuestro héroe se sube a los árboles y a los setos, y eso no puede ser.

Para arreglar esto jugaremos con las capas que nos da PALib. Recordemos que tenemos cuatro posibles fondos y que nuestros Sprites (en este caso nuestro héroe) puede andar por encima o por debajo de ellas.

Lo primero de todo es crear una imagen que sea una copia de la imagen fondo y quitar todo lo que nuestro personaje puede pisar: el camino, las flores, la parte inferior de las casas y de lo árboles. Esta es una imagen de como he ido borrando todos esos elementos de la imagen:


NOTA. Cuando decimos pisar no nos referimos a pisar con los pies, si no todo el Sprite, cabeza incluida.

La imagen resultante será esta, que la denominaremos sobre_fondo.png:

Y para cargar la imagen tendremos que cargarla con el gfx, volver a generar y finalmente añadir una línea y modificar dos en nuestro código:

Añadimos: PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_ONE, sobre_fondo);

y modificamos:
PA_InitParallaxX(SCREEN_TOP, 0, 256, 256, 0);
PA_InitParallaxY(SCREEN_TOP, 0, 256, 256, 0);

Y este el el resultado, nuestro héroe quedará oculto bajo los árboles y detrás de los tejados:

Para la próxima entrega el efecto sobre el lago!!!!

Saludos

Enlaces patrocinados:

viernes, 7 de noviembre de 2008

Un pequeño respiro

Muy buenas

El mes de octubre no he podido actualizar el blog, pero prometo seguir con él en cuanto me sea posible. Estoy muy agradecido a las personas que han dejado comentarios y que me animan a seguir con el blog.

Pronto tendremos más!!!

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