viernes, 6 de marzo de 2009

Clases básicas - Sprite

En nuestro NSMB vamos a empezar a definir una serie de clases que se encargarán, cada una de ellas, de un apartado propio del juego.

NOTA:
Aquí vamos a explicar como se han construido estas clases y como funcionan. Si no te interesa o tu nivel de programación aún no ha llegado a esto, podrías saltarte la explicación y únicamente aprender a usarlas.



Incluso podemos diferenciar la lógica del juego del apartado gráfico (las propias librerías PALib) por lo que sería más fácil pasar el juego a otro sistema. Vamos a presentar en este post la clase Sprite (vimos una primera versión en el post anterior).

Esta clase será la encargada de cargar y mostrar los personajes o los objetos con los que interactuaremos en nuestros juegos. Podremos animar los Sprites, rotarlos, moverlos, etc.

En C++ las clases se componen de dos ficheros, el .h y el .cpp.
En el .h se almacena la definición de la clase, su nombre, las funciones públicas, las privadas y las variables miembro.
En el .cpp está el código, la implementación de la propia clase.

Sprite.h
#ifndef SPRITE_H
#define SPRITE_H

class Sprite {

private:
    static unsigned int index_sprites;
    static unsigned int index_sprites_pals;

protected:
    unsigned int m_index_sprite;
    unsigned int m_index_pal;
    unsigned int m_screen;
    unsigned int m_background;
    unsigned int m_size;

    unsigned int m_firstframe;
    unsigned int m_lastframe;
    unsigned int m_speed;

    int m_x;
    int m_y;

    Sprite(unsigned int screen, unsigned int background, unsigned int size, int x, int y);

public:
    Sprite(unsigned int screen, unsigned int background, unsigned int shape, unsigned int size, void *palette, void *sprite, int x, int y, int hflip = 0);
    ~Sprite();

    unsigned int GetIndexSprite();
    unsigned int GetScreen();

    Sprite *Clone();
    Sprite *Clone(int x, int y);

    void SetAnim(unsigned int firstframe, unsigned int lastframe, unsigned int speed);
    void StartAnim(unsigned int firstframe, unsigned int lastframe, unsigned int speed);
    void StartAnim();
    void StopAnim();
    void RestartAnim();
    void PauseAnim();

    void Hflip(bool hflip);

    void SetX(int x);
    void SetY(int y);
    void SetXY(int x, int y);

    int GetX();
    int GetY();

    int MoveRight(int offset);
    int MoveLeft(int offset);
    int MoveUp(int offset);
    int MoveDown(int offset);
};

#endif


Las variables miembro son las que definen el estado de un objeto. En nuestro caso son las variables que tienen como prefijo una m_ :
m_index_sprite, m_index_pal, m_screen, m_background, m_firstframe, m_lastframe, m_speed, m_x, m_y.

Las variables miembro son las que almacenan el estado, es decir son las que diferencian un objeto de otro. Veamos cada una que hace:

m_index_sprite, m_index_pal quedan ocultas a la programación. Son valores necesarios para PALib. Si os fijaís existen unas variables (index_sprites, index_sprites_pals) que son estáticas, es decir son únicas para todos los objetos, y son las encargadas de crear los índices para cada Sprite y cada Paleta). (En el primer tutorial son los valores HEROE_PALLETE y HEROE_SPRITE, gracias a la POO nos olvidamos de ellas)

Como siempre es más difícil explicarlo que verlo o hacerlo.

El resto de variables miembro nos indican a que pantalla pertenece el Sprite y dentro de cada pantalla a que fondo, m_screen y m_background.

m_firstFrame y m_lastFrame, sirven para realizar la animación. m_speed nos indica la velocidad de la misma.

m_x y m_y almacenan las coordenadas del sprite.

El resto del fichero .h son las declaraciones de las funciones. Tenemos un constructor, con el último campo puesto = 0. Esto significa que se puede no usar y por defecto es cero. Es decir al crear el sprite lo podemos rotar horizontalmente si ponemos un 1 en ese campo, si no queremos rotarlo,no ponemos nada.

El resto son una serie de funciones que su nombre ya las explica.

Ahora veamos como usarlo:


#define COIN OBJ_SIZE_16X16, (void*)coin_Pal, (void*)coin_Sprite




Sprite coin_i(BACKGROUND_THREE, COIN, 96, 32);
coin_i.StartAnim(0, 3, 5);
Sprite coin_ii = coin_i.Clone(112, 16);
Sprite coin_iii = coin_ii.Clone();
coin_iii.MoveRight(16);
Sprite coin_iv = coin_i.Clone(144, 32);


No creo que necesite más explicación, si no, nos vemos en los comentarios.


En cuanto a la librería PALib, en esta clase utilizo por primera vez la función PA_SpriteAnimPause. Esta función permite parar una animación y continuarla posteriormente. Utiliza como parámetros el fondo donde está el sprite, así como su índice (para saber cual parar) y el último parámetro es un 1 para parar la animación y 0 para reanudarla. Ejemplo de uso:

