Allegro 5, C / C++, Desarrollo de juegos PC, Programación, Tutorial

Primeros pasos con Allegro 5

En este tutorial vamos a ver como usar la librería Allegro 5. Allegro es una librería de C, que se usa para implementar juegos, mayormente 2D, dado que la librería nos da todas las funcionalidades para dibujo, gestión de sprites, buffering, captura de eventos de usuario (teclado y ratón), música, e includo multithreading.

Además, es multiplataforma, lo que significa que podemos compilar nuestro código fuente en sistemas Windows y Linux, usando los mismos ficheros de cabecera. Lo que cambia es las librerías de linkado.

Puedes descargarte el código para reste tutorial de github: https://github.com/aalmunia/allegro5-base

Primero de todo, tenemos que instalar Allegro 5. En Windows, con Visual Studio, es trivialmente sencillo. Solo sigue las instrucciones del siguiente artículo:
Instalar Allegro 5 en Visual Studio con NuGet, y NuGet, el gestor de paquetes de Visual Studio, hará el trabajo duro por ti.

En Linux, requiere algo mas de trabajo, así que vamos a ver como realizarlo, desde el principio. Lo mejor es descargar el código fuente de Allegro 5 de aquí: https://github.com/liballeg/allegro5, usando el comando

git clone https://github.com/liballeg/allegro5

para ello. Te creará una carpeta /allegro5 a partir de donde hayas ejecutado el comando de clonado.

Primero, es una buena idea actualizar la caché de apt-get, usando el siguiente comando para ello:

sudo apt-get update

Y ahora, para poder compilar la librería, con todos los módulos adicionales que trae, debemos ejecutar el siguiente comando:

sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev cmake build-essential make libxcursor-dev cmake g++ freeglut3-dev libxcursor-dev libpng12-dev libjpeg-dev libfreetype6-dev libgtk2.0-dev libasound2-dev libpulse-dev libopenal-dev libflac-dev libdumb1-dev libvorbis-dev libphysfs-de

Si todo ha ido bien y ha instalado las librerías, ya estamos listos para compilar Allegro 5. Para crear la versión compilada de la librería, primero crearemos una carpeta llamada /build, en la que irá la librería compilada. Vayamos a la carpeta en la que está el código fuente que hemos clonado y ejecutemos los siguientes comandos:

cd allegro5
mkdir build
cd build
cmake ..

Si la ejecución de cmake ha ido correctamente, ya podemos compilar la librería y luego instalarla:

make
sudo make install

Y con esto, deberían quedar las librerías instaladas, generalmente en la carpeta /usr/local/lib, habiendo creado una serie de ficheros con extensión .so, (Shared Object), que es el equivalente a un fichero DLL en Windows. Para comprobar que la librería funciona correctamente, vamos a testearla con un sencillo programa:

allegro5_base_test.cpp
#include <string>
#include <allegro5/allegro.h>

int main(int argc, char** argv) {
	al_init();
	ALLEGRO_DISPLAY* pDisplay = al_create_display(400, 400);
	std::string sWindowTitle = "Nuestra primera aplicación Allegro 5";
	al_set_window_title(pDisplay, sWindowTitle.c_str());
	while(true) {
		al_flip_display();
	}		
	return 0;
}
	

Analizando el código, vemos que lo primero es la inclusión de dos librerías, string y el core de allegro5. Dado que en C las cadenas son de tipo char*, y es una pesadez trabajar con ellas de esta forma, usaremos siempre que nos sea posible la clase string de la librería estándar. La llamada a al_init() es fundamental. Sin ella, ninguna función de allegro va a funcionar. Tras ello, declaramos un puntero a un tipo ALLEGRO_DISPLAY, que representa la ventana en la que se renderizará nuestra aplicación. Le decimos que la queremos de 400×400, y le damos un título llamando a al_set_window_title. Por último, el bucle infinito típico de un juego. Mientras que el usuario no pulse teclas ni interaccione con la aplicación, esta renderizará el estado actual indefinidamente, hasta que algo suceda.

Para poder cerrar esta aplicación, que de momento solo muestra una ventana de 400×400, con el título, tenemos que pulsar CTRL+C en la consola. Esto es así, porque todavía no hemos implementado un sistema de captura de eventos, y la ventana no responde al evento de pulsar en el icono de cerrar de la misma hasta que no lo programemos. Hagamos lo siguiente: Si el usuario pulsa en el icono de cerrar, o si pulsa la tecla ESCAPE, salimos de la aplicación. Vamos a modificar nuestro código para contemplar este caso:

allegro5_base_test.cpp
#include <string>
#include <allegro5/allegro.h>

