miércoles, 13 de julio de 2011

GameEngine: Capitulo 19.Tile Manager

Hola a todos

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

En el último capitulo mostramos como rotar e invertir imagenes y animaciones, con lo que conseguiamos darle movimiento a nuestro personaje, pero... para que sirve que el personaje se mueva si no tiene donde ir?? Es por ello que el capitulo de hoy se encarga de explicar el TileManager, o lo que es lo mismo el gestor de escenarios 2D basados en reticulas(tilemaps).

Un tilemap no es más que un conjunto de gráficos "prefabricados" que nos ayuden a componer graficamente el escenario. Por internet encontrareis más información y ejemplos de lo que yo realmente os podria mostrar.

La función de nuestro TileManager no es mas que la de cargar los materiales ( basicamente gráficos y caracteristicas como si es atravesable o no ) que conformarán el tilemap y los niveles por donde jugaremos. Ahora por ahora hay imlpementada la carga sencilla de materiales, la carga de mapas y el pintado de estos, en entregas posteriores iremos puliendo este modulo para que nos acepte animaciones, físicas, scroll, etc...

Que hemos modificado??
  • Hemos generado un nuevo archivo de loading llamado LoadMaterials.txt que por ahora solo nos define que gráfico ya cargado está asociado a que material.
  • Hemos creado la carpeta levels donde iremos introduciendo los niveles de nuestro juego (el ejemplo que voy ha hacer va a ser un plataformero de disparos, ya os aviso)
  • Dentro de esta carpeta hemos creado un fichero llamado level01.txt que contiene la información del tilemap de nuestro ejemplillo.
111111111111111111
11______________11
11______________11
11______________11
11______________11
1133333_________11
11____________2211
11___________22211
11_________2222211
11______2222222211
11___2222222222211
111111111111111111

  • Código... hemos cambiado relativamente poco.. se ha incluido el moduglo TileManager y se ha cambiado el código de juego para cargar y pintar el mapa. Aparte de eso se ha ampliado el número de tareas gráficas a poder realizar por el Graphics2DEngine a 4000, ya que 256 tareas se habían quedado cortas para poder pintar todo el mapa.

TileManager.cpp (no ponemos el .h por que es lo de siempre):
/**************************************************************************************************/
// 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 <iostream> //Usada para imprimir por consola

using namespace std;

//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[])
{
int num_id = 0;
char cad[256]; //Cadena donde guardaremos cada linea que leamos
cout << "Inicializamos el tile manager\n";

filemanager.Init("Data\\LoadMaterials.txt");
map = NULL;

//Todo este código tendrá que ir al filemanager, pero por el momento lo dejamos aquí
//Leemos cada linea del archivo
while( filemanager.GetLine(cad) )
{
sprintf( loaded[ num_id ] ,"%s" , cad ); //Nos guardamos la referencia al recurso
cout << "Cargamos material " << cad << " con identificador " << num_id << "\n";
num_id++;
}
}

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

//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);
switch(tile)
{
//Material
case '1':
case '2':
case '3':
map[(j*map_width)+i] = tile-'0';
break;

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

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

//cout << "Iniciamos pintado de mapa\n";
for( i = 0 ; i < map_height ; i++ )
{
//cout << "Fila: " << i << "\n";
for( j = 0 ; j < map_width ; j++ )
{
tile = map[ (i*map_width + j) ];

//Si no tenemos tile, no pintamos nada
if(!tile)
continue;

tile--;
//cout << j << " ";
t = TextureManager::singleton().GetWidth(loaded[ tile ],0);
x = j*t;
y = i*t;
//cout << tile << " " << x << " " << y << " " << t << " " << loaded[ tile ] << "\n";
Core::singleton().DrawSprite(x,y,loaded[ tile],0);
}
//cout << "\n";
}
}

Código de juego:
//ARCHIVO DE JUEGO

#ifndef __PlayState__
#define __PlayState__

#include "MyGL.h"
#include <cstdlib>
#include <stdio.h>
#include <math.h>
#include "TileManager.h"

#define PI 3.14159265

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

public:

void enter()
{
TileManager::singleton().LoadMap("Data\\Levels\\level01.txt");
}

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

void draw ()
{
static bool right = true;
static int angle = 0;
int x,y;
TileManager::singleton().DrawMap(); //Pintamos el mapa cargado

if ( LPE.KeyBoardRight() )
right = true;

if ( LPE.KeyBoardLeft() )
right = false;

if ( right )
LPE.DisableFlag("INVX");
else
LPE.EnableFlag("INVX");
//Personaje
LPE.DrawAnim(300,260,"RUN");
//Pintamos el arma
LPE.GetMousePosition(&x,&y);
angle = atan2( (double)(y - 230), (double)( 290 - x) ) * 180/PI + 180;
angle %= 360;

if(right)
LPE.DrawCenterRotate(290,230,angle,"PLAYER",3);
else
LPE.DrawCenterRotate(240,230,angle,"PLAYER",3);

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

//Pintado de textos
LPE.DrawText("SYSTEM",200,350,"ANIMACIONES 2D");
}
};

#endif // __PlayState__

Si os fijais en el código de juego solo hacen falta dos lineas para gestionar el tilemap, una para cargar y otra para pintar, simplificando enormemente la creación de videojuegos. Todavía no he encapsulado el TileManager dentro del Core pero estoy esperando a nuevos cambios que me podrían afectar a esta parte, no os preocupeis que en próximas entregas ya lo tendremos arreglado.

En la siguiente imagen tenéis un ejemplo de como os debería quedar:
Ya para finalizar, recordad que el código y el ejecutable de ejemplo lo tenemos en la zona de descargas  y añadir que no os preocupeis ahora mismo por cosas como el rendimiento o que queda mejor o peor, esto son items que abordaremos en próximos capitulos.

Espero que os lo hayaís pasado bien y hayaís aprendido,

LordPakusBlog
nos vemosss

0 comentarios :

Publicar un comentario

Entradas populares