jueves, 21 de julio de 2011

GameEngine: Capitulo 24 . Mejorando el Tile Manager : Items

Hola a todos...

Bienvenidos a un nuevo capitulo de como crear tu propio game engine...

El capitulo de hoy tratará de arreglar un par de fallitos del tilemanager y de implementar un sistema dentro del tilemanager capaz de gestionar items. Por items entiendo a todo aquello que al tocarlo, te da o te quita vida , munición , escudo o que tiene algún efecto especial, como por ejemplo ir al siguiente nivel.

Dentro del código del tilemanager podreis observar que se ha arreglado el sistema de colisiones para que no gaste tantos recursos (antes miraba las colisiones del personaje contra todo el mapa y ahora solo en su zona) así como que se ha implementado un nuevo sistema de carga de materiales y mapas y funciones de colisiones con items...

/**************************************************************************************************/
// Código creado por F.Bordas (LordPakus) como ejemplo de creación de un game engine
// para el blog LordPakus (http://lordpakus.blogspot.com/).
// Prohibida la distribución fuera de este blog sin el permiso expreso del autor
/**************************************************************************************************/

/**************************************************************************************************/
// TileManager.h : Código de la parte de tile manager del game engine
// Caracteristicas especiales: Esta clase implementa un singleton, es decir, solo podrá existir un objeto de esta clase en todo el proyecto
/**************************************************************************************************/

#include "TileManager.h"
#include "Core.h"
#include "TextureManager.h"
#include "Anim2DManager.h"

#include <iostream> //Usada para imprimir por consola

using namespace std;

//Número de materiales que se han cargado
static int num_id = 0;
static int x_player,y_player;

//Instancia única del tile manager
TileManager TileManager::instance;

TileManager::TileManager()
{
}

TileManager::~TileManager()
{
}

//Devolvemos el puntero al singleton
TileManager& TileManager::singleton()
{
return instance;
}

//Sounds functions
void TileManager::Init(int* argc, char* argv[])
{
char cad[256]; //Cadena donde guardaremos cada linea que leamos
FILE *fp;

char ref; //Referencia dentro del tilemap
int type; //Tipo de tile: 0-Paredes y suelos o 1-items...
int anim; //0- estatico 1-animación
char id[32]; //Cadena de identificación de recurso

cout << "Inicializamos el tile manager\n";

map = NULL;

fp = fopen("Data\\LoadMaterials.txt","r");
while(!feof(fp))
{
fscanf(fp,"%c %d %d %s\n",&ref, &type,&anim,id);
loaded[num_id].ref = ref;
loaded[num_id].type = type;
loaded[num_id].anim = anim;
sprintf( loaded[ num_id ].cad ,"%s" , id ); //Nos guardamos la referencia al recurso
//cout << loaded[ num_id ].cad << " " << loaded[ num_id ].type << "\n";
num_id++;
}

fclose(fp);
}

void TileManager::DeInit()
{
cout << "Desinicializamos el Tile manager\n";
}

//Leemos un mapa de tiles. Se carga en tiempo de juego ya que no podriamos cargar todos los mapas del juego a la vez.
void TileManager::LoadMap(char cad[])
{
int i,j;
int out = 0;
char tile;
FILE *fd;
cout << "Intentamos cargar mapa de tiles con ruta: " << cad <<"\n";
fd = fopen(cad,"r");

if (fd == NULL )
return;

cout << "Archivo de mapa accesible\n";
if (map != NULL)
{
free(map);
map = NULL;
}

map_width = 0;
map_height = 0;

//Calculamos el ancho y largo del mapa
while(!out)
{
fscanf(fd,"%c",&tile);
if(tile == '\n')
{
out = 1;
map_height = 1;
}
else
map_width++;
}
out = 0;
while(!out)
{
fscanf(fd,"%c",&tile);
if(feof(fd))
{
out = 1;
}
else
{
if(tile == '\n')
map_height++;
}
}

cout << "Cargamos mapa con dimensiones " << map_width << "x" << map_height << "\n";

rewind(fd);

//Dinamic memory for the map
map = (int *) malloc(map_height * map_width * sizeof(int));

//Cuando llegamos aqui ya tenemos la zona de memoria reservada para guardar el mapa y sabemos que dimensiones tiene el mapa.

//Ahora nos dedicamos a leer el mapa y ponerlo en memoria
for( j = map_height-1 ; j >= 0 ; j-- )
{
for( i = 0 ; i < map_width ; i++ )
{
fscanf(fd,"%c",&tile);

//Hardcodeo. El * indica la posición inicial del player en el mapa
if(tile == '*')
{
//Ese 32 hardcodeado se ha de arreglar
x_player = i*32;
y_player = (j+2)*32;
}

map[(j*map_width)+i]=tile;
}
fscanf(fd,"%c",&tile); //pass enter
}
fclose(fd);
cout << "Mapa cargado\n";
}

