[BGE] La clase App

Por fin llegamos a la clase App, con esta clase empieza a cobrar algo de sentido todo lo anterior porque une lo visto hasta ahora, crea la ventana de la aplicación, etc.

La clase App sigue, como el SceneManager, el patrón de diseño Singleton pues no tiene sentido tener más de un objeto App, cualquier intento de crear un nuevo objeto App devolverá la instancia única.

Empezamos con las variables públicas.

[cpp]
// Constantes
///////////////////////////////////////////////////////////////////////////
static const unsigned int DEFAULT_VIDEO_WIDTH = 640;
static const unsigned int DEFAULT_VIDEO_HEIGHT = 480;
static const unsigned int DEFAULT_VIDEO_BPP = 32;

// Variables
///////////////////////////////////////////////////////////////////////////
/// Título de la ventana
std::string title;
/// Ventana de la aplicación
sf::RenderWindow window;
/// Modo de video (Width, Height, Bpp)
sf::VideoMode video_mode;
/// Opciones de la ventana
sf::WindowSettings window_settings;
/// Estilo de la ventana
unsigned long window_style;
/// Input manager de la ventana
const sf::Input& input;
/// Log de la aplicación
std::ofstream log;
/// Puntero a Scene inicial
Scene* scene;
/// Puntero al SceneManager
SceneManager* scene_manager;
[/cpp]

Como vemos lo primero que hacemos es declarar 3 constantes enteras con el modo de vídeo por defecto. Luego pasamos a las variables, están todas explicadas en los comentarios y se entiende su función claramente, cualquier duda podéis preguntar.

como privadas también tenemos unas cuantas.

[cpp]
/// Código de salida de la aplicación
int exit_code;
/// Verdadero si la aplicación se está ejecutando
bool running;
/// Archivo de log de la aplicación
std::string log_file;
/// Reloj que obtiene el tiempo pasado en cada loop
sf::Clock update_clock;
/// Almacena el tiempo pasado en cada bucle
float update_time;
[/cpp]

Sin ninguna duda tampoco de la función que cumplen.

Antes de ver los métodos vamos a ver un esquema del ciclo de ejecución y a continuación lo explicamos.

Con la imagen a mano miramos el código de la clase.

Como siempre lo primero que se ejecuta es el constructor que inicializa variables y constantes y creo un archivo de log.

A continuación se llama a ProcessArguments que se encarga de procesar los argumentos pasados por linea de comandos, este método es opcional, pero es útil para cuando se están creando los juegos pasarle comandos que hacen determinadas acciones de pruebas. De momento no está implementado el método simplemente imprime los comandos pasados en el log. La idea es que este método lo implemente el usuario para lo que le interese.

Luego se llama a SetFirstScene que recibe una escena y la establece como la inicial, luego se verá mejor porque hacerlo aquí y así.

A continuación se llama al método Run que es el que ejecuta la aplicación, este método se encarga de llamar a los métodos PreInit, Init, Loop y cleanup.

El método PreInit aún no está implementado, se encarga de leer un archivo de configuración con las opciones de las aplicaciones y con estos datos crear la ventana.

Después se llama al Init, de momento se encarga de crear el SceneManager, se añade la primera escena a la cola y se pone como escena activa. En el futuro aquí se inicializarán también clases que lleven control de fps, etc.

Llegamos al método Loop que suele ser de los más importantes de un juego, aquí se encuentra nuestro bucle que va manejando el juego.

Lo primero que hace nuestro bucle es obtener los eventos del sistema, hay algunos por defecto que se encarga de manejar la aplicación de los que por ahora solo está implementado el de cierre y el resto de eventos se los mandamos a la escena activa.

Luego actualizamos nuestra variable de tiempo pasado en cada ciclo y reseteamos el reloj, recuerda que estas son variables privadas de la escena, pero que hay métodos para obtener este tiempo.

A continuación se llama al método Update de la escena activa y al método Draw, lo típico de todos los juegos.

Por último si recuerdas en el artículo anterior en la clase SceneManager habíamos declarado App como friend, es decir una clase amiga. Esto es que la clase App puede acceder a los miembros privados de SceneManager, la razón de esto es que si revisamos la clase SceneManager el método ChangeScene es privado, este método se encarga de cambiar el puntero de la escena activa a la indicada, si cambiáramos esto a la ligera en medio de una escena podría darnos un problema de memoria.

