Mostrando entradas con la etiqueta physics engine. Mostrar todas las entradas
Mostrando entradas con la etiqueta physics engine. Mostrar todas las entradas

sábado, 16 de julio de 2011

GameEngine: Capitulo 21.Fisicas 2D

Hola a todos,

Bienvenidos a un nuevo capitulo de como crear tu propio motor de videojuegos...

En capitulos anteriores hemos visto como animar un personaje , como moverlo por la pantalla mediante teclado y raton , como pintar un tilemap) y como implementar el scroll. En el capitulo de hoy seguiremos la sucesión logica e implementaremos las fisicas 2D de nuestro juego, a fin de que nuestro personaje pueda chocar contra las paredes y saltar de plataforma en plataforma.

Las fisicas no se han implementado en un modulo aparte por que están intimamente ligadas al juego y al gestor de gráficos y animaciones. Hacer un modulo aparte tal vez estorbaria mas que otra cosa.

En esta entrega se han hecho unos cuantos cambios menores (por ejemplo se ha implementado un animación de espera, para que el personaje no estuviera todo el rato corriendo), pero el cambio fuerte ha sido en el modulo de animaciones 2D y al gestor de tiles a los cuales se les ha añadido unas funciones encargadas de calcular las colisiones entre la animación del personaje y el tilemap.

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

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

if (!tile)
continue;
tile--;

t = TextureManager::singleton().GetWidth(loaded[ tile ],0);
xt = j*t;
yt = i*t;
if( Anim2DManager::singleton().CollideWithGraph( x , y , id ,
xt , yt , loaded[tile] , 0) )
{
return true;
}
}
}
//Si hemos llegado hasta aqui es que no hemos colisionado con nada.
return false;
}

Anim2DManager:
bool Anim2DManager::CollideWithGraph(int xa, int ya, char cada[], int xg, int yg, char cadg[],int part)
{
int i;
int a = -1;

for ( i = 0 ; i < MAX_ELEMENTS_LOADED ; ++i ) //Para cada elemento cargado
{
if( !strcmp(loaded[i],cada) ) //Hemos encontrado la cadena que nos interesa
{
a = i ;
break;
}
}
for(i = 0 ; i < array_anim[a].frame[array_anim[a].actual_frame].num_elements ; ++i)
{
if ( LPE.GrafCollision( xa + array_anim[a].frame[array_anim[a].actual_frame].element[i].x ,
ya + 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,
xg,yg,cadg,part ) )
{
return true;
}
}
//Si hemos llegado hasta aquí es que no tenemos colisiones
return false;
}

Código en juego:
void draw ()
{
static bool right = true;
static int jump = 0;
static int angle = 0;
static int xplayer=200, yplayer = 260;
char anim[16];

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

TileManager::singleton().DrawMap(); //Pintamos el mapa cargado

LPE.Scroll(xplayer,yplayer);

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

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

if (!TileManager::singleton().CollideWithAnim(xplayer-5,yplayer,anim))
xplayer-=5;
if (!TileManager::singleton().CollideWithAnim(xplayer,yplayer+5,anim))
yplayer+=5;

if (!TileManager::singleton().CollideWithAnim(xplayer,yplayer-5,anim))
yplayer-=5;

}

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

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

}

