Mostrando entradas con la etiqueta 2D graphics engine. Mostrar todas las entradas
Mostrando entradas con la etiqueta 2D graphics engine. Mostrar todas las entradas

miércoles, 1 de febrero de 2012

Inicio de proyecto : "Motor Gráfico 2D: Arnow"

Artículo perteneciente a la sección de motores gráficos

Hola a todos,

Este post es para anunciaros que en breve empezaré un modesto motor 2D en C mediante OpenGL.

El motivo de este motor es hacer de puente entre los que están aprendiendo a programar mediante http://lordpakus.blogspot.com/p/tutorial-de-programacion-cc-desde-0.html y los conocimientos necesarios para crear un game engine como el que empecé a explicar y desarollar : http://lordpakus.blogspot.com/p/lordpakus-game-engine.html

Realmente, la idea inicial era crear un sencillo tutorial sobre como instalar y usar alguna libreria 2D más o menos conocida como Allegro o SDL pero me dí cuenta que crear un tutorial que tuviera en cuenta todos los casos de instalación podía ser una tarea un poco más complicada de lo que yo desearía.

Así pues, en  muy breve empezaré con el motor gráfico 2D. Lo programaré en C para que la sintaxis de C++ no nos suponga un problema añadido (si el proyecto sigue adelante lo reimplementaré mediante C++ que tampoco será tan complicado) y el pintado de 2D se hará mediante OpenGL, de forma muy parecida a como implementé el modulo de 2D dentro del LPE.

La idea es que al no tener todo lo que envuelve al game engine, el motor gráfico se pueda entender de forma sencilla y pueda ser lo suficientemente sencillo de usar como para que os animéis ha hacer vuestros primeros juegecillos.

Espero vuestras opiniones y sugerencias.

Nos vemos


LordPakusBlog

viernes, 15 de julio de 2011

GameEngine: Capitulo 20.Scroll

Hola a todos,

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

El capitulo de hoy trata sobre el scroll.

  • Que es el scroll? A grandes rasgos puede decirse que el scroll es el desplazamiento del fondo del escenario que nos permite tener la ilusión que el personaje se va moviendo. Es decir, que podemos dejar el personaje pintandose justo en el centro de la pantalla y que lo que se vaya moviendo sea el fondo.
  • Como lo hemos implementado? Basicamente se han introducido dos variables nuevas dentro del Graphics2DManager llamadas scroll_x y scroll_y que se utilizan como coordenadas de referencia para todos los gráficos en los que aplicamos el scroll. Estas variables las podemos setear desde el código del juego (por ejemplo, forzando que el personaje esté en el centro de la pantalla) . Estas variables se suman a las coordenadas de todos los gráficos y esto hace que se genere la sensación de movimiento. A fin de indicar que gráficos queremos que tengan scroll y que gráficos no, hemo usado el sistema de flags que ya se utilizó hace pocos capitulos para marcar la inversión en x.
  • Algo más a tener cuenta? Pues si, hemos creado una función nueva que se encarga de calcular el angulo entre un punto que definimos y el puntero del ratón. Esta función ya tiene en cuenta el scroll y tiene la gracia que en la mayoria de juegos 2D será necesaria.

A nivel de código, los cambios son ridiculos:

En el código de juego:
void draw ()
{
static bool right = true;
static int angle = 0;
static int xplayer=300, yplayer = 260;

//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);

if ( LPE.KeyBoardRight() )
{
xplayer++;
right = true;
}

if ( LPE.KeyBoardLeft() )
{
xplayer--;
right = false;
}

if ( LPE.KeyBoardUp() )
{
yplayer++;
}

if ( LPE.KeyBoardDown() )
{
yplayer--;
}

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

if( angle < 0.1 ) angle = 0.1;

//Personaje
LPE.DrawAnim(xplayer,yplayer,"RUN");

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,"ANIMACIONES 2D");
}


Si os fijais en el video que hay abajo el texto "ANIMACIONES 2D" se mantiene estatico en pantalla ya que se pinta despues de hacer el disable del SCROLL.

Este sistema de flag nos permite tener muchisimas menos funciones a las que acceder para dibujar nuestros juegos.Con el tiempo le iremos viendo más utilidades.

A nivel de código de motor solo pondré un par de ejemplos ya que el resto es copiar y pegar:
Dentro del Graphics2DManager:

//Calcula el angulo existente entre el punto dado y el ratón (bastante util en juegos de disparos 2D)
float Graphics2DManager::AngleToMouse(int xp, int yp)
{
int angle;
int x,y;
LPE.GetMousePosition(&x,&y);
angle = atan2( (double)( (yp+scroll_y) - y), (double)( x - (xp+scroll_x)) ) * 180/PI + 360;
angle %= 360;

return (float) angle;

}

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

if(flags_scroll)
{
x += scroll_x;
y += scroll_y;
}

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

TextureManager::singleton().GetBox(cad,p, &xi,&yi,&xf,&yf);
task2D[num_task2D].type = TYPE_DRAW ;
task2D[num_task2D].invx = flags_invx;
task2D[num_task2D].scroll = flags_scroll;
task2D[num_task2D].angle = 0;
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
}

Fijaos que las funciones de pintado como tal no se han tenido que modificar , solo se ha tenido que tocar las funciones de inserción de tareas.

Ya visto todo este código solo nos queda mirar el video de como ha quedado el resultado. El sistema de físicas todavia no está implementado, pero si todo va bien en breve tendremos unas fisicas 2D sencillitas implementadas:


Os habeis dado cuenta que todo este video es el trabajo de solo 24 lineas de código dentro del juego?? :D

LordPakusBlog
Espero que hayais disfrutado y aprendido... nos vemos

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

Entradas populares