En lugar de eso tenemos como publico el método SetActiveScene que es el siguiente.

[cpp]
void SceneManager::SetActiveScene(SceneID id_scene)
{
next_scene = id_scene;
}
[/cpp]

simplemente le da un valor a next_scene y luego desde app controlamos el cambio de escena fuera del método update y draw y sin peligro de segmentación de memoria.

[cpp]
// Comprobamos cambios de escena
if (scene_manager->next_scene != "")
{
scene_manager->ChangeScene(scene_manager->next_scene);
scene_manager->next_scene = "";
}
[/cpp]

tanto la variable next_scene como el método ChangeScene son privados, pero la clase App al ser friend si que puede acceder a ellos. Simplemente se encarga de comprobar si la variable next_scene no esta vacía en cuyo caso llama a ChangeScene y vuelve a vacías next_scene.

Mientras la aplicación se está ejecutando se repite este ciclo.

Por último cuando se sale del loop se entra en el Cleanup que se encarga de las funciones limpieza, llama al RemoveAllScene del SceneManager que a su vez llama al Cleanup de cada escena y luego las elimina. Por último elimina el SceneManager.

Tenemos varios métodos útiles declarados como son.

  • IsRunning – Valor booleano si la aplicación se está ejecutando o no.
  • GetUpdateTime – Devuelve el tiempo pasado en cada ciclo
  • Quit – Para la aplicación con un código de salida

Bueno esto ha sido un repaso general a la clase App. Si algo no se entiende preguntar en los comentarios. En el próximo artículo se vera todo más claro pues crearemos una aplicación de ejemplo.

[BGE] La clase SceneManager

Antes de ver la clase App vamos a  ver el SceneManager, ya que hemos visto en primer lugar la clase Scene ahora vamos a ver la clase que va a manejar los objetos Scene y decidir cual esta activo y cual no.

Sobre SceneManager he visto muchas formas de implementarlo, la más común es utilizar una pila donde haciendo push y pop se puede cambiar de escena fácilmente, la desventaja de una pila es la poca flexibilidad para ir de una escena a otra, veamos un ejemplo práctico.

Añadimos una escena inicial:
>> scene_manager->push("SceneStart")
Ahora "SceneStart" es la escena activa.
>> scene_manager->push("SceneMenuPrincipal")
Ahora "SceneMenuPrincipal" es la escena activa.
>> scene_manager->push("SceneGame")
Ahora "SceneGame" es la escena activa
>> scene_manager->pop()
Ahora "SceneMenuPrincipal" es la escena activa

Parece un buen método, es fácil poner una escena y volver a la anterior mediante pop, pero, ¿Y si queremos volver 2 escenas atrás? Pues hacemos 2 veces pop y solucionado, ¿Y si son 15? Esto ya empieza a ser mas engorroso. ¿Y que sucede si queremos hacer push de una escena que ya está en la pila?

Como vemos es un sistema muy rígido que no permite cambiar a la escena que queramos con facilidad.

Mi idea es utilizar un simple array con una lista de escenas inactivas y una variable con un puntero a la escena activa.

[cpp]
/// Escena actualmente activa
Scene* active_scene;
// Lista de escenas inacticas
std::vector<Scene*> inactives_scenes;
[/cpp]

Estas dos objetos pertenecen a la clase SceneManager y con diferentes métodos los controlaremos. Puedes ver los diferentes métodos de la clase y su documentación en GitHub

Como ves tenemos métodos para añadir escenas, cambiarla, eliminarlas, eliminarlas todas, etc. Todas están perfectamente documentadas, pero si surgen dudas podéis dejar un comentario.

Un detalle es que los métodos ChangeScene y RemoveAllScene son privados. Esto es así por que el primero se encarga de cambiar la variable scene_active y si no se hace en el sitio correcto dentro de la clase App puede haber un error, se verá mejor cuando veamos la clase App. En cuanto a RemoveAllScene hace lo mismo que RemoveAllInactiveScene, pero también destruye la escena activa, se usa como limpieza cuando se termina la aplicación.

Como vemos tenemos a la clase App como amiga, esto es así porque nos interesa que pueda acceder a estos métodos privados y a la escena activa.