int main(int argc, char** argv) {
	al_init();
	al_install_keyboard();
	al_install_mouse();
		
	ALLEGRO_DISPLAY* pDisplay = al_create_display(400, 400);
	ALLEGRO_EVENT_QUEUE* oQueue;
	
	oQueue = al_create_event_queue();
	al_register_event_source(oQueue, al_get_keyboard_event_source());
	al_register_event_source(oQueue, al_get_display_event_source(pDisplay));
	
	std::string sWindowTitle = "Nuestra primera aplicación Allegro 5";
	al_set_window_title(pDisplay, sWindowTitle.c_str());	
	while(true) {
		ALLEGRO_EVENT oEvent;
		al_wait_for_event(oQueue, &oEvent);
		switch(oEvent.type) {
			case ALLEGRO_EVENT_DISPLAY_CLOSE:
				al_destroy_display(pDisplay);
				return 0;
			break;
			
			case ALLEGRO_EVENT_KEY_DOWN:
				al_destroy_display(pDisplay);
				return 0;
			break;
			
			default:
			
			break;			
		}
		al_flip_display();
	}		
	return 0;
}
	

Como vemos, el código se modifica considerablmente. Tenemos que llamar a al_init_* para tener disponibles eventos de teclado y ratón. Las instrucciones:

allegro5_base_test.cpp
ALLEGRO_EVENT_QUEUE* oQueue;
oQueue = al_create_event_queue();
al_register_event_source(oQueue, al_get_keyboard_event_source());
al_register_event_source(oQueue, al_get_display_event_source(pDisplay));
  

crean la cola de eventos, y le dicen al programa que esta cola de eventos recibe eventos de dos fuentes: el teclado y el ratón. La instrucción

al_wait_for_event(oQueue, &oEvent);

es interesante. Estamos escuchando eventos, y al producirse uno, pasamos por referencia el objeto de tipo ALLEGRO_EVENT previamente creado, de tal forma que se rellena al entrar en esa función. Posteriormente, revisamos el tipo del evento, y si es alguno de los dos casos anteriormente descritos, cerramos la aplicación. Allegro provée de una inmensa cantidad de eventos tipados, desde pulsaciones de teclado y ratón, hasta Joystick y temporizadores, con todas las características del evento en cada caso. Realmente, es la gestión de toda esta enorme cantidad de eventos, lo que suele ser el grueso de una aplicación tipo juego escrita en Allegro. En este sentido, es muy sencillo programar qué pasa cuando pulsamos ciertas teclas, y dejar el juego corriendo mientras tanto. También podemos definir nuestros propios eventos, en un futuro tutorial veremos mas sobre este tema.

El problema del código anterior es su poca reutilización. Para solucionar este problema, vamos a encapsular la funcionalidad de inicialización de la aplicación dentro de una clase que llamaremos BaseApplication. Esta clase tendrá una serie de métodos, siendo, al menos uno de ellos, virtual, para que la clase que herede de ella lo implemente, y de esa forma, la aplicación se inicie. Vamos a ver el código fuente de nuestra clase BaseApplication. En C++, lo habitual es separar el código de la especificación de la clase de la implementación de la misma. La definición se suele escribir en un fichero de cabecera, con extensión .h, mientras que la implementación se guarda en un fichero de extensión .cpp

BaseApplication.h
#include <allegro5/allegro.h>
#include <string>
#include <vector>
#include <stdio.h>
#include <iostream>

#ifndef APPLICATION_H
#define APPLICATION_H

class BaseApplication {
public:
	BaseApplication();
	BaseApplication(const BaseApplication& orig);
	BaseApplication(unsigned int iWidth, unsigned int iHeight, std::string sName);	
	void initApp();
	virtual int mainLoop() = 0;
	virtual ~BaseApplication();
protected:
	ALLEGRO_DISPLAY *m_pDisplay = NULL;
	unsigned int m_iWindowWidth = 0;
	unsigned int m_iWindowHeight = 0;
	std::string m_sWindowName;	
	ALLEGRO_COLOR m_oColRed;
	ALLEGRO_COLOR m_oColGreen;
	ALLEGRO_COLOR m_oColBlue;
	ALLEGRO_COLOR m_oColWhite;
	ALLEGRO_EVENT_QUEUE* m_pEventQueue;
	void initColors();	
};

#endif /* APPLICATION_H */      
  

Y ahora, veamos el fichero de implementación de la clase:

BaseApplication.cpp
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include "BaseApplication.h"
BaseApplication::BaseApplication() {
}

BaseApplication::BaseApplication(const BaseApplication& orig) {
}

BaseApplication::BaseApplication(unsigned int iWidth, unsigned int iHeight, std::string sName) {
	this->m_iWindowWidth = iWidth;
	this->m_iWindowHeight = iHeight;
	this->m_sWindowName = sName;
}

void BaseApplication::initApp() {

	this->m_pDisplay = al_create_display(this->m_iWindowWidth, this->m_iWindowHeight);

	if (this->m_pDisplay != NULL) {
		al_set_window_title(this->m_pDisplay, this->m_sWindowName.c_str());
		this->m_bInit = true;
		this->initColors();
		al_clear_to_color(this->m_oColWhite);
		std::cout << "Initialized OK" << std::endl;
	}
}

