jueves, 6 de octubre de 2011

Curso de programación: Capítulo 14. Patrones de diseño


Hola a todos,

Antes que nada mis disculpas por tardar tanto en escribir este tutorial pero he tenido unos dias liadillo...

Algunas veces oireis hablar de ellos, pero...que es un patrón de diseño??
Pues bien, un patrón de diseño no es más que una manera de hacer las cosas que ha sido diseñado, probado y utilizado hace mucho tiempo y funciona. Dicho así me direis que os he explicado poco...:D, perfilemos más la explicación: En la mayoria de las ocasiones que nos enfrentamos a un problema, estamos repetiendo trabajo que antes ya han hecho otros, tanto a nivel de que se nos ocurra como implementarlo a la misma implementación en si (y los bugs que conlleve).

Así pues, los patrones de diseño nos permiten reaprovechar de una forma estructurada, conocimientos y planteamientos de programación que hace ya muchos años un grupo de estudiosos investigó,probó e implementó. Acostumbran a ser muy genericos y resuelven gran cantidad de problemas. Si no recuerdo mal hay 23 patrones de diseño "estandarizados" pero a nivel de concepto se podrían añadir cientos de nuevos patrones sin ningún problema.

Ejemplos:
- Singleton: Este no lo explico :D. Ya se ha usado mucho en el game engine. Quien tenga dudas que las comente sin problemas. Para quien no haya visto los capitulos del game engine: un singleton no es más que una manera de conseguir que una clase solamente genere un único objeto. Es decir, que por muchos constructores que llamemos siempre tendremos el mismo objeto (conservando su estado, lógicamente)

- Factory Method: Es una clase que se encarga de devolver objetos de clases diferentes... por ejemplo. Podriamos tener una clase modelfactory con una función LoadModel y que te devolviera diferentes tipos de objeto en función del formato del modelo (MD2,MD3,ASC,etc...). Útil en el game engine para gestionar los recursos internamente.

- Proxy: No es mas que un "encapsulamiento" de un objeto existente. Es decir, le decimos a la clase proxy que queremos cambiar X atributo y esta clase es la que se encarga de ir al objeto real y cambiarle el atributo X. Es muy util para encapsular objetos que se modificarán por red, de esta manera todo funcionará bien, ya trabajes con red o sin ella. Un ejempo de uso lo podeis encontrar en el antiguo Core.h del game engine ya que se encargaba de hacer de wrapper de las funciones de pintado y audio.

- Iterator: No es mas que la implementación de iterador de C++. Es decir un objeto que se usa para recorrer una lista o un vector.

- Observer: Este patrón de diseño permite que varios objetos puedan observar las caracteristicas y estado de un tercero. Se me hace complicado presentaros un ejemplo claro, solo se que lo he usado en ocasiones y va muy bien, pero ahora no caigo en que.

No quiero explicar más por que si no os liaré más que otra cosa. De todas maneras, si teneis dudas o quereis más información decidlo y ahondaré más donde digais.

Aparte de todo esto, me han pedido que el próximo tutorial sea sobre mates 3D. Recordad que si teneis ideas originales o si quereis un tutorial en especial , pedidlo y se mirará si se puede conseguir.

Nos vemos

P.D: Si os interesa este tema os recomiendo que consigáis estos libros sobre patrones de diseño

LordPakusBlog

lunes, 26 de septiembre de 2011

LPGameEngine: Capitulo 1 . SoundManager & ResourceManager

Hola a todos,

Este capítulo vuelve parcialmente obsoleto este otro e implementa lo que se dijo en su momento de un filemanager. Se puede aprender igualmente de él pero ya no cumple las condiciones de diseño actuales.

El cambio de este módulo proviene de dos factores:
- La gestión de recursos por un módulo centralizado (resource manager)
- La posibilidad de implementar varios managers de sonido de forma trivial

La forma de implementar estos cambios se ha realizado gracias al aprovechamiento de la herencia y las clases abstractas de C++. A grandes rasgos, para el que no sepa de que va el tema: herencia es que un clase deriva de otra (tiene características en común)  y clase abstracta significa que la clase madre no sirve para nada más que para tener subclases (de por si no tiene implementación).

Gráficamente podemos observar que la clase SoundManager es la madre de las diferentes clases que se encargan de gestionar el sonido:






Este tipo de estructura hace que en el Core.h no se defina un objeto soundmanager sino un puntero:  SoundManager*    pSound;        //Puntero al módulo gestor de sonido.

Dicho puntero podrá ser rellenado con una dirección a un objeto de cualquiera de los hijos de SoundManager... por ejemplo, dentro del resourceManager, se escoge que manager utilizar y se setea el puntero convenientemente:

void ResourceManager::GestSoundManager(void)
{
//Seteamos puntero

cout << "Sound Manager Driver : " << sound << "\n"; 
if( !sound.compare("NONE") )  {      cout << "SOUND:NONE\n"; Core::singleton().SetPointerSoundManager( NULL );      return;  } if( !sound.compare("OpenAL") )  {      cout << "SOUND:OpenAL\n";      Core::singleton().SetPointerSoundManager( (SoundManager *) new            SoundManager_OpenAL() ) ;      return; if( !sound.compare("FMOD") )          cout << "SOUND:FMOD\n";          Core::singleton().SetPointerSoundManager( (SoundManager *) new SoundManager_FMOD() ) ;          return; } if( !sound.compare("BASS") )          cout << "SOUND:BASS\n";         Core::singleton().SetPointerSoundManager( (SoundManager *) new SoundManager_BASS() ) ;          return;  } //AQUI IRÁN NUEVOS MANAGERS DE SONIDO SI HACEN FALTA. }
Posteriormente se podrá acceder al manager de sonido de esta manera sin importarnos que gestor de sónido tenemos por debajo:
psoundmanager = (SoundManager *) Core::singleton().GetPointerSoundManager();

    //Cargamos sonidos
    psoundmanager->PreLoad();

    //if (!LoadFile( soundroute , loadedSound , psoundmanager->Load ) )
    //    return false;

    TiXmlElement *root = doc.RootElement();
    for(TiXmlElement* element = root->FirstChildElement(); element; element = element->NextSiblingElement()) 
    {
        //Leemos el elemento
        id = element->FirstChild("ID")->FirstChild()->Value();            //tomamos el ID
        ruta = element->FirstChild("ROUTE")->FirstChild()->Value();    //tomamos la RUTA   
       
        //Cargamos el recurso
        loadedSound[id] = psoundmanager->Load(ruta);

        cout << "Sonido: " << id << ruta << "\n";
    }

    psoundmanager->PostLoad();

Ya que vemos este ejemplo hemos de comentar tambíen el gran cambio que se ha producido ahora y es que toda la gestión de los ficheros de recursos (configuraciones XML, audio, texturas, etc... ) pasarán por el resourcemanager que se encargará de gestionar dichos recursos.

La forma estandar de gestionar la carga será mediante la estructura
pmanager->PreLoad(); //Acciones a realizar antes de la carga (inicializaciones)
balbalballbal
pmanager->Load();  //Todo manager tendrá que tener una función de load de 1 solo recuros. El resource manager se encargará de cargar uno a uno todos los recursos.
balbalblablalb
pmanager->PostLoad(); //Acciones a realizar despues de la carga (desinicializaciones)

Todos los managers tendrán que utilizar esta estructura para poder interactuar con el resourcemanager.


Ya finalmente, por si os quereis animar a implementar FMOD y BASS os dejo la implementación con OpenAL para que veais que es trivial implementar nuevos módulos (el código completo lo podeis obtener del repositorio)

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

/**************************************************************************************************/
// Implementación del SoundManager con OpenAL
/**************************************************************************************************/
#include "SoundManager_OpenAL.h"

#include <iostream>        //Usada para imprimir por consola

using namespace std;

SoundManager_OpenAL::SoundManager_OpenAL()
{
}

SoundManager_OpenAL::~SoundManager_OpenAL()
{
}

//Sounds functions
void SoundManager_OpenAL::Init(int* argc, char* argv[])
{
    cout << "Inicializamos el Sound Manager implementado con OpenAL\n";

    alutInit(argc, argv) ;

    num_element = 0;

}

void SoundManager_OpenAL::DeInit()
{
    cout << "Desinicializamos el Sound Manager implementado con OpenAL\n";
    alutExit();
}

//Todo aquello que se ha de hacer antes de cargar los sonidos
void SoundManager_OpenAL::PreLoad()
{
    cout << "Preparamos buffers de sonido\n";
    alGenBuffers(MAX_SOUNDS,buffer);
}

//Todo aquello que se ha de hacer después de cargar los sonidos
void SoundManager_OpenAL::PostLoad()
{
    cout << "Generamos fuentes de sonido\n";
    alGenSources(MAX_SOUNDS, source);
}

//Función para cargar un elemento con la ruta que se le indica
int SoundManager_OpenAL::Load(string route)
{
    buffer[num_element++]  = alutCreateBufferFromFile ((char *)route.c_str());

    if(num_element >= MAX_SOUNDS)
        return -1;

    return (num_element-1);
}

