miércoles, 6 de julio de 2011

GameEngine: Capitulo 17. Anim2DManager

Hola a todos

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

El capitulo de hoy trata sobre la gestión de animaciones 2D, más concretamente de la creación del Anim2DManager, siendo este casi casi el último modulo que implementaremos por ahora para la parte de 2D.

El funcionamiento de este nuevo manager es similiar al de texturas http://lordpakus.blogspot.com/2011/06/gameengine-capitulo-6.html y se basa en el uso de los gráficos empaquetados que mostramos en http://lordpakus.blogspot.com/2011/07/gameengine-capitulo-15mejorando-el.html.

A nivel de recursos se ha creado un fichero txt de loading llamado LoadAnim.txt que es el que se encarga de cargar todas las animaciones, así como se ha creado una carpeta de animaciones llamada Anim, que contendrá los ficheros de la animación como tal.

El formato de los ficheros de animación es el siguiente (parte es el indice de gráfico empaquetado):
frame x y parte grafico
frame x y parte grafico
frame x y parte grafico
frame x y parte grafico

Aquí tenemos un ejemplo:
0 0 -60 1 PLAYER
0 0 0 2 PLAYER
0 -5 -40 3 PLAYER
0 10 -60 4 PLAYER
1 0 -55 1 PLAYER
1 3 -1 2 PLAYER
1 -6 -41 3 PLAYER
1 10 -60 5 PLAYER
2 0 -50 1 PLAYER
2 5 -2 2 PLAYER
2 -7 -42 3 PLAYER
2 10 -60 6 PLAYER
3 0 -55 1 PLAYER
3 2 -1 2 PLAYER
3 -6 -41 3 PLAYER
3 10 -60 7 PLAYER

A nivel de código hemos creado un nuevo manager llamado Anim2DManager que pertenece al entorno del Graphics2DManager. El código es poco y sencillo de entender:

/**************************************************************************************************/
// 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
/**************************************************************************************************/

/**************************************************************************************************/
// Anim2DManager.cpp : Código del gestor de animaciones del game engine
/**************************************************************************************************/
#include "Anim2DManager.h"
#include "MyGL.h"

#include <iostream> //Usada para imprimir por consola
#include <ctime> //Funciones de tiempo
using namespace std;


//Estructura que almacena una componente de un frame
typedef struct
{
int x; //Coordenada x.
int y; //Coordenada y.
int part; //Indice de gráfico empaquetado.
char cad[32]; //Cadena que contiene el identificador de gráfico.
}TAnimElement;

//Estructura que almacen un frame de animación
typedef struct
{
int num_elements; //Número de elementos que componen este frame
TAnimElement element[16];//HARDCODEO!!! Por ahora 16, en un futuro se arreglará.
}TFrame;

//Estructura que almacena la información de la animación
typedef struct
{
int actual_frame;
clock_t timer;
int num_frame;
TFrame frame[16];//HARDCODEO!!! Por ahora 16, en un futuro se arreglará.
}TAnim;

TAnim array_anim[MAX_ELEMENTS_LOADED]; //Array of texture ID.
int num_anims; //Number of images loaded.

//Instancia única del sound manager
Anim2DManager Anim2DManager::instance;

Anim2DManager::Anim2DManager()
{
}

Anim2DManager::~Anim2DManager()
{
}

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

//Sounds functions
void Anim2DManager::Init(int* argc, char* argv[])
{
char cad[256]; //Cadena donde guardaremos cada linea que leamos
char id[256]; //Cadena donde guardaremos el identificador de cada recurso
char ruta[256]; //Cadena donde guardaremos la ruta de cada recurso
int num_id; //número que nos asigna la función de carga del recurso.... un -1 significa que no lo ha podido cargar
int i,j;

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

//alutInit(argc, argv) ;

filemanager.Init("Data\\LoadAnim.txt");

for ( i = 0 ; i < MAX_ELEMENTS_LOADED; ++i )
{
array_anim[i].num_frame = 0;
array_anim[i].actual_frame = 0;

for( j = 0 ; j < 16 ; ++j)
array_anim[i].frame[j].num_elements = 0;
}

//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) )
{
for( int i = 0 ; i < strlen(cad); ++i)
{
if ( cad[i] == '=' )
{
cad[i]='\0';
sprintf(id,"%s",cad);
sprintf(ruta,"%s",&cad[i+1]);
i = 257;
}
}

num_id = LoadAnim(ruta); //Cargamos el recurso
if (num_id == -1) //Si el recurso no se ha podido cargar, vamos a cargar el siguiente recurso
continue;

cout << "Animacion cargada con identificadores: " << id << " " << num_id << "\n";

sprintf( loaded[ num_id ] ,"%s" , id ); //Nos guardamos la referencia al recurso
}
}

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