Otro detalle importante es que la clase está diseñada con el patrón Singleton, esto hace que solo pueda haber un SceneManager y no se cree más de uno por error.

[BGE] La clase Scene

En teoría lo lógico sería empezar hablando de la clase App, pues sera esta la principal del engine, pero necesitamos primero ver como va a ser un escena para luego poder hacer un buen app que las controle.

¿Que es una escena?

Puede parecer obvio, pero por si acaso vamos a aclararlo. Una escena es cualquier pantalla de un juego que necesite mostrarse en pantalla, actualizar datos y/o interactuar con el usuario.

Por ejemplo, una pantalla de inicio donde se muestra un vídeo de introducción al juego sería una escena, se muestra en pantalla, pero no interactúa con el usuario. Un menú de inicio donde podemos empezar un nuevo juego, cargar, salir, etc. sería otra escena de nuestro juego, etc.

Cada escena debe tener unas funciones básicas comunes, estas son:

  • Init – Se encarga de inicializar la escena.
  • Pause – Es llamado cuando se pausa la escena.
  • Resume – Es llamado cuando se reanuda la escena.
  • Cleanup – Es llamado al eliminar la escena, realiza funciones de limpieza.
  • Events – Es llamado para procesar los eventos del sistema.
  • Update – Se encarga de actualizar la lógica de la escena, llamado una vez por ciclo.
  • Draw – Se encarga de dibujar en pantalla, llamado una vez por ciclo.

Estos son algunos de los métodos que tendrá nuestra escena. Cuenta también con otros métodos constantes que devuelven información de la escena.

  • GetID – Devuelve el identificador de la escena.
  • IsInitComplete – Devuelva true si se ha completado el Init.
  • IsPaused – Devuelve true si la escena esta pausada.

Como variables tenemos, los seguientes:

  • App* app – Puntero al objeto de la aplicación (Protegido).
  • SceneID id – Almacena el id de la escena (Privado).
  • bool m_cleanup – Almacena si la escena necesita limpieza.
  • bool m_init – Almacena si la escene esta inicializada.
  • bool m_paused – Almacena si la escena está pausada.

De todos estos datos el constructor recibe los dos primeros,  Scene(SceneID m_id, App* p_app). Aún no hemos visto la clase App ya veremos luego para qué es este puntero al objeto app. Los otros tres son valores booleanos y se inicializan como false.

Bueno, la explicación está muy bien, pero creo que es mejor que enseñe el código, aquí dejo los enlaces a GitHub de la clase.

Como vemos en la implementación (scene.cpp) no están los métodos Events, Update y Draw. De hecho, en la definición de la clase (scene.h) podemos ver que están igualados a 0. Esto es que son funciones virtuales puras, es decir, esta clase no las implementan sino que deben hacerlo las clases que hereden de ella, es decir, las escenas de nuestros juegos. Los otros métodos no constantes también son virtuales, pero tienen una implementación básica necesaria.

En el próximo artículo veremos la clase App. Cualquier duda, dejad un comentario,

 

[BGE] Espacio de nombre, tipos y cabeceras

Antes de entrar en materia con las clases del sistema base surgen algunas cosas que hacer y decir, recordemos nuestra distribución del directorio src.

|- src/
   |- BGE/
      |- BGE.h
      |- types.h
      |- app.h
      |- app.cpp
      |- scene.h
      |- scene.cpp
      |- ...
   |- main.cpp
   |- scene_start.h
   |- scene_start.cpp
   |- ...

Es decir, metemos los ficheros del engine propiamente dichos dentro de la carpeta src/BGE y los archivos de prueba que usamos para ir probando nuestro engine quedan en la carpeta src/.

Esta es la estructura que tendría un juego que use el engine, simplemente añadir la carpeta con los archivos del engine y utilizarlos. Más adelante veremos como convertirlo en bibliotecas para que el Engine no se esté compilando cada vez.

Espacio de nombre BGE

He decidido que toda las clases del engine se encuentren dentro del namespace BGE, la razón de esto es que vamos a trabajar con bibliotecas de desarrollo de videojuegos y estamos haciendo un engine para hacer juegos esto hace que sea muy probable tener nombres iguales en diferentes bibliotecas, por ejemplo, en SFML hay una clase llamada Clock y el engine también tendrá una clase llamada Clock y probablemente cuando se haga un juego con el engine el programador quiera tener su propia clase Clock. Con los espacios de nombre solucionamos el problema de saber a que clase nos estamos refiriendo.

