viernes, 27 de marzo de 2009

NSMB.cpp

Hoy vamos a ver la clase NSMB.cpp que, al fin y al cabo, es lo único programado del juego hasta la fecha. El resto es una especie de librería que con el paso del tiempo podría servir para programar todo tipo de juegos para la Nintendo DS.

Lo primero, si vemos el código, es que apenas hay nada de PALib en el código. Sólo está el include y un par de constantes. Las clases han conseguido encapsular todo el código PALib.

Veamos el código del programa:


NSMB::NSMB() {
gameManager = new GameManager();
gameManager->SetKeypadManager(this);
topScreen = gameManager->GetTopScreenManager();
koopa = topScreen->CreateSprite(BACKGROUND_THREE, KOOPA, 16, 0, 1);
koopa->SetAnim(0, 15, 15);

tileSet = topScreen->CreateTileSet(BACKGROUND_TWO, PLATAFORMAS);
topScreen->PrintMap(MAPA_PLATAFORMAS, tileSet, x, y);
}


Parece poco código, veamos lo que hace:

gameManager = new GameManager();: Es la clase que crea el motor del juego. Podemos decir que engloba el HelloWorld de PaLib, es decir los init y el bucle principal.

gameManager->SetKeypadManager(this);: En esta línea le estamos diciendo que clase va a recibir la información de entrada del juego (el teclado y el sylus). 'this' significa que será la propia clase NSMB la que los recibirá.
Esto es una aproximación a lo que se suele llamar programación orientada a eventos. Es decir, cuando el usuario pulse un botón de la consola, NSMB recibirá una llamada a una de las funciones que tiene que implementar. Si, por ejemplo, se pulsa el botón de A se lanzará la función NewpressA.
Sería algo parecido a los callback, pero más orientado a objetos.

topScreen = gameManager->GetTopScreenManager();: Esto es una forma de tener una variable que apunte a la pantalla superior y no tener que ir todo el rato arrastrándo gameManager->GetTopScreenManager().

topScreen->CreateSprite(BACKGROUND_THREE, KOOPA, 16, 0, 1);: Ya tenemos un sprite hecho. Más fácil imposible. Los parámetros último son x, y y si lo queremos girar horizontalmente (1 = si).

koopa->SetAnim(0, 15, 15);: Y ahora lo animamos, entre los frames 0 a 15 y la velocidad es de 15.

tileSet = topScreen->CreateTileSet(BACKGROUND_TWO, PLATAFORMAS);: Creamos los tileset (serían los ladrillos de nuestro juego) para los fondos.

topScreen->PrintMap(MAPA_PLATAFORMAS, tileSet, x, y);: Y dibujamos el fondo de la pantalla superior.

Ya está...........

No parece muy complicado. Si no se tiene mucha experiencia en la programación con PALib puede resultar rara la definición de KOOPA. Pues nada, con echar un ojo a la entrada que se hizo en su día explicando los Sprites creo que será suficiente.

Por ahora estamos trabajando con Koopa como personaje. En cuanto saque tiempo prepararé los sprites de Mario.

El resto del código es:

void NSMB::Run() {
    gameManager->Run();
}
Se podía haber puesto en el constructor (NSMB::NSMB()) pero aquí parece que queda mejor.


bool NSMB::HeldLeft() {
if (movimiento != MOVIMIENTO_IZQUIERDA) {
koopa->Hflip(0);
movimiento = MOVIMIENTO_IZQUIERDA;
koopa->StartAnim();
} else {
koopa->RestartAnim();
}
velocidad--;
if (velocidad == 0){
velocidad = MOVIMIENTO_VELOCIDAD;
x--;
}
return 1;
}