//Carga el sonido cad y devuelve su posición en la que se ha guardado
int Anim2DManager::LoadAnim( char cad[] )
{
int max_frame = -1;
int f,x,y,p;
char str[32];
FILE *fp;

fp = fopen(cad,"r");

while(!feof(fp))
{
fscanf(fp,"%d %d %d %d %s\n",&f,&x,&y,&p,str); //Leemos la información del elemento a pintar

//cout << "Frame: " << f << " x: " << x << " y: " << y << " p: " << p << " Grafico: " << cad << "\n";

array_anim[num_anims].frame[f].element[array_anim[num_anims].frame[f].num_elements].x = x;
array_anim[num_anims].frame[f].element[array_anim[num_anims].frame[f].num_elements].y = y;
array_anim[num_anims].frame[f].element[array_anim[num_anims].frame[f].num_elements].part = p;
sprintf(array_anim[num_anims].frame[f].element[array_anim[num_anims].frame[f].num_elements].cad,"%s",str);
array_anim[num_anims].frame[f].num_elements++;
if (f > max_frame)
max_frame = f;
}

fclose(fp);

array_anim[num_anims].num_frame = max_frame + 1;

num_anims++;
return num_anims-1;
}

void Anim2DManager::Play(int x, int y, char cad[])
{
int i;
int a = -1;

for ( i = 0 ; i < MAX_ELEMENTS_LOADED ; ++i ) //Para cada elemento cargado
{
if( !strcmp(loaded[i],cad) ) //Hemos encontrado la cadena que nos interesa
{
a = i ;
break;
}
}
if( ( clock() - array_anim[a].timer ) > 83 )
{
array_anim[a].timer = clock();
array_anim[a].actual_frame++; //Por defecto los FPS de animación serán 12 (como en FLASH)
array_anim[a].actual_frame %= array_anim[a].num_frame; //Por defecto la animación será ciclica
}

for(i = 0 ; i < array_anim[a].frame[array_anim[a].actual_frame].num_elements ; ++i)
{
/*cout << "Frame: " << array_anim[a].actual_frame <<
" element: " << i <<
" x: " << x + array_anim[a].frame[array_anim[a].actual_frame].element[i].x <<
" y: " << y + array_anim[a].frame[array_anim[a].actual_frame].element[i].y <<
" grafico: " << array_anim[a].frame[array_anim[a].actual_frame].element[i].cad <<
" parte: " << array_anim[a].frame[array_anim[a].actual_frame].element[i].part << "\n";
*/
Core::singleton().DrawSprite( x + array_anim[a].frame[array_anim[a].actual_frame].element[i].x ,
y + array_anim[a].frame[array_anim[a].actual_frame].element[i].y ,
array_anim[a].frame[array_anim[a].actual_frame].element[i].cad,
array_anim[a].frame[array_anim[a].actual_frame].element[i].part);
}
}

No he copiado ni el .h (es casi idéntico al del resto de managers) ni las funciones de wrapper que permiten que se llame al pintado de animación desde la clase Core. (quien quiera verlas está el proyecto subido a la carpeta del megaupload)

A nivel de código en juego nos queda una cosa tan sencilla como:

Core::singleton().DrawAnim(200,260,"RUN");

Donde RUN es una animación que hemos cargado previamente y 200,260 las coordenadas donde queremos pintarla.

A modo de ejemplo he montado una animación muy sencillita con los gráficos del siguiente spritesheet:

El video del resultado es el siguiente:


Espero que os lo hagáis pasado tan bien como yo y que hayáis aprendido. Recordad que si teneis dudas o sugerencias poned un comentario o enviad un email aquí

LordPakusBlog
Nos vemos

0 comentarios :

Publicar un comentario

Entradas populares