void TileManager::GetInitialPosition(int *x, int *y)
{
*x = x_player;
*y = y_player;
}

void TileManager::DrawMap()
{
int i,j,k;
float x,y,t;
int tile;

for( i = 0 ; i < map_height ; i++ )
{
for( j = 0 ; j < map_width ; j++ )
{
tile = map[ (i*map_width + j) ];

//Buscamos que esa tile la tengamos definida
for(k = 0 ; k < num_id ; ++k)
{
//Si coincide la tile con el material, salimos de la busqueda
if(loaded[k].ref == tile)
break;
}

switch(loaded[k].anim)
{
//Tile de tipo gráfico estático
case 0:
t = TextureManager::singleton().GetWidth(loaded[k].cad,0);
x = j*t;
y = i*t;
Core::singleton().DrawSprite(x,y,loaded[k].cad,0);
break;

//Tile de tipo gráfico animado
case 1:
//Hardecodeo que se arreglará en otro momento.
t = 32;//TextureManager::singleton().GetWidth(loaded[tile].cad,1);
x = j*t;
y = i*t;
Core::singleton().DrawAnim(x,y,loaded[k].cad);
break;
}
}
}
}

bool TileManager::CollideWithAnim(int x, int y, char id[])
{
int i,j,k,t,tile,xt,yt;
int xmin,xmax,ymin,ymax;

//Por ahora hardcoded
xmin = x/32 - 5;
xmax = x/32 + 5;
ymin = y/32 - 5;
ymax = y/32 + 5;

if( ymin < 0)
ymin = 0;

if( xmin < 0)
xmin = 0;

if( ymax > map_height)
ymax = map_height;

if( xmax > map_width)
xmax = map_width;

for( i = ymin ; i < ymax ; i++ )
{
for( j = xmin ; j < xmax ; j++ )
{
tile = map[ (i*map_width + j) ];

//Buscamos que esa tile la tengamos definida
for(k = 0 ; k < num_id ; ++k)
{
//Si coincide la tile con el material, salimos de la busqueda
if(loaded[k].ref == tile)
break;
}
//Solo calcularemos colisiones si es un suelo o una pared
if(!loaded[k].type)
{
t = TextureManager::singleton().GetWidth(loaded[k].cad,0);
xt = j*t;
yt = i*t;
if( Anim2DManager::singleton().CollideWithGraph( x , y , id ,
xt , yt , loaded[k].cad , 0) )
{
return true;
}
}
}
}
//Si hemos llegado hasta aqui es que no hemos colisionado con nada.
return false;
}

bool TileManager::CollideWithGraph(int x, int y, char id[], int part)
{
int i,j,k,t,tile,xt,yt;
int xmin,xmax,ymin,ymax;

//Por ahora hardcoded
xmin = x/32 - 5;
xmax = x/32 + 5;
ymin = y/32 - 5;
ymax = y/32 + 5;

if( ymin < 0)
ymin = 0;

if( xmin < 0)
xmin = 0;

if( ymax > map_height)
ymax = map_height;

if( xmax > map_width)
xmax = map_width;

for( i = ymin ; i < ymax ; i++ )
{
for( j = xmin ; j < xmax ; j++ )
{
tile = map[ (i*map_width + j) ];

//Buscamos que esa tile la tengamos definida
for(k = 0 ; k < num_id ; ++k)
{
//Si coincide la tile con el material, salimos de la busqueda
if(loaded[k].ref == tile)
break;
}
//Solo calcularemos colisiones si es un suelo o una pared
if(!loaded[k].type)
{
t = TextureManager::singleton().GetWidth(loaded[k].cad,0);
xt = j*t;
yt = i*t;

if( LPE.GrafCollision(x , y , id , part ,
xt , yt , loaded[k].cad , 0) )
{
return true;
}
}
}
}
//Si hemos llegado hasta aqui es que no hemos colisionado con nada.
return false;
}

