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