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

miércoles, 16 de abril de 2008

Animar nuestro Sprite

Para animar a nuestro héroe únicamente hace falta la sentencia PA_StartSpriteAnim. En ella definimos el sprite inicio y final de la animación así como la velocidad de esta.

Para controlar la animación haremos que cuando se pulse uno de los cursores el muñeco realice la animación de esa dirección, hasta que se pulse otro cursor, o no se deje de pulsar. A mi se me ha ocurrido hacerlo usando una variable que guarde el estado de la animación, con valores tipo ANIMACION_DERECHA, ANIMACION_IZQUIERDA, ANIMACION_PAUSA, ... La variable la llamaremos estadoAnimacion.
El código a incluir será:

Antes del bucle u32 estadoAnimacion = ANIMACION_PAUSA;

El if a poner en el bucle sería algo así:

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

Y con esto ya lo tenemos en movimiento.

El código quedará de la siguiente manera:

#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

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

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

    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_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);
    PA_WaitForVBL();
    while  (1) {
        fondoX += Pad.Held.Right - Pad.Held.Left;
        fondoY += Pad.Held.Down - Pad.Held.Up;

        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) {
            PA_ParallaxScrollXY(SCREEN_TOP, fondoX, fondoY);
        }    
        PA_WaitForVBL();
    }
    return 0;
}


¿Qué nos queda? Lo primero que nuestro héroe no se escape del mundo que le hemos creado. PALib, de nuevo, nos ayudará a lograr el que será nuestro próximo objetivo, 'la colisión'.

Un saludo

Enlaces patrocinados
gafas de sol
infografias
Peluches
hipermetropia
despedida de soltera en Cadiz
despedida de soltera en barcelona

Sprites

El siguiente paso en nuestro juego será poner a nuestro héroe a recorrer el mundo que le hemos creado en la anterior entrega.

Como hemos hecho que lo que se mueva sea el fondo, el muñeco estará en el centro de la pantalla.

Pero ¿Qué es un Sprite? Un sprite es un elemento gráfico que se superpone a los fondos, y que gracias a las transparencias, no borra a estos. Vamos a ver nuestro primer sprite:

Muy bien. En este Sprite tenemos a nuestro héroe con las distintas posturas que tendrá en el juego. Guardaremos dicha imagen en C:\devDS\PokemonDS\source\gfx como heroe.png. Para cargar la imagen en el juego, realizaremos los siguientes pasos:
  1. Ejecutar PAGC Frontend (el que está dentro de la carpeta gfx de nuestro source)
  2. Load INI (para cargar el INI donde tenemos el fondo, que si no perdemos el fondo!)
  3. En la pestaña de Sprites cargamos la imagen heroe.png
  4. Save and Convert
Como muchos os habéis imaginado el color rosa es el color transparente, y el espacio que hay entre cada muñeco es porque la DS (o PALib) soporta los siguientes tamaños de Sprites:

8x8 16x8 32x8
8x16 16x16 32x16
8x32 16x32 32x32 64x32
32x64 64x64

En nuestro caso el héroe entraba en una caja de 16x32 y como tenemos 9 posturas, pues la imagen mide 16 x 288.
¿Dónde están las posturas mirando a la izquierda? Pues resulta que podemos rotar al sprite, así que no nos van a hacer falta (porque para este Sprite el ir a la izquierda o a la derecha es idéntico, cosa que no siempre es así).

Pongamos el código necesario. Primero, añadiremos una nueva constante a nuestro fichero devnintendods.h, que quedará así:

#define SCREEN_TOP 1
#define SCREEN_BOTTOM 0

#define BACKGROUND_ZERO 0
#define BACKGROUND_ONE 1
#define BACKGROUND_TWO 2
#define BACKGROUND_THREE 3

#define COLOR_MODE_256 1

y después así es como quedará el main.c:
// Includes
#include <pa9.h> // Include for PA_Lib
#include "gfx/all_gfx.h" #include "gfx/all_gfx.c" #include "devnintendods.h" #define BACKGROUND_X 56 #define BACKGROUND_Y 132 #define HEROE_PALLETE 0 #define HEROE_SPRITE 0 #define HEROE_X 120 #define HEROE_Y 88 int main(int argc, char ** argv) { PA_Init(); PA_InitVBL(); PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_THREE, fondo); PA_InitParallaxX(SCREEN_TOP, 0, 0, 256, 0); PA_InitParallaxY(SCREEN_TOP, 0, 0, 256, 0); 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; while (1) { fondoX += Pad.Held.Right - Pad.Held.Left; fondoY += Pad.Held.Down - Pad.Held.Up; if (Pad.Held.Up) { PA_SetSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 4); PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0); } if (Pad.Held.Down) { PA_SetSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 0); PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0); } if (Pad.Held.Left) { PA_SetSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 6); PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 1); } if (Pad.Held.Right) { PA_SetSpriteAnim(SCREEN_TOP, HEROE_SPRITE, 6); PA_SetSpriteHflip(SCREEN_TOP, HEROE_SPRITE, 0); } PA_EasyBgScrollXY(SCREEN_TOP, BACKGROUND_THREE, fondoX, fondoY); PA_WaitForVBL(); } return 0; }

