viernes, 18 de diciembre de 2009

MegaMan ya dispara

Para ver a nuestro nuevo héroe disparando he creado un nuevo proyecto.

El código que he escrito lo he metido dentro de la librería devnintendods, pero no tiene que ser difícil ponerlo en otro proyecto. Por supuesto el juego está en 2D.

Se ha creado un vector en la clase ScreenManager que se denomina m_dynamicSprite. Este es el vector donde se irán añadiendo los distintos elementos dinámicos del juego. En este caso son disparos, pero podrían ser otras cosas, por ejemplo los enemigos, o las casas que se van construyendo en un juego tipo Age of Empire. Los objetos que se asocian a este array deben implementar el interface DyamicSprite. De esta forma todos tendrán la función Next.

Desde el bucle del juego, en GameManager se llama a la función Next de ScreenManager. Se hacen dos llamadas, una para la pantalla de arriba y otra para la pantalla de abajo:

void GameManager::Run() {
 bool exit = 1;
 while (exit) {
  if (m_KeypadManager) {
   if (Pad.Newpress.Up) {
    exit = m_KeypadManager->NewpressUp();
   } else if (Pad.Newpress.Left) {
    exit = m_KeypadManager->NewpressLeft();
   } else if (Pad.Newpress.Right) {
    exit = m_KeypadManager->NewpressRight();
   } else if (Pad.Newpress.Down) {
    exit = m_KeypadManager->NewpressDown();
   } else if (Pad.Held.Up) {
    exit = m_KeypadManager->HeldUp();
   } else if (Pad.Held.Left) {
    exit = m_KeypadManager->HeldLeft();
   } else if (Pad.Held.Right) {
    exit = m_KeypadManager->HeldRight();
   } else if (Pad.Held.Down) {
    exit = m_KeypadManager->HeldDown();
   } else {
    exit = m_KeypadManager->NoKey();
   }
  }
  m_TopScreenManager->Next();
  m_BottomScreenManager->Next();
  exit = exit && m_KeypadManager->BeforeVBL();
  PA_WaitForVBL();
 }
}

ScreenManager es la que recorre el array y llama a todas las funciones Next, independientemente del tipo de objeto que sea. En nuestro caso es siempre un objeto de tipo Disparo.

void ScreenManager::Next() {
 unsigned int i;
 for (i = 0; i < m_dynamicSprites->size(); i++) {
  DynamicSprite *dynamicSprite = m_dynamicSprites->at(i);
  dynamicSprite->Next(this);
 }
}

void ScreenManager::EraseDynamicSprite(DynamicSprite *dynamicSprite) {
 unsigned int i;
 for (i = 0; i < m_dynamicSprites->size(); i++) {
  DynamicSprite *dynamic = m_dynamicSprites->at(i);
  if (dynamic == dynamicSprite) {
   m_dynamicSprites->erase(m_dynamicSprites->begin() + i);
   delete dynamicSprite;
  }
 }
}

Al pulsar la tecla de disparar (he puesto la flecha para arriba porque en mi emulador no funciona la A) se crea un disparo y se añade al array. La función Next del disparo es la que se encargará de mover la bala en la dirección correcta y cuando llegue al final de la pantalla destruir el propio objeto.

bool Disparo::Next(void *screenManager) {
 if (m_direccion == DIRECCION_IZQUIERDA) {
  int x = this->MoveLeft(VELOCIDAD);
  if (x < 0) this->Stop(screenManager);
 } else if (m_direccion == DIRECCION_DERECHA) {
  int x = this->MoveRight(VELOCIDAD);
  if (x > 256) this->Stop(screenManager);
 }
 return 1;
}

void Disparo::Stop(void *screenManager) {
 ScreenManager *sm = (ScreenManager *)screenManager;
 sm->EraseDynamicSprite(this);
}

Y esto es todo. Si disparáis más de 100 veces deja de disparar. He probado y todos los destructores de las clases se ejecutan y la función PA_DeleteSprite también, pero como se ha comentado en algún post anterior, este tema parece que no va bien.


En el próximo post veremos como disparar un sólo proyectil. El proyecto se puede descargar de aquí.

Saludos

viernes, 11 de diciembre de 2009

Cogiendo las monedas

Por fin, Mario puede coger sus monedas y ya empieza esto a parecerse más al New Super Mario Bross de la Nintendo DS.

Está quedando chulo, parece algo:



Se ha tenido que cambiar alguna cosilla de las librerías, pero nada demasiado raro.

Para coger las monedas lo primero es saber donde están y cuanto abultan. Es por esto que se ha tenido que introducir las variables width y height dentro de la clase Sprite. De paso hemos aprovechado para limpiar un poco los constructores y dejarlo todo más fino.

Dentro de la clase Sprite se han modificado e introducido las siguientes funciones:

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

Sprite(unsigned int screen, unsigned int background, unsigned int shape, unsigned int size, void *palette, void *sprite, int x, int y, int width, int height, int hflip);