El archivo de encabezado

Basándome en SFML he decidido hacer un archivo de encabezado genérico por módulo, es decir un header que incluya lo necesario para hacer funcionar cada parte del engine. esto sería el ejemplo de como estaría actualmente.

[cpp]
#ifndef BGE_H
#define BGE_H

#include "types.h"

#include "app.h"
#include "scene.h"

#include "clock.h"

#endif // BGE_H
[/cpp]

Como vemos incluye las clases de las que hemos hablado y que son básicas para hacer funcionar el engine. No os preocupéis se que aún no hemos entrado en materia con ninguna, en próximos artículos lo haremos.

Tipos propios del engine

En el código anterior verás un archivo del que no hemos hablado el types.h. Este archivo contendrán los tipos específicos creados para el engine. He declarado algunos básicos.

[cpp]
/**
* Definición de tipos del espacio de nombre BGE
*
* @file types.h
* @author Adrián Guerra Marrero
* @date 20110422 – Initial Release
*/

#ifndef TYPES_H
#define TYPES_H

#include <string>

namespace BGE
{

// Tipo de dato que representa el ID de una Escena
typedef std::string SceneID;

/// Status Enumeration for Status Return values
enum StatusType
{
// Values from -99 to 99 are common Error and Good status responses
StatusAppMissingAsset = -4, ///< Application failed due to missing asset file
StatusAppStackEmpty = -3, ///< Application States stack is empty
StatusAppInitFailed = -2, ///< Application initialization failed
StatusError = -1, ///< General error status response
StatusAppOK = 0, ///< Application quit without error
StatusNoError = 0, ///< General no error status response
StatusFalse = 0, ///< False status response
StatusTrue = 1, ///< True status response
StatusOK = 1 ///< OK status response

// Values from +-100 to +-199 are reserved for File status responses
};

// Forwards Declarations
class App;
class SceneManager;
class Scene;

} // Namespace BGE

#endif // TYPES_H
[/cpp]

En primer lugar he declarado el tipo SceneID que no es más que una string que se usará para identificar las escenas, ya lo veremos mejor cuando expliquemos la clase Scene.

Luego tenemos un enumerado con una lista de estados del proyecto, por ahora solo hay definidos algunos básicos. Esto se usa para el return de la aplicación y evitar los números mágicos.

por último tenemos los Fowards Declarations, si no sabes lo que son puedes mirar este artículo en concreto es el problema 2, pero recomiendo leer todo el artículo.

[BGE] Sistema base del Engine

Bueno es hora de entrar en materia ya y empezar a ver la estructura del Engine. Mi idea es basarme en la estructura que usan otros engines y apis pues no es cuestión de reinventar la rueda, sino de uno entender como se hace.

Estructura básica de un juego

Se sale de este artículo explicar el desarrollo del bucle de un juego, pero no está mal recordarlo con un simple esquema:

Es un esquema muy simplificado, sin tener en cuenta temas como la velocidad de actualización para diferentes máquinas, etc. Si alguno necesita un tutorial sobre estos conceptos básicos que deje un comentario y haré un tutorial sobre el tema.

Cuando desarrollamos juegos simples este esquema está muy bien, pero cuando necesitamos manejar un proyecto a gran escala se nos empieza a quedar corto, por ejemplo, si necesitamos manejar varias pantallas como un menú, una pantalla de inicio, créditos, etc. este sistema sería bastante engorroso pues habría que estar añadiendo muchas sentencias de control para ver cuando se ejecuta qué y al final no habría forma de continuar del lío que se formaría, ahí es donde entra un gestor de escenas.

Gestor de Escenas

La idea es similar a lo explicado en este artículo con Pygame. Tener una clase abstracta llamada Scene que contenga los métodos virtuales de las diferentes escenas y gestionarlos en el bucle principal a través de un Scene Manager.

Para los que no estéis muy puestos en el tema lo de arriba os sonará a chino, pero con un esquema quizás se vea mejor.

Como vemos existe una clase App que se encarga de de inicializar el engine, carga ventanas, bibliotecas, archivos de configuración, etc.