if ( LPE.KeyBoardUp() && !jump)
{
//Si estamos tocando el suelo
if (TileManager::singleton().CollideWithAnim(xplayer,yplayer-5,anim))
{
jump = 25;
}
}
if(jump)
{
yplayer+=jump;
if (TileManager::singleton().CollideWithAnim(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;
yplayer-=5;
if (TileManager::singleton().CollideWithAnim(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");

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


En el siguiente video podréis ver el resultado, espero que os guste:


Recordad que en la carpeta de descargas podeis bajaros el código y el ejecutable ya compilado.
Ya sin más, deseo que os haya gustado el capitulo de hoy y que os lo hayaís pasado bien.

LordPakusBlog
Nos vemos

domingo, 3 de julio de 2011

GameEngine: Capitulo 15.Mejorando el Graphics2DManager (III)

Hola a todos,

Bienvenidos a un nuevo capitulo de como programar un game engine desde 0...

El capitulo de hoy es una nueva mejora del manager de gráficos 2D esta vez centrada en la carga y pintado de gráficos empaquetados.

Habreís visto en muchas ocasiones que los juegos 2D utilizan los gráficos empaquetados, es decir, que muchos gráficos pequeños se agrupan en un solo gráfico que posteriormente se particiona. Las causas que estos gráficos se empaqueten pueden ser varias pero las tres más importantes son:
  1. Gestión de gráficos (es más facil gestionar un par de gráficos que no 30)
  2. Aprovechamiento del espacio debido a que las texturas deben ser cuadradas.(se pueden poner múltiples gráficos no cuadrados en una sola textura cuadrada)
  3. Menor tamaño ocupado (si en vez de 30 gráficos tenemos 1, nos ahorramos 29 cabeceras de archivo)

A nivel personal, en la mayoria de juegos que he visto este particionamiento se hace hardcodeado en código con todos los problemas que esto conlleva. Mi propuesta se basa en un archivo de configuración de particición que elimina totalmente los hardcodeos. Los cambios para implementar esta propuesta son numerosos pero se pueden resumir en el siguiente código:

Cambios en el Graphics2DManager:

typedef struct
{
int type;
float Xi,Yi,Xf,Yf; //Coordenadas de textura
float xi,yi,xf,yf; //Coordenadas de pintado
char cad[256];
}Task2D;

void Graphics2DManager::Draw(float Xi, float Yi, float Xf, float Yf,float xi, float yi, float xf, float yf,char cad[])
{
TextureManager::singleton().Texture(cad); //Usamos la textura
glBegin(GL_QUADS);
glTexCoord2f( Xf, Yi); glVertex2i( xf , yf );
glTexCoord2f( Xi, Yi); glVertex2i( xi , yf );
glTexCoord2f( Xi, Yf); glVertex2i( xi , yi );
glTexCoord2f( Xf, Yf); glVertex2i( xf , yi );
glEnd();
}

//Pinta un gráfico con su tamaño original en las coordenadas x e y
void Graphics2DManager::InsertDraw(float x, float y, char cad[])
{
int w,h;

w = TextureManager::singleton().GetWidth(cad);
h = TextureManager::singleton().GetHeight(cad);

task2D[num_task2D].type = TYPE_DRAW ;

task2D[num_task2D].Xi = 0.0;
task2D[num_task2D].Yi = 0.0;
task2D[num_task2D].Xf = 1.0;
task2D[num_task2D].Yf = 1.0;

task2D[num_task2D].xi = x;
task2D[num_task2D].yi = y;
task2D[num_task2D].xf = (x+w);
task2D[num_task2D].yf = (y+h);

sprintf(task2D[num_task2D].cad,"%s",cad);

num_task2D++; //Incrementamos el número de tarea
}

//Pinta un gráfico partido con su tamaño original en las coordenadas x e y
void Graphics2DManager::InsertDrawPart(float x, float y, char cad[], int p)
{
int w,h;
float xi,yi,xf,yf;

w = TextureManager::singleton().GetWidth(cad,p);
h = TextureManager::singleton().GetHeight(cad,p);

TextureManager::singleton().GetBox(cad,p, &xi,&yi,&xf,&yf);
//cout << "Pintamos gráfico partido " << cad << " componentes: " << xi << yi << xf << yf << "\n";

task2D[num_task2D].type = TYPE_DRAW ;

task2D[num_task2D].Xi = xi;
task2D[num_task2D].Yi = yi;
task2D[num_task2D].Xf = xf;
task2D[num_task2D].Yf = yf;

task2D[num_task2D].xi = x;
task2D[num_task2D].yi = y;
task2D[num_task2D].xf = (x+w);
task2D[num_task2D].yf = (y+h);

sprintf(task2D[num_task2D].cad,"%s",cad);

num_task2D++; //Incrementamos el número de tarea
}

void Graphics2DManager::Render()
{
char cadFPS[50];
static int x,y;
int i;

glLoadIdentity(); // Reset The View
switchToOrtho(); //Pasamos a 2D
glEnable(GL_TEXTURE_2D);

//Habilitamos el alpha
glAlphaFunc(GL_GREATER, 0.05f);
glEnable(GL_ALPHA_TEST);

//Pintamos el ratón
InputManager::singleton().DrawMouse();

//Renderizamos las tareas de pintado. Se tendrá que mejorar
for( i = 0 ; i < num_task2D ; ++i)
{
switch(task2D[i].type)
{
case TYPE_DRAW:
Draw( task2D[i].Xi , task2D[i].Yi , task2D[i].Xf , task2D[i].Yf,
task2D[i].xi , task2D[i].yi , task2D[i].xf , task2D[i].yf ,
task2D[i].cad ); //Pintamos la tarea que nos han encomendado
break;

case TYPE_TEXT:
Text( task2D[i].xi , task2D[i].yi , task2D[i].cad ); //Pintamos los textos que nos han encomendado
break;
}
}
num_task2D = 0; //Ya hemos pintado todas las tareas, así que no nos queda ninguna por gestionar.


//Pintamos FPS
sprintf(cadFPS,"FPS: %d",CalcFPS());
FontManager::Print(10,10,cadFPS);


glDisable(GL_TEXTURE_2D);

switchBackToFrustum(); //Pasamos a 3D
}

El código de juego se podría resumir en :

Core::singleton().DrawSprite(xbola,ybola,"PACK",1);       //Primer gráfico empaquetado
Core::singleton().DrawSprite(xpala1,ypala1,"PACK",2);   //Segundo gráfico empaquetado
Core::singleton().DrawSprite(xpala2,ypala2,"PACK",2);  //Segundo gráfico empaquetado

El código lo tenéis en la carpeta de descargas , esta vez no he hecho video por que sería exactamente igual al anterior solo hemos introducido un cambio en la estructura de archivos que aunque es muy beneficiosa, no aporta mejoras gráficas de cara al usuario.

LordPakusBlog
Este capitulo de por si no es gran cosa pero nos abre las puertas a módulos tan interesantes como el gestor de físicas, animaciones y de fuentes bitmapeadas, por solo contar con los más importantes. En breves capitulos os dareis cuenta de su importancia.
Espero que hayais disfrutado y aprendido.. nos vemos

Entradas populares