void SetWidth(int width);
void SetHeight(int height);
void SetWidthHeight(int width, int height);

int GetWidth();
int GetHeight();

bool InSprite(int x, int y);

Esta última función, InSprite mira que el punto x,y esté dentro del Sprite. En nuestro caso será dentro de la moneda y se ha codificado de la siguiente manera:

bool Sprite::InSprite(int x, int y) {
return (x >= m_x && x <= m_x + m_width && y >= m_y && y <= m_y + m_height);
}

No está fino del todo, pero ya lo iremos mejorando.
También hemos codificado el destructor de la clase: 

Sprite::~Sprite() {
PA_StopSpriteAnim(m_screen, m_index_sprite);
PA_DeleteSprite(m_screen, m_index_sprite);

}

De esta forma al coger la moneda destruimos el sprite. Ahora la clase ScreenManager. En las funciones para crear Sprites y sprites de fondo se ha introducido el width y el height:
Sprite *CreateSprite(unsigned int background, unsigned int shape, unsigned int sizes, void *palette, void *sprite, int x, int y, int width, int height, int hflip = 0);

Sprite *CreateBackgroundSprite(unsigned int background, unsigned int shape, unsigned int sizes, void *palette, void *sprite, int x, int y, int width, int height, int hflip = 0);
Y hemos introducido más funciones para trabajar con los Sprites de fondo:
int ScreenManager::GetBackgroundSpriteIndex(int x, int y) {
unsigned int i;
for (i = 0; i < m_backgroundSprites->size(); i++) {
Sprite *sprite = m_backgroundSprites->at(i);
if (sprite->InSprite(x, y)) return i;
}
return -1;
}

Sprite *ScreenManager::GetBackgroundSprite(unsigned int i) {
return m_backgroundSprites->at(i);
}

void ScreenManager::EraseBackgroundSprite(unsigned int i) {
Sprite *sprite = m_backgroundSprites->at(i);
delete sprite;
m_backgroundSprites->erase(m_backgroundSprites->begin() + i);
}
La primera obtiene el índice de un Sprite de fondo a partir de unas coordenadas. Devolverá el primer sprite de fondo que contenga esas coordenadas. La segunda devuelve el Sprite de fondo a partir de un índice. La tercera borra un Sprite de fondo a partir de un índice. Esta última destruye el Sprite. En cuanto a código PAlib hemos usado por primera vez, creo, la función que sirve para borrar un Sprite: PA_DeleteSprite(m_screen, m_index_sprite); Se le pasa la pantalla donde está el sprite asignado y el indice que se le asoció al crearlo (no confundir con el índice de los Sprites de fondo). Para adaptar todas estas nuevas funciones a nuestro propósito, que es coger monedas al pasar sobre ellas, he introducido las siguientes líneas al final de la función BeforeVBL de la clase NSMB.cpp:
int i = topScreen->GetBackgroundSpriteIndex(mario->GetX() + 8, mario->GetY() + 8);
if (i > -1) { //hemos pillado moneda
topScreen->EraseBackgroundSprite(i);
puntuacion_monedas++;
PA_OutputText(SCREEN_BOTTOM, 1, 1, "monedas = %d      ", puntuacion_monedas); // para debugear
}

El código lo podéis encontrar en esta dirección.

Esto es todo por ahora.

Saludos

viernes, 4 de diciembre de 2009

Cargamos las monedas (II)

Hemos encontrado un problema a la hora de cargar las monedas. La función de clonar tiene la peculiaridad de que clona incluso el comportamiento del Sprite, en este caso la animación.

Eso nos venía muy bien hasta ahora, porque todas la monedas se ponían a girar alegremente. Pero ahora, al coger las monedas, las borramos y las paramos (si no se hace esto aparecen efectos raros en la pantalla).

Al parar la primera moneda (la de más arriba), de la que hemos clonado todas, se paraban todas. Así que hemos modificado la función de clonar añadiendo nuevos parámetros (opcionales):

//en ScreenManager.h
void LoadBackgroundSprites(Sprite *sprite, int *map, int map_columns, int map_rows, unsigned int firstframe = 0, unsigned int lastframe = 0, unsigned int speed = 0);

//en ScreenManager.cpp
void ScreenManager::LoadBackgroundSprites(Sprite *sprite, int *map, int map_columns, int map_rows, unsigned int firstframe, unsigned int lastframe, unsigned int speed) {
 unsigned int i;
 unsigned int length = map_columns * map_rows;
 bool primero = true;
 for (i = 0; i < length; i++)
  if (map[i] > 0) {
   int y = i / map_columns;
   int x = i - (y * map_columns);
   if (primero) {
    sprite->SetXY(x * 16, y * 16);
    primero = false;
   } else {
    Sprite *newSprite = CloneBackgroundSprite(sprite, x * 16, y * 16);
    if (speed > 0) {
     newSprite->SetAnim(firstframe, lastframe, speed);
     newSprite->StartAnim();
    }
   }
  }
}

Lo nuevo de la función está al final:

Si la velocidad de la animación es mayor de cero entonces preparamos el sprite para animarlo y comenzamos la animación. De esta forma cada sprite es animado de forma independiente y no depende del primer sprite.

Ya se pode descargar el editor de mapas.

Saludos

P.D: Es importante parar el Sprite a la hora de borrarlo, por lo que estas dos líneas deben ir juntas, si no darán problemas el resto de sprites. En el destructor de la clase Sprite hemos escrito el siguiente código:
Sprite::~Sprite() {
 PA_StopSpriteAnim(m_screen, m_index_sprite);
 PA_DeleteSprite(m_screen, m_index_sprite);
}

y se invoca de esta forma:
delete sprite;

Borra el objeto sprite, y de esta forma se ejecuta el destructor de la clase Sprite.

Aún así el tema no va bien. Si creas y destruyes muchos Sprites al final el sistema no permite crear más o aunque los cree no se ven correctamente, parece un problema de PAlib.

Saludos

miércoles, 2 de diciembre de 2009

Un juego tipo Shooter

O lo que es lo mismo uno de disparar.

Hace un par de semanas respondimos a un comentario de como hacer un juego de disparos. Hemos preparado una nueva versión de la librería devnintendods para que permita a nuestros héroes disparar.

En su momento se respondió con una explicación técnica de como es posible hacerlo. Esta era más o menos la explicación que se dio:  se crea un array o vector y ahí se van acumulando todos los disparos que se vayan efectuando. El motor del juego recorrería ese array en cada ciclo del bucle del juego y moverá los disparos a su siguiente posición.

Hemos diferenciado dos tipos de juegos, los que puede hacer un disparo cada vez (no se puede hacer otro hasta que no acabe el anterior) y otro con disparos ilimitados.

Hemos preparado un nuevo desarrollo basado en el popular personaje de Megaman y nos hemos puesto a pegar tiros como locos por la pantalla.



Las próximas semanas iremos publicando el código y la explicación de como se ha hecho.

Saludos

P.D. En cuanto a la programación en C++ es interesante ver como se ha preparado un Interface. Para los que trabajen en Java parece algo trivial pero a la hora de trabajar en C++ no me ha parecido tan sencillo. Lo veremos en los próximos post.

viernes, 27 de noviembre de 2009

Qué se necesita para programar en Wii

O mejor dicho que he usado yo. En primer lugar, deciros que hay un emulador para Wii o gamecube que se llama dolphin-emu. Pero parece ser que necesita un montón de recursos. Yo lo he intentado ejecutar y me ha sido imposible.

Así que una vez más os voy a mostrar la forma 'chapucera' que tengo de trabajar con la Wii. En primer lugar vemos mi 'laboratorio'. Tengo un router wifi, un ordenador (en mi caso un portátil con ubuntu) y la Wii, estos dos últimos conectados al router, que es por él por donde salgo a Internet.

Me he bajado el ftpii que es un servidor de ftp para la Wii. Lo he copiado a la tarjeta SD de la Wii (dentro de apps) en una carpeta que he llama ftpii_clave, donde clave será nuestra contraseña de entrada.

En el Homebrew channel saldrá el ftpii como un programa a ejecutar. Lo hacemos y nos mostrará la IP con la que sirve los ficheros. Pues nada cogemos nuestro cliente de ftp (en mi caso el filezilla) que estará instalado en el portátil y nos conectamos a esa IP. Si la carpeta la hemos llamado ftpii_clave habrá que usar 'clave' como la contraseña de la cuenta.

Y ya está, tenemos acceso a la tarjeta SD de la Wii, sin tener que estar manipulándola todo el rato. Dentro de apps creo la carpeta del juego que estoy compilando y, eso sí, cambio el nombre del archivo ejecutable mi_programa.dol por boot.dol.

Para probar yo os animaría a instalar de esta forma cualquier otro programa homebrew que bajéis de Internet.

Saludos

viernes, 20 de noviembre de 2009

Cargamos las monedas

Vamos a cargar las monedas en el mundo de New Super Mario Bros, y para hacer esto utilizaremos el programa que nos genera los mapas.

Se ha agregado una nueva opción al generador de mapas para que permita editar una capa denominada coins (monedas). En la siguinete imagen se puede ver al editor a pleno rendimiento:


El editor al guardar generará un nuevo fichero que se llamará coins_mapa_8_nombre del txt) y coins_mapa_16_nombre del txt). Este último será el que utilizaremos.

ahora veamos los cambios en el código del juego.

En primer lugar seguimos añadiendo funcionalidades a las librerías:
En la clase ScreenManager.h se ha añadido una función para cargar sprites de fondo a partir de una array (el que genera el editor de mapas):

/**
* Carga una serie de sprites de tipo background a partir de un mapa y de un primer sprite de tipo background
* ya cargado.
* Este primwr sprite será reubicado a la priemra posición indicada por el mapa.
*
* @param sprite El sprite a cargar
* @param map mapa que indica donde colocar cada sprite
* @param mapCols número de columnas del mapa
*/
void LoadBackgroundSprites(Sprite *sprite, int *map, int map_columns, int map_rows);