void SoundManager_OpenAL::Play(int id)
{
  static unsigned int s = 0;
  
  alSourcei (source[s], AL_BUFFER, buffer[id]);
  alSourcePlay (source[s]);

  if(++s == MAX_SOUNDS)
    s = 0;
}
Espero que os haya gustado y hayais disfrutado.

Nos vemos

LordPakusBlog

LPGameEngine: Capitulo 0 . Reescribiendo el motor

Hola a todos

Antes que nada, disculpad la tardanza en postear, pero entre el niño y los númerosos cambios que estamos teniendo en la estructura del motor de juego me ha sido imposible postear antes...

Si os fijais vereis que la etiqueta que he puesto es la de LPGameEngine (por contra de la que utilizaba hasta ahora de GameEngine) y que la numeración la he empezado desde el principio... NO, no me he vuelto loco, al menos no más de lo que ya estaba en su momento :D.

Este cambio es debido a que todo el motor lo estoy reescribiendo desde 0 (aprovechando todo lo que puedo de lo que ya tenia hecho) a fin de obtener un proyecto más mantenible. Había llegado a un punto en que me resultaba imposible incluir más código sin que me petara todo.

Coincidiendo con la reescritura del motor, killrazor ha empezado a colaborar con el furor de un adolescente hormonado (y eso que me consta que la adolescencia le queda lejos :D ) y ha propuesto numerosos cambios en la estructura organizativa del proyecto que me han parecido muy acertadas:
1. Tenemos página del googlecode: http://code.google.com/p/lpgameengine/ Es decir, tenemos repositorio donde dejar el código. No lo volveré a dejar en el megaupload con los numerosos problemas que había. Si quereis saber como bajaros el proyecto echadle un ojo aquí y postead pidiendo ayuda si teneís problemas
2. Tenemos grupo de googlegrouphs: http://groups.google.com/group/lpgameengine . El blog seguirá sirviendo para postear los tutoriales y las noticias y el grupo servirá para plantear los problemas y los dilemas del desarollo del engine. Hechadle un ojo, vereis que tenemos temas interesantes abiertos.

Como temas interesantes adicionales tenemos que usaremos la libreria BOOST para el desarollo del proyecto (a dia de hoy la encuesta es bastante favorable a su uso) que no es más que una especie de C++++ :). kill tiene tutoriales en su blog.

Los capitulos que vaya posteando bajo esta etiqueta serán los que aporten nueva información al proyecto. No me dedicaré a repostear lo que hice en su momento en el motor original.

Aparte de todo esto, el grupo de trabajo que estamos ahora nos dedicaremos a tener un motor funcional, esto quiere decir que habrá muchos flecos abiertos para que el resto de la comunidad pueda ir rellenandolos si quiere (nuevos managers, mejora de los existentes, "nuevos drivers")

Sin más, ahora os subiré el capitulo del sound mananger.

Espero que os guste, que aprendais y que querrais participar.

Nos vemos

LordPakusBlog

miércoles, 21 de septiembre de 2011

Curso de programación: Capítulo 12. Repositorios


Hola a todos...

Una cosa que en muchas ocasiones nos pilla desprevenidos en nuestro primer trabajo (se entiende que de programador) es el concepto de repositorio. A mi particularmente no me lo explicaron nunca en ninguna clase y tal vez es la cosa que más uso en mi dia a dia laboral.

Un repositorio es un lugar "virtual" donde guardamos gran cantidad de información susceptible de ser vista y tocada por mucha gente; en nuestro caso, proyectos de software. Es decir, vendria a ser algo así como un disco duro que está en un servidor donde copiamos nuestros archivos para que los demás los vean y de donde podemos bajarnos las cosas de las demás.

Hay muchos protocolos de repositorio (SVN,CVS,etc..) pero uno de los que más se usa  es el SVN. Para acceder a un repositorio se ha de tener un cliente de repositorio(un programa de gestión), nuevamente, hay muchos clientes, yo el que uso normalmente en casa es el TortoiseSVN . Vosotros usad el que queraís, hay muchos y lo importante al final es trabajar a gusto.

Vale, ok, ya se más o menos que es un repositorio y tengo el software para conectarme a el, y ahora que?. Pues nos conectamos al repositorio. Depende del cliente, pero normalmente para conectarte a un repostorio se ha de crear una carpeta vacia en nuestro disco duro , darle al botón derecho y darle a algo del tipo "CheckOut". al hacer esto nos pedirá la dirección del repositorio y usuario y password si es un repositorio restringido. Una vez hecho esto nos bajará el proyecto a nuestra carpeta.

