viernes, 19 de junio de 2009

Mejoramos el movimiento

Todas estas semanas atrás hemos estado programando el juego utilizando las funcionalidades de los Tiles que nos brinda PALib. Esto está muy bien para juegos tipo Tetris, de tablero, etc. Pero para el New Super Mario Bros, o para los Pokemon no es totalmente válido.

El programar con tiles permite dibujar la pantalla con 'azulejos' de 8x8 pixeles, por lo que el movimiento del fondo (necesario en este tipo de juegos) ha de ser de 8 en 8 pixeles y eso no puede ser. Necesitamos unas transiciones más suaves.

PALib permite crear tiles y mover el fondo como de si una imagen se tratara. Así que hemos realizado los siguientes cambios en nuestra librería y en el editor de mapas.

Con respecto a la nueva librería hemos creado nuevas funciones para tratar los fondos. Estas funciones estarán en ScreenManager:


/**
* Asigna una imagen de fondo
*
* @param background Cada pantalla de la consola permite cuatro niveles de
* fondos. Los valores posibles son: BACKGROUND_ZERO, BACKGROUND_ONE,
* BACKGROUND_TWO, BACKGROUND_THREE (de mayor a menor prioridad)
*/
void SetBackground(unsigned int background, int *info, void * pal, const unsigned char *tiles, u32 tiles_size, const unsigned short *mapa);

/**
* Devuelve la coordenada X única a todos los fondos.
*/
int GetBackgroundsX();

/**
* Devuelve la coordenada Y única a todos los fondos.
*/
int GetBackgroundsY();

/**
* Asigna la coordenada X única a todos los fondos.
* Se utiliza para mover todos los fondos a la vez
*
* @param y coordenada x
*/
void SetBackgroundsX(int x);

/**
* Asigna la coordenada Y única a todos los fondos.
* Se utiliza para mover todos los fondos a la vez
*
* @param y coordenada Y
*/
void SetBackgroundsY(int y);

/**
* Asigna las coordenadas X, Y única a todos los fondos.
* Se utiliza para mover todos los fondos a la vez
*
* @param y coordenada x
* @param y coordenada Y
*/
void SetBackgroundsXY(int x, int y);

/**
* Desplaza a la izquierda todos los fondos una cantidad dada
* Se utiliza para mover todos los fondos a la vez
*
* @param offset desplazamiento
* @return la nueva coordenada x
*/
int MoveBackgroundsLeft(int offset);

/**
* Desplaza a la derecha todos los fondos una cantidad dada
* Se utiliza para mover todos los fondos a la vez
*
* @param offset desplazamiento
* @return la nueva coordenada x
*/
int MoveBackgroundsRight(int offset);

/**
* Desplaza arriba todos los fondos una cantidad dada
* Se utiliza para mover todos los fondos a la vez
*
* @param offset desplazamiento
* @return la nueva coordenada y
*/
int MoveBackgroundsUp(int offset);

/**
* Desplaza abajo todos los fondos una cantidad dada
* Se utiliza para mover todos los fondos a la vez
*
* @param offset desplazamiento
* @return la nueva coordenada y
*/
int MoveBackgroundsBottom(int offset);


De esta forma podemos asignar una serie de imágenes a los distintos fondos y moverlos sin problema los pixeles que se desean. En nuestro caso los fondos se moverán de izquierda a derecha.

Las funciones utilizadas hasta hoy, CreateTileSet, y la clase TileSet se dejarán para los proyectos que lo necesiten.

En cuanto al programa de edición de mapas se han cambiado los ficheros de salida. Ahora genera un png con el dibujo que se ha editado. Este dibujo debe pasar por PAGfx para generar los ficheros de salida. A continuación publicamos las dos imágenes de salida del programa de edición:


Fondo cero


Fondo uno

El código en NSMB.cpp quedará así:


topScreen->SetBackground(BACKGROUND_ZERO, FONDO_CERO);
topScreen->SetBackground(BACKGROUND_ONE, FONDO_UNO);
topScreen->SetBackground(BACKGROUND_THREE, FONDO_TRES);
topScreen->SetCollisionMap8x8((void *)newImage_0_Map, newImage_0_Info[1] / 8, newImage_0_Info[2] / 8);


Esta semana no voy a publicar código. Son demasiados cambios de un solo golpe y no puedo mantener bien las versiones. Además de que el código estará lleno de errores.

Ahora mi objetivo es dejar el salto fino y coger las monedas.

Espero tener para la semana que viene cosas nuevas que ofrecer, aunque el verano me va a quitar tiempo.

Saludos

viernes, 12 de junio de 2009

Los sonidos

A partir del post de como programar los sonidos en la Nintendo DS, con el código de David que publicamos hace ya un tiempo, vamos a implementar los sonidos de nuestro juego.

Como ya explicamos en aquel post, las librerías PALib leen los ficheros de audio en formato raw.

Para pasar de mp3 a raw lo he hecho con 'Switch Sound File Converter'. Para editar los ficheros hemos usado Audacity.

Los ficheros de sonido se dejan en la carpeta data de SuperMarioDS.

La Instrucción PA_InitSound la hemos puesto en el constructor de GameManager.cpp. Ahora en la instalación que tengo de PALib ha empezado a dar problemas y he tenido que cambiar el código, que ha quedad de esta forma:


GameManager::GameManager() {
PA_Init();
PA_InitVBL();

//PA_InitSound();
AS_Init(AS_MODE_SURROUND | AS_MODE_16CH );
AS_SetDefaultSettings(AS_PCM_8BIT, 11025, AS_SURROUND);


El código a cambiar en NSMB.cpp es el siguiente:


#include
#include "salto.h"

....

bool NSMB::HeldUp() {
if (!volando) { //Si no estamos volando podremos saltar
//PA_PlaySimpleSound(0, salto);
PA_PlaySimpleSound(salto);
tiempoSalto = TIEMPO_SALTO;
volando = true;
fuerzaSalto = FUERZA_SALTO_INICIAL;
}
return 1;
}

El error que aparecía al compilar era
'PA_InitSound' was not declared in this scope
, y ahí me he quedado, he cambiado PA_InitSound y PA_PlaySimpleSound(0, salto) por las otras instrucciones y ya funciona (¿?¿?¿?¿?).

Ahora habrá que ir poniendo sonidos a las distintas acciones...

Saludos

viernes, 5 de junio de 2009

La gravedad y el salto

Bueno, aquí no vamos a explicar lo que es la gravedad, pero si que es necesario ponerla en nuestro juego.

Vamos a programar la gravedad en nuestro juego.

El tema está en que al saltar, Mario no sube y después baja, no....si no que al saltar Mario sube, el bajar es cosa de la gravedad, no del salto. Por lo que habrá que programar primero el tema de la gravedad.

Lo primero será hacer un mapa con una zona elevada y poner ahí a nuestro héroe. Y que al salir de dicha zona la gravedad le haga caer y que lo haga hasta que ocurra una colisión.

he creado varias variables, una para saber que estamos cayendo, ya que en esos momentos no podemos saltar. Sólo se salta desde el suelo.
Otra es para el tema de la fuerza al caer, es decir, cada vez se cae más deprisa y el retardo del salto para que no caiga demasiado deprisa.


bool NSMB::BeforeVBL() {
if (tiempoSalto-- < 0) {
tiempoSalto = MOVIMIENTO_VELOCIDAD;
int y = koopa->GetY();
koopa->SetY(y + fuerzaSalto);
if (topScreen->IsCollision(koopa, mundo_plataformas_cero->GetX(), mundo_plataformas_cero->GetY())) {
koopa->SetY(y);
fuerzaSalto = 1;//inicializar
volando = false;
} else {
if (fuerzaSalto < MAXIMA_FUERZA_CAIDA) fuerzaSalto++;
volando = true;
}
}
return 1;
}


El código lo hemos colocado en la función BeforeVBL que se ejecuta en cada vuelta del bucle y antes del refresco de la pantalla. Esto se ha hecho así porque la gravedad actua continuamente.

Lo que realmente hace nuestro código es coger la coordenada 'Y' de nuestro héroe y sumarle la fuerza. La hemos llamado fuerzaSalto porque sirve igualmente para saltar que para caer, ya hemos dicho que saltar y caer es lo mismo.

Una vez sumados los valores vemos si hay colisión, si no es el caso aumentamos la fuerza, cada vez caemos más fuerte (hasta un límite 'MAXIMA_FUERZA_CAIDA'). Este algoritmo se ejecutará hasta llegar a un suelo.

Y ahora que pasa si fuerzaSalto es negativo. Pues que tenemos a nuestro héroe saltando. Por lo que necesitamos este código para comenzar a saltar:


bool NSMB::HeldUp() {
if (!volando) { //Si no estamos volando podremos saltar
tiempoSalto = TIEMPO_SALTO;
volando = true;
fuerzaSalto = FUERZA_SALTO_INICIAL;
}
return 1;
}


Y ya está, programando la gravedad nos ha salido gratis el salto. Así da gusto!

El juego tiene este aspecto ahora mismo:


Aún así queda un efecto feo al aterrizar. Ya lo trabajaremos más adelate.

Ahora sólo queda coger las monedas, meter los sonidos, los efectos de fondo, más plataformas,... esto no acaba nunca.

Saludos, nos vemos en los comentarios.

viernes, 29 de mayo de 2009

Las monedas

El tema de las monedas nos va a permitir crear un nuevo tipo de Sprite, los Sprites de fondo. Esto no es algo de PALib, si no que es algo implementado por nuestra pequeña librería.

Los sprites de fondo o 'background sprite' se moverán con el fondo, por lo que habrá que hacer también funciones que muevan el fondo.

Se ha implementado casi todo dentro de ScreenManager. La función para crear un Sprite de fondo es 'CreateBackgroundSprite'. Tiene los mismos parámetros que 'CreateSprite', lo único que el Sprite queda en una lista de sprites de fondo. También se ha preparado 'CloneBackgroundSprite' que crea un sprite idéntico ahorrando espacio en memoria.

Antes de ver las funciones, conviene resaltar que se ha usado por primera vez una clase de C++ denominada Vector. Esta clase permite tener una especie de 'array' que se puede ir cargando de forma dinámica. En este caso se ha usado de esta forma:


//ScreenManager.h
std::vector *m_backgroundSprites;


Esta es la forma de crear un vector de clases de Sprite. Más delante se ve como recorrerlo, en 'MoveRightBackgroundSprites' y en 'MoveLeftBackgroundSprites'.

Las funciones nuevas son las siguientes:


Sprite *ScreenManager::CreateBackgroundSprite(unsigned int background, unsigned int shape, unsigned int sizes, void *palette, void *sprite, int x, int y) {
return CreateBackgroundSprite(background, shape, sizes, palette, sprite, x, y, 0);
}

Sprite *ScreenManager::CreateBackgroundSprite(unsigned int background, unsigned int shape, unsigned int sizes, void *palette, void *sprite, int x, int y, int hflip) {
Sprite *newSprite = new Sprite(m_screen, background, shape, sizes, palette, sprite, x, y, hflip);
if (!m_backgroundSprites) m_backgroundSprites = new std::vector();
m_backgroundSprites->push_back(newSprite);
return newSprite;
}

Sprite *ScreenManager::CloneBackgroundSprite(Sprite *sprite) {
return CloneBackgroundSprite(sprite, sprite->GetX(), sprite->GetY());
}

Sprite *ScreenManager::CloneBackgroundSprite(Sprite *sprite, int x, int y) {
Sprite *clone = sprite->Clone(x, y);
m_backgroundSprites->push_back(clone);
return clone;
}


Y, finalmente, las funciones encargadas de mover todos los sprites de fondo de un golpe son:


void ScreenManager::MoveRightBackgroundSprites(int offset) {
unsigned int i;
for(i = 0; i < m_backgroundSprites->size(); i++) {
Sprite *sprite = (Sprite *)m_backgroundSprites->at(i);
sprite->MoveRight(offset);
}
}

void ScreenManager::MoveLeftBackgroundSprites(int offset) {
unsigned int i;
for(i = 0; i < m_backgroundSprites->size(); i++) {
Sprite *sprite = (Sprite *)m_backgroundSprites->at(i);
sprite->MoveLeft(offset);
}
}


En este punto surgió un problema y es que cuando la x se hacía negativa o mayor de 256 aparecían los sprites por el otro lado. Este tema se ha corregido creando una función nueva que permite ocultar los sprites:


void Sprite::SetVisible(int visible) {
if (!visible) {
if (PA_GetSpriteMode(m_screen, m_index_sprite) != 1) {
PA_SetSpriteMode(m_screen, m_index_sprite, 1);
PA_EnableSpecialFx(m_screen, SFX_ALPHA, 0, SFX_BG0 | SFX_BG1 | SFX_BG2 | SFX_BG3 | SFX_BD);
PA_SetSFXAlpha(m_screen, 0, 0);
}
} else if (PA_GetSpriteMode(m_screen, m_index_sprite) != 0) {
PA_SetSpriteMode(m_screen, m_index_sprite, 0);
}
}


Esta función pone el sprite totalmente transparente (Esto no es cierto del todo, Existen dos modos y en uno de ellos serán todos los sprites transparentes). Se puede utilizar desde nuestros programas y además lo utilizan las funciones SetX, SetY y SetXY si las coordenadas están fuera de la pantalla:

void Sprite::SetX(int x) {
m_x = x;
PA_SetSpriteX(m_screen, m_index_sprite, m_x);
if (m_x < 0 || m_x > 256) SetVisible(0);
else SetVisible(1);
}

void Sprite::SetY(int y) {
m_y = y;
PA_SetSpriteY(m_screen, m_index_sprite, m_y);
if (m_y < 0 || m_y > 192) SetVisible(0);
else SetVisible(1);
}

void Sprite::SetXY(int x, int y) {
m_x = x;
m_y = y;
PA_SetSpriteXY(m_screen, m_index_sprite, m_x, m_y);
if (m_x < 0 || m_x > 256) SetVisible(0);
else SetVisible(1);
}


Las monedas las hemos creado en NSMB.cpp de la siguiente manera:

Sprite *coin = topScreen->CreateBackgroundSprite(BACKGROUND_TWO, COIN, 192, 80);
coin->SetAnim(0, 3, 4);
coin->StartAnim();
topScreen->CloneBackgroundSprite(coin, 224, 88);
topScreen->CloneBackgroundSprite(coin, 248, 96);
topScreen->CloneBackgroundSprite(coin, 548, 104);


La primera línea crea una moneda (un sprite de fondo).
La segunda define los frames para la animación.
La tercera pone a girar la moneda.
Y las otras tres crean tres monedas más.

Este es el aspecto del juego en estos momentos:



¡¡Ahora solo nos falta saltar para coger esas monedas!!

Tenemos que hacer el tema de la gravedad y lo de saltar.

El código correspondiente a esta última entrega lo podéis descargar desde aquí.

Nos vemos en los comentarios.

Saludos

viernes, 22 de mayo de 2009

Creación de mapas para el Juego (II)

Hemos seguido desarrollando el programa de Java para crear mapas. Se ha detectado un error a la hora de generar los mapas de 8 pixeles y se ha añadido la posibilidad de crear más niveles (hasta los cuatro que permite la Nintendo DS).



En la foto se puede ver que se ha añadido una lista de opciones con el nombre 'cero', 'one', 'two' y 'three'. Al añadir dibujos a la pantalla se añadirán al mapa que esté seleccionado. tener en cuenta que el mapa 'cero' es de de mayor prioridad, y así hasta el 'three'. (Por supuesto, mayor prioridad es que sale encima).

En la foto las plantas están en la pantalla 'one', y los ladrillos en la 'two'.

A la hora de cargar un mapa se ha de cargar el cero_mapa_16.txt, con la opción abrir mapa 16. A la hora de guardar se pondrá, únicamente, el nombre de un txt.
Si por ejemplo se pone casa.txt el programa generará:
cero_mapa_16_casa.txt, (este es el que habrá que cargar la próxima vez para editar todos los mapas)
cero_mapa_8_casa.txt,
cero_collision_8_casa.txt,
one_mapa_16_casa.txt,
one_mapa_8_casa.txt,
one_collision_8_casa.txt,
two_mapa_16_casa.txt,
two_mapa_8_casa.txt,
two_collision_8_casa.txt,
three_mapa_16_casa.txt,
three_mapa_8_casa.txt y
three_collision_8_casa.txt

Los ficheros de colisión serán los que utilizaremos para el tema de las colisiones. En nuestro caso sólo usaremos el cero_collision_8_...

La última versión la encontraréis aquí.

Con todo esto creo que tenemos toda la información necesaria para poder programar nuestro juego. Otra cosa que se puede hacer en el futuro es cargar estos txt directamente desde el juego. Tiempo al tiempo.

Saludos

viernes, 15 de mayo de 2009

El movimiento

Otra característica del Mario Bros es que Mario no está siempre en el centro de la pantalla. Si no que cuando se acaban las plataformas, tanto a un lado como a otro, llega hasta el borde de la pantalla.

Esta característica hay que programarla dentro del juego y no de las librerías, es decir, lo pondremos en NSMB.cpp.

El código fuente de la función avanzar será el siguiente:

int NSMB::avanzar() {
velocidad--;
if (velocidad == 0) {
velocidad = MOVIMIENTO_VELOCIDAD;
if (movimiento == MOVIMIENTO_DERECHA) {
if (koopa->GetX() < 128) {//128 = pixeles mitad pantalla
koopa->MoveRight(MOVIMIENTO_VELOCIDAD);
if (topScreen->IsCollision(koopa, mundo_plataformas->GetX(), mundo_plataformas->GetY()))
koopa->MoveLeft(MOVIMIENTO_VELOCIDAD);//deshacemos el avance
} else {
int x = mundo_plataformas->GetX() + 1;
if (x < COLUMNAS_MAPA - COLUMNAS_PANTALLA) {
if (!topScreen->IsCollision(koopa, x, mundo_plataformas->GetY())) {
mundo_plataformas->SetX(x);
mundo_plataformas->PrintMap8x8(MAPA_PLATAFORMAS);
}
} else if (koopa->GetX() < 256) {
koopa->MoveRight(MOVIMIENTO_VELOCIDAD);
if (topScreen->IsCollision(koopa, mundo_plataformas->GetX(), mundo_plataformas->GetY()))
koopa->MoveLeft(MOVIMIENTO_VELOCIDAD);//deshacemos el avance
}
}
} else {//MOVIMIENTO_IZQUIERDA
if (koopa->GetX() > 128) {//128 = pixeles mitad pantalla
koopa->MoveLeft(MOVIMIENTO_VELOCIDAD);
if (topScreen->IsCollision(koopa, mundo_plataformas->GetX(), mundo_plataformas->GetY()))
koopa->MoveRight(MOVIMIENTO_VELOCIDAD);//deshacemos el avance
} else if (mundo_plataformas->GetX() > 0) {
int x = mundo_plataformas->GetX() - 1;
if (!topScreen->IsCollision(koopa, x, mundo_plataformas->GetY())) {
mundo_plataformas->SetX(x);
mundo_plataformas->PrintMap8x8(MAPA_PLATAFORMAS);
}
} else if (koopa->GetX() > 0) {
koopa->MoveLeft(MOVIMIENTO_VELOCIDAD);
if (topScreen->IsCollision(koopa, mundo_plataformas->GetX(), mundo_plataformas->GetY()))
koopa->MoveRight(MOVIMIENTO_VELOCIDAD);//deshacemos el avance
}
}
}
return 1;
}


Hoy no voy a poner una versión del código porque está cambiando bastante por lo que lo dejaré para futuros post. Sólo os puedo decir que las funciones Print de ScreenManager están ahora a nivel de TileSet. La clase Sprite está cambiando y mantener varias versiones no me es posible.

Saludos

viernes, 8 de mayo de 2009

Las colisiones

Quizás es uno de los temas más importantes de nuestro juego: no demos salirnos de nuestro mundo, ni atravesar las paredes. La intención de este post es de dotar a las librerías que estamos creando, y de paso al NSMB, de esta cualidad tan necesaria.

Vamos a ver cosas que hemos tenido en cuenta a la hora de programar el tema de las colisiones:

  1. Veamos cómo está definido Koopa, nuestro héroe:

    Está dentro de un array de 4x4, pero en el fondo sólo ocupa 2x4 (2 columnas y 4 filas). Es decir, una colisión se dará en el caso en que un objeto entrase en las coordenadas (x+1, y) o (x+1, y+1) o (x+1, y+2) o (x+1, y+3) o (x+2, y) o (x+2, y+1) o (x+2, y+2) o (x+2, y+3). El resto de coordenadas no causan colisión.


  2. Otro tema a tener en cuenta. Nuestro juego es de plataformas y Koopa está siempre en las mismas coordenadas, son las plataformas las que se mueven, por lo que habrá que calcular en que casillas del mapa está nuestra tortuga.

    En el código que tenemos hecho hasta la fecha estamos trabajando con formas de 16x16 pixeles,

    y esto nos va a dar problemas. Hay que cambiar las cosas, tenemos que pasar a que nuestro mapa vuelva a ser de 8x8. Dejaremos las funciones de 16x16 por si otro proyecto las necesitase.

  3. El mapa de colisiones, es decir un array parecido al que define la forma de nuestro mundo pero de colisiones.



En definitiva hemos añadido el siguiente código:


  • Se ha creado una función que nos permite asignar un array donde definir los obstáculos de nuestro mundo:
    void ScreenManager::SetCollisionMap8x8(void *collisionMap, unsigned int cols, unsigned int rows).

  • Y una que nos permita preguntar por una posible colisión:
    int ScreenManager::IsCollision(Sprite *sprite, int x, int y). Ambas funciones se han implementado en la clase ScreenManager.

  • En la clase Sprite se han definido las siguientes funciones:
    void SetCollisionMap(void *collisionMap, unsigned int cols, unsigned int rows);
    void *GetCollisionMap();
    unsigned int GetCollisionCols();
    unsigned int GetCollisionRows();
    Que son las que sirven para definir el mapa de colisiones del Sprite.

  • En NSMB.cpp hemos añadido el control de colisiones. Para hacer pruebas hemos dejado el código de subir y bajar, que no son del juego de NSMB, pero nos sirven, en este momento, para probar la funcionalidad. Este control lo encontraréis en la función int NSMB::avanzar() que es invocada desde HeldRight y HeldLeft.

  • El editor de pantallas tiene nuevas opciones como son la de grabar mapas de 8x8 y uno de colisiones.



Para ver la versión que corresponde a este post pinchar aquí.
La nueva versión del editor de mapas está aquí.

Esto empieza a tener algo de forma. Nos falta que nuestro héroe, que tiene que ser Mario y no koopa, empiece a saltar. Lo dejaremos para próximos post. Y las monedas, tenemos que llenar nuestro mundo de monedas!!!

Nos vemos en los comentarios.

Saludos