void Sprite::RestartAnim() {
    PA_SpriteAnimPause(m_screen, m_index_sprite, 0);
}

void Sprite::PauseAnim() {
    PA_SpriteAnimPause(m_screen, m_index_sprite, 1);
}


De esta forma al pulsar poco a poco el teclado, la animación se hace continua y no parece que esté andando a la pata coja.


Otra función que se utiliza por primera vez en este tutorial es la de Clonar un Sprite, PA_CloneSprite. Como parámetros se le pasa la pantalla de la DS donde está cargado el Sprite, el índice de este para saber cual clonar y, por último, el índice del nuevo Sprite. Clonar Sprites nos ahorra espacio en memoria, ya que comparten toda la información gráfica. En nuestro caso va a ser muy util para las monedas que aparecen a lo largo de todo el juego.

Saludos

PD: Voy a dejar todo el proyecto, tal y como está ahora, en la siguiente dirección: http://sites.google.com/site/devnintendods/Home/nsmb. Lo que dejo ahí es todo lo que tengo hecho hasta ahora (SuperMarioDS_0.2). Es todo el montaje que estoy haciendo. Lo iré explicando en los siguientes Post. Sólo decir que main.cpp únicamente llama a NSMB.cpp que es el que lo hace todo.

Aún así, se pueden usar las clases de forma independiente en cualquier otro proyecto.

Enlaces patrocinados

6 comentarios:

Anónimo dijo...

El descargable al darle a Build, aparecen los errores h.21,h.22,h.25,h.26,h.27....


Por que ocurre esto?

David Martínez Martínez dijo...

A mi no me da errores, no se... Y por cierto, hace cosas raras al ejecutarlo.

Si te desplazas a la derecha, el escenario se va subiendo cada vez más y más.

¡Estás haciendo un gran trabajo! ¡Sigue así!

Inigo dijo...

Buenas

Lo del error parece que algo no esté bien configurado, o el devkitpro o el programa que se use para compilar. No te puedo ayudar.

En cuanto a lo de ir a la derecha, el programa no tiene puesto límites así que se puede ir a la derecha todo lo que se quiera, y a la izquierda, y claro los datos se empiezan a desconfigurar.

Es una versión de trabajo (no es ni una beta) y está en pañales. Seguramente mucha gente no querrá enseñar el trabajo que hace en un estado tan inicial, pero creo que es una buena manera de ver como crece un programa.

Saludos

David Martínez Martínez dijo...

Vale vale. Era por si esa no era la idea, si debía repetirse el escenario o algo así.

¡Y sigues sin usar la carpeta include! xD

Saludos.

Inigo dijo...

Ups!!! ha sido de puro churro, porque ya lo metí. Se ve que al montar la versión que subí no estaba. Pero ya lo puse, sin ningún problema!

Saludos y gracias, todos aprendemos algo!!!

David Martínez Martínez dijo...

Ahora mirando tu tutorial con más detenimiento, luego dices que exagero...

Esta línea me ha encantado:

#define COIN OBJ_SIZE_16X16, (void*)coin_Pal, (void*)coin_Sprite

No sabía que en un define se podían poner varias cosas separadas por comas. Realmente, tiene sentido, son constantes, simplemente no lo había pensado. Son detalles como este que hacen tu tutorial diferente.

Luego, para crear tu moneda, solo pones:

Sprite coin_i(BACKGROUND_THREE, COIN, 96, 32);

Pues bien. En otro tutorial, lo que pondrían sería:

PA_LoadSpritePal(1, 0, (void*)COIN_Pal);

PA_CreateSprite(1, 0, (void*)COIN_Sprite, OBJ_SIZE_16X16, 1, 0, 0, 0);

PA_SetSpritePrio(1, 0, 3);

Y se quedan tan agusto. Yo, en el segundo caso, no me entero DE NADA. Y eso que lo he escrito yo, ahora mismo. Imagínate si me pongo a mirarlo dentro de un mes.

La librería devnintendods.h, y las clases que estás haciendo ahora, hacen mucho más legible tu código. Y la verdad, yo soy así también, puede que incluso más exagerado.

Este es el main de mi proyecto:

int main(int argc, char ** argv)
{
iniciarEntornoPAlib();

iniciarFondos16bitsParaTablero();

cargarOpciones();

cargarTablerosParteIzquierda();

cargarPaletasYSpritesElementos();

cargarNieblaDeGuerra();

cargarNieblaDeGuerraParteIzquierda();

cargarMenuTactil();

definirTableroGeneral();

descubrirZona(barbaroX, barbaroY);

// Infinite loop to keep the program running
while (1)
{
moverHeroes();

dibujarHeroes();

PA_WaitForVBL();
}

return 0;
} // End of main()

Las únicas dos líneas que tengo con código de verdad, son el VBL, el while y el return 0, todo lo demás en funciones y ordenadito :P

Seguiré muy de cerca tu tutorial, ¡¡qué ganas de que sea mañana!! Saludos, cuídate.