El código es muy sencillo: se carga la paleta de colores del sprite, podemos tener paletas compartidas y así ahorramos recursos, y después se crea el sprite. Si os fijáis las variables heroe_Pal y heroe_Sprite las crea el programa PAGC.

Sólo nos falta animar a nuestro héroe. Empezaremos por partes, para esta entrega con girarlo dependiendo de la dirección en que nos desplacemos será suficiente. if (Pad.Held.Left) nos indicará si se ha pulsado el cursor izquierdo, por lo que ponemos el Sprite en el primer frame de la posición izquierda y lo giramos. if (Pad.Held.Right) nos indica que hemos pulsado el cursor derecho y ponemos el frame en la primera posición que refleja el ir a la derecha. Aunque en este caso no haría falta girar el Sprite, como es posible que lo hayamos hecho al ir a la izquierda, tendremos que anular dicho giro.

Vemos una imagen del juego:

Y esto es todo. En la próxima entrega haremos que nuestro héroe mueva las piernas y los brazos.

Un saludo

lunes, 10 de marzo de 2008

Movernos por nuestro juego

Ya tenemos cargado el fondo en nuestro juego, ahora sólo nos queda movernos por él.

Antes de nada vamos a ordenar un poco el código, porque si no la cosa se irá poniendo fea. En primer lugar deberíamos cambiar el nombre al proyecto, o mejor crear un proyecto nuevo, ya que HelloWorld no es lo más apropiado. Quizás un buen nombre sea PokemonDS.

Pues nada manos a la obra:

1. ¿Hemos modificado el PalibTemplate? Con esto me refiero a únicamente crear la carpeta gfx dentro del source y copiar en ella los ficheros que viene en C:\devkitPro\PAlib\Tools\PAGfx.

2. Copiamos PalibTemplate a c:\DevDS y cambiamos el nombre por PokemonDS. Tanto el nombre de la carpeta como dentro del fichero make. Si vamos a usar el Programmer's Notepad deberemos modificar el fichero Template.pnproj por PokemonDS.pnproj y en el interior del fichero cambiar la palabra template por PokemonDS.

3. Vamos a crear una librería, un .h para dejar nuestras constantes y no tener que andar con números raros en las funciones. Esta librería se podría poner en el template para tenerla siempre accesible. La librería la denominaremos 'devnintendods.h'.

En un primer momento la librería tendrá este código:

#define SCREEN_TOP 1
#define SCREEN_BOTTOM 0

#define BACKGROUND_ZERO 0
#define BACKGROUND_ONE 1
#define BACKGROUND_TWO 2
#define BACKGROUND_THREE 3

Con esto tendremos definidas una serie de constante para distinguir las dos ventanas de la DS (SCREEN_TOP -> será la ventana superior) y una serie de constantes para definir los cuatro fondos o backgrounds que puede tener cada pantalla.

¿Para que sirve tener cuatro fondos? Pues jugando con las transparencias se pueden crear efectos muy chulos.

Llegados a este punto sólo nos queda poner el código para mover la pantalla:

1. Para activar el movimiento de un fondo (o de los cuatro a la vez) utilizaremos la rutina PA_InitParallaxX y PA_InitParallaxY, que permite inicializar el movimiento tanto horizontal como vertical y las velocidades de desplazamiento (256 es la normal)

PA_InitParallaxX(SCREEN_TOP, 0, 0, 256, 0); indica que para la pantalla superior de la DS el tercer fondo se moverá a velocidad normal, los otros no se desplazarán.

2. Vamos a movernos utilizando los cursores que hay en la DS. Existe una estructura denominada Pad, que es la que nos permitirá acceder a las pulsaciones de los botones.

El código quedará así:

// Includes
#include <pa9.h> // Include for PA_Lib
#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"

#include "devnintendods.h"

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

PA_EasyBgLoad(SCREEN_TOP, BACKGROUND_THREE, fondo);

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

s32 fondoX = 0;
s32 fondoY = 0;
while (1) {
fondoX += Pad.Held.Right - Pad.Held.Left;
fondoY += Pad.Held.Down - Pad.Held.Up;

PA_EasyBgScrollXY(SCREEN_TOP, BACKGROUND_THREE, fondoX, fondoY);
PA_WaitForVBL();
}
return 0;
}

Si ya lo tenéis funcionando, veréis un efecto extraño en el fondo, se solapa en vertical y aparece un espacio negro en horizontal. Esto es por que la imagen no coincide con el tamaño de la pantalla, que es de 256 (0 a 255) por 192 (0 a 191).
Aún así La DS espera fondos de 256x256 (y múltiplos de este tamaño), así que meteremos nuestro fondo en una imagen de 512x512.

Como hemos indicado, el objeto Pad controla todas las teclas de la consola. Estas son todas las teclas:

Pad.Held.Start
Pad.Held.Select
Pad.Held.Right
Pad.Held.Left
Pad.Held.Up
Pad.Held.Down
Pad.Held.A
Pad.Held.B
Pad.Held.X
Pad.Held.Y
Pad.Held.L
Pad.Held.R

Lo de Held es el momento de pulsar el botón (nos olvidamos de ello por ahora).

El aspecto actual de nuestro juego es el siguiente:

Y con esto ya tenemos un poco más de nuestro juego. ahora nos falta poner el muñequito, pero eso será para la próxima entrega.

Saludos