También contiene una clase llamada SceneManager que se encarga de gestionar las escenas, esta clase cuanta con una lista de escenas inactivas y una Escena activa que con diferentes métodos podemos añadir, eliminar o cambiar, cuando veamos el SceneManager entraremos en detalles.

En el próximo artículo empezamos a ver el código de estas clases.

Creando un archivo de log en C++

Cuando se está haciendo un programa es normal utilizar la salida estándar para ir poniendo mensajes de resultados parciales y pruebas para ver si las cosas están sucediendo como se esperan. Cuando el software que estamos creando alcanza mayor complejidad estos mensajes no son suficientes, es aquí donde entran los archivos de log. Esto son básicamente ficheros de texto que van guardando esto mensajes para luego poder analizarlos en profundidad.

Hay muchas maneras de crear un archivo de log algunas más complejas y con mas datos como hora, fehcas, colores, etc y otras mas sencillas. Aquí voy a mostrar una manera sencilla de crear uno de estos archivos utilizando las grandes herramientas de control de flujo de C++. Como no hay mucho más que explicar simplemente dejo el código.

[cpp]
#include <string>
#include <fstream>

int main()
{
// Declaramos las variables
std::ofstream log;
std::string log_file;

// Creamos el archivo de log
log_file.assign("log.txt");
log.open(log_file.c_str());

// Escribimos una línea con el nombre del archivo
log << "LogFile: " << log_file << std::endl;

// Escribimos en el log
log << "Esta es una linea del log" << std::endl;

// Cerramos el archivo
log.close();
}
[/cpp]

Como ves no es un log con mil pijerías pero es rápido y eficiente, podemos usar simplemente:

[cpp]
log << "Lo que quiera poner en el fichero";
[/cpp]

Como lo haríamos con la salida estándar con cout.

El resultado sería un archivo log.txt con el siguiente contenido:

LogFile: log.txt
Esta es una linea del log

Las herramientas de control de flujo de C++ que están en la biblioteca estándar son muy poderosas, en próximos artículos hablaremos más de ellas.

[BGE] Comenzando el proyecto

Vamos a comenzar esta especie de diario acerca de la creación de un Game Engine, los post del blog que traten sobre el Engine incluirán la etiqueta [BGE] (Basic Game Engine) para que sean fácilmente reconocibles.

Creando un repositorio

Lo primero que necesitaba era crear un repositorio para el proyecto donde tener un control de versiones y avances, me he decidido por GitHub pues ya he trabajado con Git antes y GitHub es un buen host basado en Git.

Este será el repositorio del proyecto: https://github.com/adrigm/Basic-Game-Engine

Estructura del proyecto

Una de las primeras cosas que uno tiene que decidir es como organizar el proyecto, como distribuir los archivos etc. La idea es que los diferentes módulos del proyecto sean al final Bibliotecas que se puedan añadir sencillamente para crear un juego sobre ellas, pero mientras está en fase de desarrollo no se pueden aislar como bibliotecas.

Otra cosa a tener en cuenta es separar el código fuente de cualquier IDE, compilador o plataforma,

Así que la distribución elegida es la siguiente:

|- Basic-Game-Engine/
    |
    |- src/
        |- BGE/
        |- ficheros.hpp
        |- ficheros.cpp
    |
    |- builds/
        |- cmake/
        |- codelite/
        |- VS2010/
        |- ...
    |
    |- doxygen/
        |- Doxyfile
        |- ...
    |
    |- README
    |- log.txt
    |- Executable

Como puedes ver en principio habrá 3 directorios:

  • src – Donde irá el codigo fuente, separados en carpetas según módulos.
  • builds – Aquí irán las diferentes carpetas con los ficheros de proyecto de las diferentes ide, yo trabajo con codelite y por ahora solo está configurado para esta IDE, también lo configuraré para Cmake, pero cualquiera puede añadir una carpeta para cualquier otro IDE. En la carpeta del proyecto se deben crear todos los ficheros que necesite la IDE, los .o, etc y generar el ejecutable en el directorio base del proyecto.
  • doxygen – Aquí se encuentran los ficheros necesarios para generar la documentación del proyecto.

Por ahora eso son los directorios pensado, conforme crezca el proyecto es posible que se necesiten otros extras.