Cuando el jugador pulse en el botón de flecha izquierda se ejecutará este código, que ahora mismo únicamente, anima a nuestra tortuga. Usa la variable 'movimiento' para hacerlo una animación más fluida, y girar a nuestro héroe cuando sea necesario. Falta el tema de las colisiones.
Con más detalle: si la tortuga estaba con otra orientación (if (movimiento != MOVIMIENTO_IZQUIERDA)) no hace falta girarlo (Hflip(0) = lo desgiramos - el sprite original mira a la izquierda) y empezamos la animación. Si ya estábamos mirando a la izquierda, únicamente, continuamos con la animación (RestartAnim). El resto del código es para ir poco a poco (lo intentaremos mejorar llamando a una función, que también la llamará HeldRight, que será la encargada de mover a nuestro héroe y mirar las colisiones).


bool NSMB::HeldRight() {
if (movimiento != MOVIMIENTO_DERECHA) {
koopa->Hflip(1);
movimiento = MOVIMIENTO_DERECHA;
koopa->StartAnim();
} else {
koopa->RestartAnim();
}
velocidad--;
if (velocidad == 0){
velocidad = MOVIMIENTO_VELOCIDAD;
x++;
}
return 1;
}

Igual que el anterior pero para ir a la derecha.


bool NSMB::NoKey() {
koopa->PauseAnim();
return 1;
}

Si no se ha pulsado ninguna tecla se ejecutará esta función. Nosotros la usaremos para parar la animación de nuestro héroe.


bool NSMB::BeforeVBL() {
topScreen->PrintMap(MAPA_PLATAFORMAS, tileSet, x, y);
return 1;
}

Esto se ejecutará siempre, una vez por cada vuelta del bucle, después de tratar el teclado (incluida la función de que no se ha pulsado ninguna tecla).

Y esto es todo, por ahora. La primera versión de GameManager ha sido publicada, esperemos que no sea la última.

Y ¿cómo usarlo? Es decir, ¿cómo llamar a NSMB?

Vemos el código de main.cpp:


#include "NSMB.h"

int main(int argc, char ** argv) {
NSMB *nsmb = new NSMB();
nsmb->Run();
return 0;
}

Uhmmm, no sé que se puede explicar. Se crea un objeto de tipo NSMB y ejecutamos un 'run'.

Nos vemos en los comentarios.

Saludos

viernes, 20 de marzo de 2009

Nueva versión del Tetris

Hacemos un paréntesis en la programación del NSMB y vamos a presentar la última versión del Tetris.

Se ha creado un menú en la pantalla táctil, por lo que usaremos el stylus por primera vez en este blog. La imagen es la siguiente:



Posibilita la selección de tres tipos de juegos.

PALib controla cuando se pulsa en la pantalla táctil y devuelve las coordenadas (x, y) donde se ha pulsado.