void BaseApplication::initColors() {
	this->m_oColRed = al_map_rgb(255, 0, 0);
	this->m_oColGreen = al_map_rgb(0, 255, 0);
	this->m_oColBlue = al_map_rgb(0, 0, 255);
	this->m_oColWhite = al_map_rgb(255, 255, 255);
}

BaseApplication::~BaseApplication() {
	if (this->m_pDisplay != NULL) {
		al_destroy_display(this->m_pDisplay);
	}
	if (this->m_pEventQueue != NULL) {
		al_destroy_event_queue(this->m_pEventQueue);
	}
}      
  

Parece bastante complejo, pero en realidad es muy simple. Dado que siempre, cuando escribamos una aplicación de Allegro vamos a querer ciertas funcionalidades disponibles, lo mejor es programarlas en una clase que tenga algún método virtual, de tal forma que siempre tendremos disponible la pantalla, el teclado, el ratón, etc… Solo tenemos que preocuparnos del código de nuestra aplicación, en vez de todo el código que conlleva inicializar la aplicación. Además, de esa forma, si en un futuro agregamos una funcionalidad a nuestra clase base (la capacidad de recibir ciertos parámetros de entrada para su configración, por ejemplo), lo tendremos disonible en el resto de aplicaciones que hayamos escrito con esta clase como base. Para nuestro ejemplo, escribamos una clase que herede de BaseApplication:

Application.h
      
#pragma once
#ifndef APPLICATION_H
#define APPLICATION_H
#include 
#include "BaseApplication.h"

class Application :
	public BaseApplication
{
public:
	Application(unsigned int iWidth, unsigned int iHeight, std::string sName) : BaseApplication(iWidth, iHeight, sName) {
		al_init();
		al_install_mouse();
		al_install_keyboard();
	};
	virtual ~Application();
	int mainLoop();
};

#endif    
  

Y el código de la implementación:

Application.h
#include "Application.h"

Application::~Application()
{
}

int Application::mainLoop() {

	this->m_pEventQueue = al_create_event_queue();
	al_register_event_source(this->m_pEventQueue, al_get_keyboard_event_source());
	al_register_event_source(this->m_pEventQueue, al_get_display_event_source(this->m_pDisplay));

	while (true) {
		ALLEGRO_EVENT oEvent;
		al_wait_for_event(this->m_pEventQueue, &oEvent);

		switch (oEvent.type) {
			case ALLEGRO_EVENT_DISPLAY_CLOSE:
				return 0;
			break;

			case ALLEGRO_EVENT_KEY_DOWN:
				return 0;
			break;
		}

		al_flip_display();
	}
}      
  

Como vemos, el código es bastante menos extenso que el de la clase base, porque toda la inicialización ya está hecha. El constructor usa una nueva funcionalidad de C++11, que permite iniicalizar el contructor de la clase base, al tiempo que llamamos a nuestra propia inicialización para la clase hija. El código interesante está en el método mainLoop, en el que lo que hemos hecho es encapsular la funcionalidad de cerrar la aplicación si pulsamos ESCAPE, o pulsamos en el icono de cerrar de la ventana.

Ahora, el proceso de compilación. Si estás usando Visual Studio, ese proceso es un par de clicks, pero si estás usando Linux, es algo mas complicado. Podemos usar NetBeans o Code::Blocks, y lo único que tendríamos que hacer es configurar el proyecto para que, a la hora de realizar el linkado, agregue las librerías que están, si hemos seguido el proceso de instalación descrito, en /usr/local/lib, y se llaman liballegro*.so. Pero si queremos realizar la compilación en consola, deberíamos escribir un script llamado Makefile. Es muy sencillo, vamos a ver el código a continuación:

Makefile
CC=g++
CFLAGS=-std=c++11

all: application

application: main.o BaseApplication.o Application.o
    $(CC) main.o BaseApplication.o Application.o -AllegroApplication1

main.o: main.cpp
    $(CC) $(CFLAGS) main.cpp

BaseApplication.o: BaseApplication.cpp
    $(CC) $(CFLAGS) BaseApplication.cpp

main.o: main.cpp
    $(CC) $(CFLAGS) main.cpp

clean:
    rm *o AllegroApplication1
    

Aunque, sinceramente, recomiendo usar un IDE, como Visual Studio, NetBeans, Eclipse, Code::Blocks, o el que te de la gana. No obstante, es interesante conocer como compilar y linkar programas en línea de comandos, por si nos hiciese falta.

C++ ofrece muchas capacidades que, desde mi punto de vista, compensan la complejidad añadida de trabajar con el. Aunque Allegro está escrita en C,y la mayoría de ejemplos que puedes encontrar (incluso los que vienen con la propia librería), están escritos en C, mantener una base de código reutilizable es mas sencillo usando orientación a objetos que librerías de funciones. Es por eso que en el siguiente tutorial usaremos la clase Application que hemos implementado, para programar un sencillísimo juego.

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *