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.