Que acciones básicas se pueden realizar con el repositotio?
- CheckOut: Bajarte el proyecto del servidor a tu ordenador
- Update: Una vez el proyecto está bajado, podemos ir actualizando los últimos cambios que suben el resto de usuarios sin necesidad de bajar todo el proyecto.
- Commit: subir el proyecto al repositorio
- Compare: para comparar código del servidor con lo que tenemos
- revert: para eliminar nuestros cambios locales y quedarnos como al principio
- syncronizar: para gestionar multiples subidas desde diferentes ordenadores
- branch/tag : para generar diferentes versiones del proyecto
- switch: para cambiar de un versión a otra del proyecto

Estas acostumbran a ser las comunes, despues ya cada cliente puede montarselo de una manera u otra.


Que ventajas nos da usar un repositorio??
Si programas solo en casa, en proyectos de 1000 lineas de código, no te aconsejaria que usaras un repositorio, pero si trabajaras en un grupo de 50 personas distribuidas por todo el mundo con un código de 500k lineas , tu mismo verias que pasarse los cambios por mail no sería la mejor opción. :D
Aparte de la ventaja obvia de tener todo el código centralizado tenemos un conjunto de características que nos pueden resultar muy provechosas:
- Permisos: Hay una figura de administrador de proyecto que es el que da y quita permisos, esto quiere decir que no todo el mundo tiene por que poder hacer checkout ni mucho menos commit, reduciendo las subidas a un grupo de programadores experimentados (por ejemplo)
- Branches: el repositorio te permite crear diferentes versiones de tu código que pueden evolucionar por separado, por ejemplo : versión Linux , versión Windows , versión MAC y que haya diferentes grupos que trabajen en cada branch. Posteriormente estos branchs se pueden volver a juntar o no (por ejemplo, cuando es un branch de test)
- Comparación de código: Por defecto el repositorio guarda solamente las diferencias entre un commit y otro, esto significa que en todo momento se puede saber quien subió qué código , cuando y por que. si se detecta un fallo que apareció a partir de tal fecha se hace mucho más sencillo descubrir la raíz del problema.
- Revisión de código: La organización del repositorio está pensada para que la comunidad o un subgrupo de esta tenga que dar el visto bueno a un conjunto de cambios para que dichos cambios se hagan efectivos en la versión final. Esto en el fondo no es más que 100 ojos ven más que 2.

Ok, ya lo sé casi todo sobre repositorios, como me monto uno?? Pues la verdad, la mejor manera para mi gusto es lo que me recomendó killrazor en su momento : google code. No me meteré en como se gestiona, pero no es complicado y hay númerosos tutoriales por internet que lo explicarán mucho mejor que yo.

Finalmente, el resumen para los vagos que no quieren leerse toda esta parrafada :D
1. Bajate el tortoiseSVN
2. Crea un carpeta vacia en tu disco duro
3.Botón derecho SVN Checkout
4. Copia esto: https://lpgameengine.googlecode.com/svn/trunk/ donde te dice "URL of repository"
5. Dale al ok.

Si todo va bien se te tendría que bajar el proyecto, si no es así dimelo y lo miraremos.

Cualquier cosa que creais que falte, decidmelo y la incluiré o la explicaré de otra manera.

martes, 20 de septiembre de 2011

Game engine: The Next Generation :D

Hola a todos

Como supongo que más de uno estaria esperando la reestructuración del game engine va por buen camino.


Características que ya tenemos implementadas:
0. Tal y como kill me ha recomendado, estoy poniendo el proyecto en el repositorio svn del google code. El problema es que no me resulta demasiado intuitivo y me pierdo para subir todo el proyecto, solo me deja subir de archivo en archivo. Alguien sabe como hacerlo para subir todo el proyecto de golpe??

1. La clase core contiene todo el game engine (principalmente los managers)

2. Se ha creado el Resource Manager que es el encargado de tratar con todos los ficheros del game engine. A la espera de cierto código prometido por killrazor :D. De mientras usamos este módulo para cargar el archivo de configuración general del game engine. Aquí teneís un ejemplo de por donde irán los tiros:
<conf>
  <InputManager>Glut</InputManager>
  <SoundManager>OpenAL</SoundManager>
  <Graphics2DManager>OpenGL</Graphics2DManager>
  <Graphics3DManager>OpenGL</Graphics3DManager>
  <GraphicsASCIIManager>OpenGL</GraphicsASCIIManager>
</conf>