if (Stylus.Held) {
    tipo_juego = obtener_tipo_juego(Stylus.X, Stylus.Y);

El Stylus.Held indica que se está pulsando en la pantalla y las coordenadas quedan en Stylus.X y en Stylus.Y. Ahora para saber si se ha pulsado un botón u otro se ha creado esta función:


u8 obtener_tipo_juego(s32 xStylus, s32 yStylus) {
if (pulsado_boton_con_stylus(xStylus, yStylus, 60, 37, 205, 79)) {
return JUEGO_MARATON;
} else if (pulsado_boton_con_stylus(xStylus, yStylus, 74, 83, 224, 125)) {
return JUEGO_EXPERTO;
} else if (pulsado_boton_con_stylus(xStylus, yStylus, 61, 133, 226, 170)) {
return JUEGO_SUPER_BLOQUES;
} else {
return 0;
}
}

bool pulsado_boton_con_stylus(s32 xStylus, s32 yStylus, s32 x1, s32 y1, s32 x2, s32 y2) {
return (xStylus > x1 && xStylus <> y1 && yStylus < y2);
}


Esta última función es la que uso en todos los proyectos. Sirve para saber si un punto (x, y) esta dentro de un rectángulo (x1, y1) - (x2, y2).
Los rectángulos los sacamos de la imagen, y aunque los botones estén un poco torcidos más o menos he trazado unos rectángulos y son esas coordenadas las que he usado:


El modo superbloques muestra fichas de distintas formas, posibilidad que nos da el haber creado las fichas como matrices de 4x4. Y tanto este modo como el experto van incrementando las velocidad a medida que se consigue aumentar la puntuación.

Veamos la función actualizar_puntuacion:


if (tipo_juego == JUEGO_EXPERTO || tipo_juego == JUEGO_SUPER_BLOQUES) {
if (nivel != puntos / 100) {
nivel = puntos / 100; velocidad -= nivel;
}
}

Cada cien puntos aumentamos la velocidad (o disminuimos el tiempo de espera).

Os dejo todo el código en el lugar habitual para que echéis un vistazo. Como siempre nos vemos en los comentarios.

Saludos

sábado, 14 de marzo de 2009

Clases básicas: TileSet

TileSet es una clase que nos permite trabajar con los tiles. Los tiles son esas pequeñas imágenes, de 8x8 pixeles, que sirven para dibujar en las pantallas de la nintendo DS.

Para explicarlo mejor, las pantalla de la DS tienen 256x192 pixeles. Dividido entre 8 nos da 32x24 casillas. Es decir, cada pantalla tienen 32x24 casillas, y en cada casilla se puede dibujar un tile.

PaLib nos permite trabajar con estos tiles de una forma muy sencilla, pero la clase TileSet nos permite aún una mayor abstracción. Sólo contiene, por ahora, tres funciones.

El constructor de la clase nos permite cargar en memoria la imagen con todos los tiles de nuestro juego.

La función SetTile16x16 nos permitirá dibujar en una ubicación dada un tile.

La función GetTile nos permite ver que tile hay en una ubicación dada. Esto nos servirá para el tema de colisiones.

Esta es la imagen que usamos, en este momento, en el proyecto:



Si os fijáis bien el primer rectángulo es rosa, eso significa que el 0 será un recuadro transparente.

Un ejemplo de uso:

#define PLATAFORMAS (void *)plataformas_Pal, (void *)plataformas_Tiles, SIZEOF_16BIT(plataformas_Tiles), (void *)plataformas_Map

#define COLUMNAS_16x16_PANTALLA 16
#define COLUMNAS_16x16_MAPA 48
#define FILAS_16x16 12
#define MAPA_PLATAFORMAS (void *)mapa, COLUMNAS_16x16_PANTALLA, FILAS_16x16, COLUMNAS_16x16_MAPA

int mapa[COLUMNAS_16x16_MAPA * FILAS_16x16] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 5, 2, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 12, 8, 11, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 8, 13, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 12, 8, 11, 14, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 7, 9, 8, 10, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 12, 13, 8, 14, 0, 0, 0, 0, 0, 0, 7, 12, 10, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 13, 9, 10, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 12, 8, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 8, 12, 13, 15, 4, 3, 3, 4, 2, 3, 4, 2, 5, 4, 4, 3, 5, 2, 3, 5, 4, 3, 3, 4, 2, 3, 4, 2, 5, 4, 2, 3, 5, 2, 3, 5, 4, 3, 3, 4, 2, 3, 4, 2, 5, 4, 6,
7, 11, 13, 12, 8, 10, 9, 11, 12, 11, 8, 12, 12, 13, 12, 8, 10, 9, 11, 12, 11, 8, 12, 9, 11, 12, 11, 8, 12, 9, 13, 12, 8, 10, 9, 11, 8, 12, 12, 13, 12, 10, 12, 11, 8, 12, 9, 14
};

TileSet *newTileSet = new TileSet(SCREEN_TOP, BACKGROUND_TWO, PLATAFORMAS);
newTileSet->SetTile16x16(MAPA_PLATAFORMAS, 10, 10);

Es muy sencillo. Me gusta usar los defines, porque el código queda mucho más claro, desaparecen * y (void *) y queda todo más cómodo.

Saludos

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