bool TileManager::CollideItemWithAnim(int x, int y, char id[], char *result)
{
int i,j,k,t,tile,xt,yt;
int xmin,xmax,ymin,ymax;

//Por ahora hardcoded
xmin = x/32 - 5;
xmax = x/32 + 5;
ymin = y/32 - 5;
ymax = y/32 + 5;

if( ymin < 0)
ymin = 0;

if( xmin < 0)
xmin = 0;

if( ymax > map_height)
ymax = map_height;

if( xmax > map_width)
xmax = map_width;

for( i = ymin ; i < ymax ; i++ )
{
for( j = xmin ; j < xmax ; j++ )
{
tile = map[ (i*map_width + j) ];

//Buscamos que esa tile la tengamos definida
for(k = 0 ; k < num_id ; ++k)
{
//Si coincide la tile con el material, salimos de la busqueda
if(loaded[k].ref == tile)
break;
}
//Esta función se encarga de decir si hemos colisionado contra un item (y solo contra un item)
if(loaded[k].type == 1)
{
t = TextureManager::singleton().GetWidth(loaded[k].cad,0);
xt = j*t;
yt = i*t;
if( Anim2DManager::singleton().CollideWithGraph( x , y , id ,
xt , yt , loaded[k].cad , 0) )
{
//Devolveremos que hemos colisionado contra un item y escribiremos su cadena de gráfico.
sprintf(result,"%s",loaded[k].cad);

//Borramos el item, ya se ha gastado
map[ (i*map_width + j) ]= 0;
return true;
}
}
}
}
//Si hemos llegado hasta aqui es que no hemos colisionado con nada.
return false;
}


Dentro del código de juego, entre otros cambios menores se ha implementado que cuando se toque el objeto de "exit" vayamos al siguiente nivel.
//ARCHIVO DE JUEGO

#ifndef __PlayState__
#define __PlayState__

#include "MyGL.h"
#include "BulletManager.h"

#include <string.h>
#include <cstdlib>
#include <stdio.h>
#include <math.h>

#define PI 3.14159265

static int level = 1;
static int xplayer, yplayer;

