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,
nos vemosss