3. Se ha aumentado el nivel de exigencia del sistema. Si algo no funciona como debiera se aborta la carga del motor. Así evitamos problemas posteriores. Esto significa que todas las funciones de Init de los managers devolverán un bool diciendonos si han cargado bien o no.

4. Se ha facilitado mucho la posibilidad de  usar nuevas librerias para cosas ya hechas. Es decir, cambiar la libreria de sonido de OpenAL a BASS o FMOD es trivial. Esto se ha conseguido gracias a la herencia de C++. El core contiene un puntero generico a un manager (SoundManager por ejemplo) y el ResourceManager, en función de la configuración del game engine , se encargará de setearle un puntero a SoundManager_OpenAL o SoundManager_BASS por ejemplo. Mientras que las funciones coincidan en parametros y funcionamiento todo funcionará correctamente.

El código que hay por ahora del ResourceManager (solo es un ejemplo) es el siguiente
/**************************************************************************************************/
//        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
/**************************************************************************************************/

/**************************************************************************************************/
// ResourceManager.h : Código de la parte de recursos del game engine
/**************************************************************************************************/
#include "ResourceManager.h"

//Lista de managers
#include "Core.h"
#include "SoundManager_OpenAL.h"


#include "tinyxml.h"    //Usada para parsear XML

#include <iostream>        //Usada para imprimir por consola
#include <stdio.h>        //Usada par gestionar ficheros

using namespace std;

ResourceManager::ResourceManager()
{
}

ResourceManager::~ResourceManager()
{
}

//Nos devuelve si ha podido cargar adecuadamente o no.
bool ResourceManager::Init(char *route)
{
    cout << "Inicializamos el Resource manager\n";
   
    TiXmlDocument doc( route);

    //Si el archivo de configuración no existe, abortamos la carga
    if ( !doc.LoadFile() )
    {
        printf( "Could not load file %s. Error = %s. Exiting.\n", route,doc.ErrorDesc() );
        return false;
    }
    cout << "Cargamos el archivo de configuracion " << route << "\n";

    //Empezamos a leer el archivo por el principio
    TiXmlElement *root = doc.RootElement();

    //Cargamos los diferentes managers
    sound = root->FirstChild("SoundManager")->FirstChild()->Value() ;
    input = root->FirstChild("InputManager")->FirstChild()->Value() ;
    graphics2D = root->FirstChild("Graphics2DManager")->FirstChild()->Value() ;
    graphics3D = root->FirstChild("Graphics3DManager")->FirstChild()->Value() ;
    graphicsASCII = root->FirstChild("GraphicsASCIIManager")->FirstChild()->Value() ;

    //Gestionamos los managers
    GestManagers();

    //Si hemos llegado aquí es que hemos podido cargar todo adecuadamente.
    return true;
}

void ResourceManager::DeInit()
{
    cout << "Desinicializamos el Resource manager\n";
}



//Funciones internas del resourcemanager

//Nos dice si un fichero existe o no
bool ResourceManager::ExistFile(char *route)
{
    FILE *fp;

    fp = fopen(route,"r");

    if( fp )
    {
        fclose(fp);
        return true;
    }

    return false;
}

void ResourceManager::GestSoundManager(void)
{
    //Seteamos puntero

    cout << "Sound Manager Driver : " << sound << "\n";
    if( sound.compare("OpenAL") )
    {
        Core::singleton().SetPunteroSoundManager( (SoundManager *) new SoundManager_OpenAL() ) ;
        return;
    }

    //Aquí irian las lineas encargadas de setear los diferntes managers de sonido: ejemplo:
    /*
    if( sound.compare("FMOD") )
    {
        Core::singleton().SetPunteroSoundManager( (SoundManager *) new SoundManager_FMOD() ) ;
        return;
    }

    if( sound.compare("BASS") )
    {
        Core::singleton().SetPunteroSoundManager( (SoundManager *) new SoundManager_BASS() ) ;
        return;
    }
    */
}

void ResourceManager::GestInputManager(void)
{
    cout << "Input Manager Driver : " << input << "\n";
}

void ResourceManager::GestGraphicsManager(void)
{
    cout << "Graphics2D Manager Driver : " << graphics2D << "\n";
    cout << "Graphics3D Manager Driver : " << graphics3D << "\n";
    cout << "GraphicsASCII Manager Driver : " << graphicsASCII << "\n";
}

//Gestionamos la carga de managers
void ResourceManager::GestManagers()
{
    //Gestionamos cada uno de los managers
    GestSoundManager();
    GestInputManager();
    GestGraphicsManager();
}

Espero que os guste,

Nos vemos

LordPakusBlog

Entradas populares