En ScreenManager.cpp hemos codificado la funcion de la siguiente forma:


void ScreenManager::LoadBackgroundSprites(Sprite *sprite, int *map, int map_columns, int map_rows) {
unsigned int i;
unsigned int length = map_columns * map_rows;
bool primero = true;
for (i = 0; i < length; i++)
if (map[i] > 0) {
int y = i / map_columns;
int x = i - (y * map_columns);
if (primero) {
sprite->SetXY(x * 16, y * 16);
primero = false;
} else CloneBackgroundSprite(sprite, x * 16, y * 16);
}
}

Lo que hace esta función es coger un sprite de fondo y un array y donde el array le indique coloca un copia del sprite de fondo, que en este caso serán modenas.

Vamos a ver como se usa esta nueva función en nuestro juego:


int coins_map[48 * 12] = {
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, 56, 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, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 56, 0, 56, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 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, 56, 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, 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, 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
};
#define COINS_MAPA coins_map, 48, 12

....

Sprite *coin = topScreen->CreateBackgroundSprite(BACKGROUND_THREE, COIN, 0, 0);
coin->SetAnim(0, 3, 4);
coin->StartAnim();
topScreen->LoadBackgroundSprites(coin, COINS_MAPA);


La explicación sería algo así:
Carga una primera moneda (las coordenadas dan igual, en este caso se ha puesto 0, 0),
Se anima el sprite,
y finalmente se cargan tantas monedas como diga el mapa.

Los números que aparecen en el mapa no es importante (por ahora). Sólo es importante que sea cero donde no va moneda y distinto de cero donde queremos una moneda.

Nos falta coger las dichosas monedas!!

En el próximo post intentaremos cogerlas y de paso publicaremos código.

Saludos

viernes, 13 de noviembre de 2009

Programar para la Wii

Ya desde el primer post teníamos intención de programar algo para la Wii, y no ha sido hasta el pasado mes de Octubre que tuvimos un par de tardes libre y pudimos hacer las primeras pruebas.

En este post vamos a mostrar como preparar la instalación de devkitpro para poder compilar juegos para la Wii. En su día preparamos una instalación sobre ubuntu y ahora vamos a hacer que esa misma instalación soporte a la Wii, y a la Game Cube que es lo mismo.

En resumen podemos decir que es lo mismo que el otro día pero instalando las librerías libogc en vez de libnds. Vamos a ver paso a paso:

Lo primero será bajar todo lo necesario:

Bajar devkitPPC de http://sourceforge.net/projects/devkitpro/files/ (si no lo tenemos instalado ya)
libogc -> http://sourceforge.net/projects/devkitpro/files/libogc/ (libogc-1.7.1.tar.bz2)
libfat-ogc -> http://sourceforge.net/projects/devkitpro/files/libfat/ (libfat-ogc-1.0.4.tar.bz2)
Wii examples (optional) -> http://sourceforge.net/projects/devkitpro/files/examples/ (wii-examples-20090509.tar.bz2)

En segundo lugar todos estos comandos:
$ cd Escritorio

(Si no tenemos instalado devkitpro)
$ sudo mkdir /usr/local/devkitpro 'si ni existiera ya
$ sudo chmod 777 /usr/local/devkitpro 'si ni existiera ya
$ sudo bunzip2 devkitARM_r26-i686-linux.tar.bz2
$ sudo tar xvf devkitARM_r26-i686-linux.tar -C /usr/local/devkitpro

(Seguimos)
$ sudo bunzip2 devkitPPC_r17-i686-linux.tar.bz2
$ sudo tar xvf devkitPPC_r17-i686-linux.tar -C /usr/local/devkitpro
$ sudo mkdir /usr/local/devkitpro/libogc
$ sudo chmod 777 /usr/local/devkitpro/libogc
$ sudo bunzip2 libogc-1.7.1.tar.bz2
$ sudo tar xvf libogc-1.7.1.tar -C /usr/local/devkitpro/libogc
$ sudo bunzip2 libfat-ogc-1.0.4.tar.bz2
$ sudo tar xvf libfat-ogc-1.0.4.tar -C /usr/local/devkitpro/libogc

Ahora añadimos unas variables de contexto:

$ gksudo gedit /etc/environment

y añadimos al final las siguientes líneas:

DEVKITPRO="/usr/local/devkitpro"
DEVKITARM="/usr/local/devkitpro/devkitARM"
DEVKITPPC="/usr/local/devkitpro/devkitPPC"

Reiniciando la máquina ya podremos compilar los ejemplos.

Instalar libwiisprite:

Descargar de http://chaosteil.googlepages.com/libwiisprite-0.3.0b.tar.gz

Lo descomprimos en el mismo lugar donde lo descargamos y realizamos los siguientes pasos:

De la carpeta libwiisprite/libwiisprite/include copiamos todos los archivos a /usr/local/devkitpro/libogc/include
(en mi caso he ejecutado la siguiente acción desde un terminal: sudo cp /home/inigo/Escritorio/libwiisprite/libwiisprite/include/*.h /usr/local/devkitpro/libogc/include)
Ahora sudo cp /home/inigo/Escritorio/libwiisprite/libwiisprite/lib/*.a /usr/local/devkitpro/libogc/lib/wii

Lo mismo con la carpeta libwiisprite/libpng:
sudo cp /home/inigo/Escritorio/libwiisprite/libpng/include/*.h /usr/local/devkitpro/libogc/include
sudo cp /home/inigo/Escritorio/libwiisprite/libpng/lib/*.a /usr/local/devkitpro/libogc/lib/wii

Yo en este punto tuve un problema con libwiisprite y realicé los siguientes pasos:

Bajar una nueva compilación de http://www.mediafire.com/download.php?wwycxmg0j0x
y ejecutar: sudo cp /home/inigo/Escritorio/libwiisprite.a /usr/local/devkitpro/libogc/lib/wii

Descargar con Synaptic la librería libfreeimage

Si todo ha ido bien ya se podrá compilar los ejemplos que vienen con la librería.

Hemos instalado la librería libwiisprite, hay más librerías pero nosotros sólo vamos a usar esta, por ahora.

Y que vamos a hacer nosotros en este blog, pues algo muy sencillo, vamos a pasar el Tetris que hicimos para la DS a la Wii!!!

Para ir abriendo boca os dejo un video para que veáis como ha quedado el juego, si os fijaís bien es el mismo que el de la nintendo DS (sólo hemos cambiado el fondo).


Saludos

P.D. En los siguientes post sobre Wii veremos como trabajo con ella, pero lo que no puedo explicar bien es como crear el Homebrew channel. Lo hice hace un año y ahora hay nuevas formas de hacerlo y no tengo ni idea.

P.D. Seguimos con el tema del New Super Mario Bros. El próximo post será para él.

martes, 10 de noviembre de 2009

Retomamos

Ha habido un cierre temporal del blog estos meses, que espero que sea eso, temporal.

Como penitencia voy a crear unos post hablando de la Wii y de como programar para ella. Ya tengo portado el tetris (por ahora usando el mando como un pad) y no hemos pasado el New Super Mario Bros, porque lo van a hacer los de Nintendo este mes y no era cuestión de adelantarnos a ellos :D

Por supuesto, seguiremos con el New Super Mario Bros para la Nintendo DS.

Este viernes posteamos fijo!!!

Saludos

P.D. Hemos cambiado el título del blog, ahora es Programar para la Nintendo DS y Wii, la url por supuesto sigue igual!!

viernes, 18 de septiembre de 2009

La aventura de Mario

Por fin ponemos a Mario en su sitio. Mario se hace dueño y señor de su mundo. Un juego que se llama New Super Mario Bros y que no saliera él por ningún sitio era algo extraño. Pues nada un poco de photoshop para ponerle guapo y ya está. Quitamos a koopa y ponemos a Mario.

Nos ha hecho cambiar alguna cosa más, como por ejemplo el mario_collision_map. Es que este Mario es un poco más bajito que Koopa. También cambia el número de frames que forman la animación (el número de imágenes que representan el movimiento).

El aspecto de nuestro juego DS es el siguient:


Es el Mario pequeñito. Ya pondremos al intermedio, porque el grande es otra historia.

Saludos

viernes, 11 de septiembre de 2009

Instalar DevkitPro en linux - Ubuntu

Hemos instalado devkitpro y PAlib en una distribución de Ubuntu.

Hemos seguido los siguientes pasos:

Hemos descargado de la red los siguientes ficheros:

devkitarm, libNds, DSwifi, libfat de http://sourceforge.net/project/showfiles.php?group_id=114505
y PAlib: http://www.palib.info/wiki/doku.php

Se han descargado en el Escritorio. Abrimos un terminal y ejecutamos los siguientes comandos:

$ cd Escritorio
$ sudo mkdir /usr/local/devkitpro
$ sudo chmod 777 /usr/local/devkitpro
$ sudo bunzip2 devkitARM_r26-i686-linux.tar.bz2
$ sudo tar xvf devkitARM_r26-i686-linux.tar -C /usr/local/devkitpro
$ sudo mkdir /usr/local/devkitpro/libnds
$ sudo chmod 777 /usr/local/devkitpro/libnds
$ sudo bunzip2 libnds-1.3.6.tar.bz2
$ sudo tar xvf libnds-1.3.6.tar -C /usr/local/devkitpro/libnds
$ sudo bunzip2 dswifi-0.3.9.tar.bz2
$ sudo tar xvf dswifi-0.3.9.tar -C /usr/local/devkitpro/libnds
$ sudo bunzip2 libfat-nds-20070127.tar.bz2
$ sudo tar xvf libfat-nds-20070127.tar -C /usr/local/devkitpro/libnds
$ sudo mkdir /usr/local/devkitpro/PAlib
$ sudo chmod 777 /usr/local/devkitpro/PAlib

Aquí me cansé de lanzar tantos comandos y lo que hice fue copiar con el explorador de archivos el
fichero PAlib99999.7z a devkitpro y con el ratón ejecutar descomprimir aquí.

Para acabar lanzamos el siguiente comando:
$ gksudo gedit /etc/environment

y añadimos al final las siguientes líneas:

DEVKITPRO="/usr/local/devkitpro"
DEVKITARM="/usr/local/devkitpro/devkitARM"
PAPATH="/usr/local/devkitpro/PAlib"

Para instalar un emulador realizamos los siguientes pasos:

En el menú principal: Sistema/administración/gestor de paquetes synaptic
buscamos 'Desmume' y lo bajamos. Son dos paquetes.

Y ya está todo.

He creado la carpeta devDS en /home/inigo/devDS (donde inigo es mi usuario) y ahí he copiado el proyecto. Dentro de SuperMarioDS se puede ejecutar (desde el terminal) la sentencia make. Una vez compilado para ejecutarlo escribimos desmume SuperMarioDS.nds y ...



Es Mario y no Koopa!!.....la semana que viene lo vemos.

Saludos

NOTA:
A fecha de Junio de 2010 esta ayuda ya no va bien. Además de los ficheros que se han instalado es necesario bajarse otros más como son maxmod_ns, libfilesystem y default arm7. Todos estos ficheros los hemos instalado en la carpeta libnds.
Os pongo el link a la wiki de devkitpro donde explica todo: http://wiki.devkitpro.org/index.php/Getting_Started/devkitARM

Existe otro forma de instalarlo y es ejecutando un script (devkitpro.sh) que puedes bajar de la página http://lmn.mooo.com/projects/devkitpro-sh/. Este script se pueden modificar las primeras líneas para que no instale lo que no se quiera:
INSTALL_PALIB="yes"
INSTALL_ULIB="yes"
INSTALL_NOCASHGBA="yes"

Se cambia el 'yes' por otra cosa y esa librería o programa no se instala. Te pide tener instalado el 7zr y el unrar. estos se instalan escribiendo en la consola:  sudo apt-get install p7zip y  sudo apt-get install unrar.

Acordarse de dar permisos de ejecución al script:  chmod a+x devkitpro.sh

Aunque todo esto lo explican más o menos en la página web.

viernes, 4 de septiembre de 2009

Nueva versión del entorno de desarrollo

Tenemos nueva versión de devkitPro y parece que muchos de los problemas de instalación han desaparecido. Ya no hace falta cambiar algunas de las librerías que vienen por defecto con el devkitPro para que PAlib funcione.

Pero sí que va a ser necesario cambiar algo de nuestro código. Pero eso lo veremos más adelante.

Ahora mismo los pasos para instalar el entorno de homebrew de la nintendo DS son los siguientes:

Para instalar devkitPro en windows:

1. Bajar devkitPro Updater 1.5.0 de esta dirección.
2. En una de las pantallas nos muestra esta opción 'Download and install/install from downloaded files' la dejamos pulsada y seguimos
3. La siguiente nos preunta que hacer con los ficheros que ha bajado. Por defecto está seleccionada Keep downloaded files, que lo único que hace es dejar todos los ficheros en la carpeta de descargas. Yo lo dejé por si tenía algún problema. Cada uno... Seguimos.
4. Seleccionamos devkitARM -> para trabajar con la DS (la que usamos en este blog)
devkitPPC -> para gamecube/wii
devkitPSP -> para la PSP
2. A la hora de instalarlo Lo instalaremos en c:\devkitPro (Instalarlo donde queráis pero que la ruta no tenga espacios en blanco)
6. Se tirará un ratito bajando e instalando cosas...

Y ya está, ahora le toca el turno a PAlib.

7. Bajamos la última versión (es una beta).
8. Creamos la carpeta c:\devkitPro\PAlib y descomprimimos el zip PAlib090801.7z ahí.

Y ya está todo!!!!.

Ahora un emulador. Yo he bajado no$gba y lo he instalado en C:\devkitPro\emulators\no$gba.

Ahora le toca el turno a nuestro programa, el SuperMarioDS.

Hay que cambiar una serie de cosas en el código, así que crearemos un proyecto nuevo y trasladaremos las cosas del antiguo a este. Lo primero que hacemos es copiar la carpeta C:\devkitPro\PAlib\template a c:\devDS o donde trabajemos. Cambiamos el nombre template por SuperMarioDS y borramos el fichero main.c de source. Yo para hecer esto renombre el antiguo SuperMarioDS por SuperMarioDS_old

Si nos fijamos las carpetas han cambiado un poco. El mayor cambio es la carpeta gfx, que pasa de source a estar al nivel de todas las demás. El programa ha cambiado un poco así que únicamente copiaremos los ficheros PNG a la nueva carpeta. El ini ha cambiado, también, así que lanzamos el programa PAGfxFrontend.exe (se necesita tener instalado .net) y se cargan como sprites lo siguientes ficheros: coin.png, goomba.png y koopa.png
Como backgrounds se cargarán estos otros: fondo_azul.png, newImage_0.png, newImage_1.png

Salvamos y generamos, ningún problema. Sólo que únicamente crea all_gfx.h y NO all_gfx.c. Tendremos que quitar esa referencia.

En el make file hacemos una pequeña modificamos en la línea 46:

Antes: SOURCES := source data gfx/bin
Ahora: SOURCES := source data gfx/bin source/devnintendods

De esta forma podemos usar las fuentes que hay en source/devnintendods.

Ahora el resto de cosas. Los ficheros que estaban en data (dos .raw) siguen en la nueva data.

El fichero devnintendods.h de include se va al nuevo include y source sigue igual quitando el gfx antiguo, claro.

Ahora, si compilamos, saldrán una serie de errores. En mi caso bastantes. Lo primero, quitar all_gfx.c de NSMB.cpp.

Otra cosa, en ScreenManager.cpp cambia la línea 45 y en el .h correspondiente la línea 137:
Lo que antes era:
SetBackground(unsigned int background, int *info, void * pal, const unsigned char *tiles, u32 tiles_size, const unsigned short *mapa)
ahora es:
SetBackground(unsigned int background, int *info, void * pal, const unsigned char *tiles, unsigned int tiles_size, const unsigned int *mapa)

El array Blank, que era un array a blanco ha desaparecido, así que da error la línea 16 del TileSet.cpp, la he comentado. Ahora no se usa, pero...

Y no recuerdo más cambios.

El juego ya compila y se puede ejecutar.

La semana que viene veremos como instalar en ubuntu, que es un poco más largo, pero no da ningún tipo de problema.

Saludos

P.D. Tras el descanso veraniego, en que le blog no ha sido actualizado, vamos a intentar seguir con el blog. No sé de cuanto tiempo dispondré pero bueno...

Enlaces paqtrocinados

viernes, 17 de julio de 2009

Dejamos el salto fino

Hasta ahora a la hora de caer de un salto nuestro personaje realizaba una misteriosa maniobra. Justo antes de caer al suelo frenaba de forma misteriosa. Lo que ocurría era que el código al obtener una colisión con el suelo reseteaba todos los valores del salto y empezaba a caer de nuevo.

Eso hay que cambiarlo.

Lo único que se necesita es que al obtener una colisión, como estamos cayendo con una 'fuerza' de más de un pixel, debemos encontrar la primera posición libre antes de las plataformas. Un sencillo for realizará este trabajo (NSMB.cpp):


bool NSMB::BeforeVBL() {
if (tiempoSalto-- < 0) {

tiempoSalto = MOVIMIENTO_VELOCIDAD_INTERMEDIA;
int y = koopa->GetY();
koopa->SetY(y + fuerzaSalto);
if (topScreen->IsCollision(koopa, topScreen->GetBackgroundsX() / 8, topScreen->GetBackgroundsY() / 8)) {
if (fuerzaSalto > 0) {
int i = 0;
for (i = 0; i < MAXIMA_FUERZA_CAIDA; i++) {
koopa->MoveUp();
if (!topScreen->IsCollision(koopa, topScreen->GetBackgroundsX() / 8, topScreen->GetBackgroundsY() / 8)) break;
}
} else {
koopa->SetY(y);
}
fuerzaSalto = 0;
volando = false;
} else {
if (fuerzaSalto < MAXIMA_FUERZA_CAIDA) fuerzaSalto++;
volando = true;
}
}
return 1;
}


El aspecto del juego no ha cambido demasiado:

Otras cosas que hemos hecho es seguir 'mejorando' la librería. Por ejemplo, las funciones de mover un sprite, MoveLeft, MoveUp que tengan el parámetro opcional y así realizar un movimiento de 1 por defecto, preparando el código para coger las monedas, etc.

Esta semana si vamos a colgar el proyecto, tal cual está.

Saludos

P.D. Otra cosa, estamos en verano y los post no serán tan fluidos, pero supongo que tampoco las visitas.

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

viernes, 24 de abril de 2009

Creación de mapas para el Juego

Después del paréntesis de la semana santa volvemos al proyecto en que estamos metidos, hacer el New Super Mario Bros para la Nintendo DS.

En esta entrada vamos a presentar un programa que sirve para hacer los mapas del juego. Es un poco cutre, pero el tiempo no da para más. Aquí os dejo un pantallazo del programa.



Su uso es muy sencillo:
  • Primero, se debe cargar el tileset con el que queremos dibujar el mapa (fichero/abrir tileset...),

  • Segundo, si queremos modificar un mapa ya hecho lo cargamos con la opción fichero/abrir mapa 16,

  • Tercero, seleccionamos el tile que queremos dibujar en la parte superior,

  • Cuarto, vamos 'clickando' por la pantalla para ir poniendo los cuadraditos y

  • Finalmente guardamos el mapa con la opción 'guardar 16'.

Si os fijáis, al cargar se carga con la opción de cargar mapa 16. Esto es que el programa trabaja con tiles de 16x16 pixeles, así que el mapa a cargar sera de 16.

La opción de 'grabar 16' graba el mapa con tiles de 16x16 pixeles (salida_16.txt).
La opción de 'grabar 8' graba con tiles de 8x8 pixeles (salida_8.txt).

El proyecto java lo encontrareis aquí.

No he explicado nada de java, no es tema de este blog, pero si alguien quiere saber algo, que lo diga. Por otra parte, ¡¡¡he comentado bastante el código!!!


Para ver el mapa dentro de nuestro juego, únicamente, tendremos que copiar el contenido del fichero a NSMB.cpp. Es el array denominado mapa8.

El último código lo encontraréis aquí.

Hay un cambio con respecto a la anterior versión y es que antes dibujábamos casillas de 16x16 y ahora de 8x8. Dejaremos la anterior funcionalidad en las librerías por si a algún otro proyecto le pueda interesar.

Ahora ya podemos ir a por el tema de las colisiones y, después, a por el tema de la gravedad.

Ya siento no poder avanzar más pero de donde no hay no se puede sacar, y no tengo nada de tiempo.

Nos vemos en los comentarios, saludos

viernes, 10 de abril de 2009

Doxygen :: Comentar el código

He estado hablando con David y estamos viendo la posibilidad de comentar el código de una forma más extensa que hasta ahora. O mejor dicho, hasta ahora no se comentaba casi nada en las fuentes.

Hemos estado mirando las distintas herramientas que hay en Internet para documentar un proyecto y parece que Doxygen es la que nos parece más potente y fácil de usar. Posee una opción para utilizar los comentarios JavaDoc por lo que el que conozca esta forma de comentar ya lo tiene todo hecho, y ese es más o menos mi caso.

El resultado es algo parecido a esto:


¡Ahora sólo queda comentar el código!

Saludos

viernes, 3 de abril de 2009

Sonidos en la Nintendo DS

Esta semana vamos a tocar un tema que hasta el momento no habíamos hecho nada, el sonido.

Para ilustrar el tema tenemos código que ha mandado David. David es un crack de la programación de la nintendo DS, además de ser quien aporta más comentarios al Blog.

El código es el siguiente:


#include &lh;PA9.h>

#include "intro.h"
#include "uno.h" // Include the sound (found in the data folder in .raw format)
#include "dos.h"
#include "tres.h"
#include "cuatro.h"

#include "constantes.h"

int main(void)
{
PA_Init(); // Initializes PA_Lib
PA_InitVBL(); // Initializes a standard VBL

PA_InitSound();

PA_InitText(SCREEN_TOP, BACKGROUND_ZERO); // Initialise the text system on the top screen
PA_InitText(SCREEN_BOTTOM, BACKGROUND_ZERO); // Initialise the text system on the top screen

PA_OutputSimpleText(SCREEN_TOP, 0, 1, "Prueba raw,pulsa Arriba para escuchar");
PA_OutputSimpleText(SCREEN_TOP, 0, 3, "--------------------------------");

PA_WaitForVBL();

s32 sonido = 1;
while (1)
{
if (Pad.Newpress.Up)
{
switch (sonido)
{
case 1:
PA_PlaySimpleSound(0, uno);
sonido ++;
break;
case 2:
PA_PlaySimpleSound(0, dos);
sonido ++;
break;
case 3:
PA_PlaySimpleSound(0, tres);
sonido ++;
break;
case 4:
PA_PlaySimpleSound(0, cuatro);
sonido = 1;
break;
default:
PA_PlaySimpleSound(0, intro);
break;
}
}

PA_OutputText(SCREEN_BOTTOM, 1, 3, "sonido %d ", sonido);

PA_WaitForVBL();
}

return 0;
}


Para realizar este proyecto se han de seguir los siguientes pasos:

1. Copiar los ficheros de sonido .raw a la carpeta data
2. realizar los includes de los sonidos
3. constants.h se encuentra en la carpeta 'include'. Como ya nos enseñó David, en esa carpeta puedes dejar cosas comunes y el compilador va a buscarlas ahí por defecto. constants.h hace las veces de devnintendods.h en los proyectos mios.
4. PA_InitSound(); Esta función de PALib no la habíamos usado nunca hasta ahora. Inicializa el sistema de sonido de la consola.
5.PA_PlaySimpleSound(0, uno); Hace sonar el fichero de sonido uno, por el canal 0. Tenemos hasta 8 canales disponibles, lo que permite tener varios sonidos sonando a la vez. En este ejemplo sólo falta una música de fondo, como si fuera la melodía de un juego.

El programa mantiene una variable, sonido, que va pasando de 1 a 4 para que suenen los distintos ficheros.

Un código sencillo y elegante.

El proyecto lo dejo donde siempre.

Saludos y gracias David.

P.D. En estos momentos estoy sin tiempo, y con la proximidad de semana santa quizás no consiga llegar a mi cita semanal...

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