class PlayState
: public State
{
public:
PlayState() {}

public:

void enter()
{
char cad[32];
if(level>10)
level = 1;

sprintf(cad,"Data\\Levels\\level0%d.txt",level);
LPE.LoadTileMap(cad);
LPE.GetInitialPosition(&xplayer,&yplayer);
BulletManager::singleton().Init();
}

void leave()
{
}
void update()
{
}

void draw ()
{
static bool right = true;
static int jump = 0;
static int angle = 0;
char anim[16];
char r[32]; //String de resultados de la función de colisión con items
char cad[64];

//A todo lo que pintemos a partir de ahora se le aplicará scroll.
LPE.EnableFlag("SCROLL");

LPE.DrawMap2D(); //Pintamos el mapa cargado

BulletManager::singleton().Draw();

LPE.Scroll(xplayer,yplayer);

//Por defecto el personaje no correrá sino que estará a la espera
sprintf(anim,"STAND");

//Comprovamos que el personaje no esté tocando con ningún item
if ( LPE.CollideItemWithAnim(xplayer,yplayer,anim,r) )
{
if(!strcmp(r,"AMMO"))
{
LPE.PlaySound("COIN");
}

if(!strcmp(r,"COIN"))
{
LPE.PlaySound("COIN");
}

if(!strcmp(r,"LIFE"))
{
LPE.PlaySound("COIN");
}
if(!strcmp(r,"EXIT"))
{
//Hemos llegado al final, salimos
level++;

Core::singleton().changeStateTo("STATS");
}
}

//Comprovamos inicialmente que no esté chocando con nada
if (LPE.CollideAnimWithMap2D(xplayer,yplayer,anim))
{
if (!LPE.CollideAnimWithMap2D(xplayer+5,yplayer,anim))
xplayer+=5;

if (!LPE.CollideAnimWithMap2D(xplayer-5,yplayer,anim))
xplayer-=5;
if (!LPE.CollideAnimWithMap2D(xplayer,yplayer+5,anim))
yplayer+=5;

if (!LPE.CollideAnimWithMap2D(xplayer,yplayer-5,anim))
yplayer-=5;

}

if ( LPE.KeyBoardRight() )
{
xplayer+=5;
right = true;
sprintf(anim,"RUN");
if (LPE.CollideAnimWithMap2D(xplayer,yplayer,"RUN"))
{
xplayer -= 5;
}
}

if ( LPE.KeyBoardLeft() )
{
xplayer-=5;
right = false;
sprintf(anim,"RUN");
if (LPE.CollideAnimWithMap2D(xplayer,yplayer,"RUN"))
{
xplayer += 5;
}

}

if ( LPE.KeyBoardUp() && !jump)
{
//Si estamos tocando el suelo
if (LPE.CollideAnimWithMap2D(xplayer,yplayer-5,anim))
{
jump = 25;
}
}
if(jump)
{
yplayer+=jump;
if (LPE.CollideAnimWithMap2D(xplayer,yplayer,anim))
{
yplayer -= jump;
jump = 1; //Paramos de saltar
}
jump--;
}

if ( right )
LPE.DisableFlag("INVX");
else
LPE.EnableFlag("INVX");
angle = LPE.Angle2DToMouse(xplayer,yplayer);

if( angle < 0.1 ) angle = 0.1;
if ( LPE.MouseClick() )
{
if(right)
BulletManager::singleton().Shoot(xplayer+10 , yplayer-15 , angle,40,"PLAYER",8,"DISPARO");
else
BulletManager::singleton().Shoot(xplayer-60 , yplayer-15 , angle,40,"PLAYER",8,"DISPARO");
}

yplayer-=5;
if (LPE.CollideAnimWithMap2D(xplayer,yplayer,anim))
{
yplayer+=5;
}

//Personaje
LPE.DrawAnim(xplayer,yplayer,anim);

if(right)
LPE.DrawCenterRotate(xplayer-10,yplayer-30,angle,"PLAYER",3);
else
LPE.DrawCenterRotate(xplayer-60,yplayer-30,angle,"PLAYER",3);

//Cuando hemos acabado de pintar lo que queremos tener girado, desactivamos los flips de x
LPE.DisableFlag("INVX");
//Cuando hemos acabado de pintar lo que queremos tener con el scroll activado, lo desactivamos.
LPE.DisableFlag("SCROLL");

sprintf(cad,"ITEMS:LEVEL%02d",level);
//Pintado de textos
LPE.DrawText("SYSTEM",200,350,cad);
}
};

#endif // __PlayState__

Si quereis ver el correcto funcionamiento podeis bajaros el código y el ejecutable de la zona de descargas y podéis ver los siguientes videos que os enseñarán el desarrollo de los primeros niveles del juego que estoy desarrollando. Aún no está implementado que los items tengan efectos (más allá del de pasar de nivel) pero en breve lo tendré listo.




Hay 5 videos más en el canal sobre este post.. no los pongo aquí para no saturar el blog....

Agradecería sobremanera que fuerais probando los niveles con vuestros PC ya que me da miedo tener los primeros problemas de rendimiento y me gustaría poder detectarlos para solventarlos cuanto antes. Si encontráis cualquier problema hacedmelo llegar.

Ya sin más os dejo que le echeis un ojo a los videos que hoy hay unos pocos...

LordPakusBlog
Nos vemos

0 comentarios :

Publicar un comentario

Entradas populares