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

Timers y eventos en Allegro 5 (I)

En este tutorial, vamos a aprender a animar un bitmap, recoger entrada del teclado del usuario, y usar un timer (contador de tiempo), para implementar lo que se acabará conviertiendo en nuestro juego de disparos: SpaceShooter. Un clásico shooter de maquina recreativa.

Pero antes de poder programar el mejor shooter de la historia, a partir del código de SpaceShooter, tenemos que aprender una serie de conceptos. En este tutorial vamos a ver como animar bitmaps, usando para ello interacción del usuario, así como contadores de tiempo que nos permitirán controlar eventos a intervalos de tiempo. Por último, veremos una técnica llamada Double Buffering, que hará que nuestro juego se vea con una animación suave y fluida.

Para facilitar que entendamos bien los conceptos sobre los que trata este artículo, no vamos a usar las clases previamente diseñadas, sino que todo el programa estará en una función main(). Tras explicarlo y entenderlo, veremos como crear la arquitectura de clases que necesitamos para poder integrar este proyecto con la librería que, poco a poco, vamos construyendo.

Por ello, constará de dos partes. En esta primera, veremos como escribir el programa «del tirón» para aprender las técnicas de trabajo con Allegro5, y en la segunda parte, tras (espero) haber trasteado con el programa actual, y habiendo aprendido mas por vuestra cuenta, veremos como integrar esto en el paradigma POO de C++, agregando al mismo tiempo funcionalidad a nuestra librería, que terminaremos usando para hacer mas cosas que SpaceShooter.

El código de esta primera parte lo podemos encontrar en el siguiente repositorio de GitHub: https://github.com/aalmunia/allegro5-timers-events

Vamos a definir que de momento, la especificación tiene que cumplir las siguientes características:

  • Renderizar la nave del jugador sobre el fondo de la aplicación
  • Responder a las pulsaciones de las teclas ARRIBA, ABAJO, DERECHA e IZQUIERDA, que harán moverse la nave en esa dirección
  • Tiene que quedar una animación suave, sin tirones, en la medida de lo posible.
  • Podremos renderizar el bitmap de la nave del jugador, que responde a interacción, así como los bitmaps de las naves enemigas
  • Podremos controlar la velocidad a la que se mueven las naves enemigas, usando para ello pulsaciones de teclado
  • Podremos modificar la cantidad de frames por segundo a los que se renderiza la aplicación

Es importante que entendamos que el programa que vamos a ver no es un juego. Es una animación de varios bitmaps, que se pueden controlar a través de interaccionar con el teclado.

Primero, veamos el codigo de nuestro programa:

main.cpp
#include <cstdlib>
#include <time.h>
#include <iostream>
#include <string>
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>

using namespace std;

void redraw(ALLEGRO_DISPLAY *pDisplay, ALLEGRO_BITMAP* pBitmap, ALLEGRO_BITMAP* pBuffer, ALLEGRO_COLOR oBgColor, float fX, float fY) {	
	al_set_target_bitmap(pBuffer);
	al_clear_to_color(oBgColor);
	al_draw_bitmap(pBitmap, fX, fY, 0);
	
}

void redrawEnemies(ALLEGRO_DISPLAY *pDisplay, ALLEGRO_BITMAP** arrPtrEnemies, ALLEGRO_BITMAP* pBuffer, float* fX, float* fY, int iEnemies, float fSpeedMult = 0.0f) {
	int iBitmapHeight = al_get_bitmap_height(pBuffer);	
	for (int i = 0; i < iEnemies; i++) {
		fY[i] += (fSpeedMult == 0.0f) ? 0.1f : 0.1f * fSpeedMult;
		if (fY[i] > iBitmapHeight) { fY[i] -= iBitmapHeight; }
		if (fY[i] < 0) { fY[i] = al_get_bitmap_height(pBuffer); }
		al_set_target_bitmap(pBuffer);
		al_draw_bitmap(arrPtrEnemies[i], fX[i], fY[i], 0);
	}
}

int main(int argc, char** argv) {

	al_init();
	al_install_keyboard();
	al_install_mouse();
	al_init_image_addon();

	std::string sTitleApp = "Allegro 5 Timers and Smooth Animation";
	int gbl_iAppWidth = 800;
	int gbl_iAppHeight = 800;

	ALLEGRO_DISPLAY* pDisplay = al_create_display(gbl_iAppWidth, gbl_iAppHeight);
	int iFPS = al_get_display_refresh_rate(pDisplay);
	if (iFPS == 0) {
		iFPS = 60;
	}

	ALLEGRO_TIMER* pTimer = al_create_timer(1.0 / iFPS);	
	ALLEGRO_EVENT_QUEUE* pQueue = al_create_event_queue();

	al_set_window_title(pDisplay, sTitleApp.c_str());

	al_register_event_source(pQueue, al_get_keyboard_event_source());
	al_register_event_source(pQueue, al_get_mouse_event_source());
	al_register_event_source(pQueue, al_get_timer_event_source(pTimer));
	al_start_timer(pTimer);	
	
	al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP);
	ALLEGRO_BITMAP* pBitmap = al_load_bitmap("spaceship_2.png");
	
	unsigned int iEnemies = 24;
	
	ALLEGRO_BITMAP** arrPtrEnemies = new ALLEGRO_BITMAP*[iEnemies];	
	float* fEnemiesPosX = new float[iEnemies];
	float* fEnemiesPosY = new float[iEnemies];

	int xPlus = 1;
	int yPlus = 1;
	int iEnemiesPerRow = 8;

	for (unsigned int i = 0; i < iEnemies; i++) {		
		if (i % iEnemiesPerRow == 0) { ++yPlus; xPlus = 1; }
		fEnemiesPosX[i] = xPlus * 75.0f;
		fEnemiesPosY[i] = yPlus * 75.0f;
		arrPtrEnemies[i] = al_load_bitmap("spaceship_enemy_2.png");
		++xPlus;
	}
	
	ALLEGRO_BITMAP* pDoubleBuffer = al_create_bitmap(gbl_iAppWidth, gbl_iAppHeight);
	ALLEGRO_COLOR oBlack = al_map_rgb(0, 0, 0);
	
	float fPlayerShipMovement = 10.0f;
	float fPosX = (600.0f / 2) + (128.0f / 2);
	float fPosY = (600.0f - 128.0f) - 15.0f;
	float fEnemiesSpeedMult = 0.0f;
	bool bMoveRight = false;
	bool bMoveLeft = false;
	bool bMoveUp = false;
	bool bMoveDown = false;

	ALLEGRO_EVENT oEvent;
	while (true) {

		al_wait_for_event(pQueue, &oEvent);

		if (oEvent.type == ALLEGRO_EVENT_TIMER && oEvent.timer.source == pTimer) {

			if (bMoveRight == true) {
				fPosX += fPlayerShipMovement;
			}

			if (bMoveLeft == true) {
				fPosX -= fPlayerShipMovement;
			}

			if (bMoveUp == true) {
				fPosY -= fPlayerShipMovement;
			}

			if (bMoveDown == true) {
				fPosY += fPlayerShipMovement;
			}

			redraw(pDisplay, pBitmap, pDoubleBuffer, oBlack, fPosX, fPosY);
			redrawEnemies(pDisplay, arrPtrEnemies, pDoubleBuffer, fEnemiesPosX, fEnemiesPosY, iEnemies, fEnemiesSpeedMult);
			al_set_target_bitmap(al_get_backbuffer(pDisplay));
			al_draw_bitmap(pDoubleBuffer, 0, 0, 0);
			al_flip_display();
		}

		if (oEvent.type == ALLEGRO_EVENT_KEY_DOWN) {
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
				std::cout << "ESCAPE, SALIENDO...\n";
				break;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_RIGHT) {
				std::cout << "DERECHA\n";
				bMoveRight = true;
				bMoveLeft = false;
				bMoveUp = false;
				bMoveDown = false;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_LEFT) {
				std::cout << "IZQUIERDA\n";
				bMoveLeft = true;
				bMoveRight = false;
				bMoveUp = false;
				bMoveDown = false;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_UP) {
				std::cout << "ARRIBA\n";
				bMoveLeft = false;
				bMoveRight = false;
				bMoveUp = true;
				bMoveDown = false;
			}

			if (oEvent.keyboard.keycode == ALLEGRO_KEY_DOWN) {
				std::cout << "ABAJO\n";
				bMoveLeft = false;
				bMoveRight = false;
				bMoveUp = false;
				bMoveDown = true;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_PAD_PLUS) {
				std::cout << "Velocidad enemigos: " << fEnemiesSpeedMult << "\n";
				fEnemiesSpeedMult += 0.5f;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_PAD_MINUS) {
				std::cout << "Velocidad enemigos: " << fEnemiesSpeedMult << "\n";
				fEnemiesSpeedMult -= 0.5f;
			}
		}


		if (oEvent.type == ALLEGRO_EVENT_KEY_UP) {
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_RIGHT) {
				bMoveRight = false;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_LEFT) {
				bMoveLeft = false;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_UP) {
				bMoveUp = false;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_DOWN) {
				bMoveDown = false;
			}
		}

		if (oEvent.type == ALLEGRO_EVENT_KEY_CHAR) {
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_PAD_PLUS) {
				std::cout << "Velocidad enemigos: " << fEnemiesSpeedMult << "\n";
				fEnemiesSpeedMult += 0.5f;
			}
			if (oEvent.keyboard.keycode == ALLEGRO_KEY_PAD_MINUS) {
				std::cout << "Velocidad enemigos: " << fEnemiesSpeedMult << "\n";
				fEnemiesSpeedMult -= 0.5f;
			}
		}
	}

	al_destroy_timer(pTimer);
	al_destroy_bitmap(pBitmap);
	for (unsigned int i = 0; i < iEnemies; i++) {
		al_destroy_bitmap(arrPtrEnemies[i]);
	}
	al_destroy_display(pDisplay);
	al_destroy_event_queue(pQueue);

	delete fEnemiesPosX;
	delete fEnemiesPosY;

	return 0;
}
  

Analicemos el código fuente que tenemos. Lo primero de todo, tenemos que inicializar la librería Allegro5, así como la funcionalidad de teclado, ratón, y la librería extra de imágenes. Las siguientes líneas realizan esta tarea:

main.cpp
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>

int main(int argc, char** argv) {
    al_init();
    al_install_keyboard();
    al_install_mouse();
    al_init_image_addon();
    // Resto del código
}
  

Sin haber llamado a estas funciones, no podemos usar la librería, así que, siempre tenemos que acordarnos de llamarlas. Es muy típico, por ejemplo, con la librería extra de imágenes, llamar a, por ejemplo, load_bitmap(), pero esa función está en la librería de imágenes. Dará un error al llamarla, si no hemos llamado previamente a al_init_image_addon()

Ahora, vamos a ver qué es uno de las mas importantes estructuras de Allegro 5: la cola de eventos. Una cola de eventos es una estructura en la que se van agregando eventos de diferentes fuentes, y se procesan en modo FIFO (First-In, First-Out), es decir, que el primer evento que llega es el primer evento que se procesa. Pero ¿qué es un evento? Pues casi toda la interacción que, con los métodos de entrada (teclado, ratón), se producen sobre la aplicación. Por ejemplo, un clic de ratón, o una pulsación de tecla. Incluso mover el ratón encima de la aplicación genera un evento. No solo existen eventos en los que el usuario interacciona con la aplicación, sino que, por ejemplo, a través de timers, se pueden disparar eventos a intervalos regulares, lo cual ya veremos que es muy útil y necesario para ciertos aspectos del juego.

Loe eventos están tipados. Eso significa que podemos saber exactamente qué evento se ha producido en nuestra aplicación en cada momento.

En nuestro programa vamos a necesitar un contador, que vamos a utilizar para redibujar la pantalla a intervalos regulares, de tal forma que si hemos estado pulsando el botón de mover la nave a la derecha, lo redibuje. Esa frecuencia de actualización, la vamos a establecer en 60 frames por segundo, a través de la variable iFPS. 60 frames por segundo es una buena frecuencia, pero permitiremos cambiar el valor de esta variable usando las tecla z para aumentar la frecuencia de refresco, y x para disminuirla. Podremos comprobar como cambia el tema. Demasiados FPS, y demasiados bitmaps en pantalla, producirán efectos no deseados. Esta es la razón por la que las empresas que fabrican tarjetas de vídeo siempre están sacando nuevo hardware. 60 FPS se considera óptimo para el ojo humano, pero hay gente que opina que cuantos mas frames por segundo, mejor. Como no queremos entrar en esos detalles, simplemente permitiremos cambiar ese dato en nuestro programa. Una cosa que si debemos tener en cuenta es que las cosas se moverán mas rápido a mas frames por segundo, y las pulsaciones continuadas de tecla serán más a mas frames por segundo. En la segunda parte del tutorial corregiremos esto, usando varias colas de evento.

El código para inicializar el contador, la cola de eventos, y poner la misma a escuchar eventos del teclado, el ratón y el contador, es el siguiente.

main.cpp
ALLEGRO_TIMER* pTimer = al_create_timer(1.0 / iFPS);
ALLEGRO_EVENT_QUEUE* pQueue = al_create_event_queue();
al_register_event_source(pQueue, al_get_keyboard_event_source());
al_register_event_source(pQueue, al_get_mouse_event_source());
al_register_event_source(pQueue, al_get_timer_event_source(pTimer));
al_start_timer(pTimer);
  

La función al_register_event_source() toma dos parámetros: un puntero a la cola de eventos, y el origen desde el que se encolarán eventos de dicha cola. A partir de ahí, la cola de eventos irá recibiendo, y podremos procesarlos.

Como ejemplo de lo anterior, y para entender mejor como funciona el sistema de eventos de Allegro5, vamos a escribir y guardar el siguiente programa. Podemos usarlo como campo de pruebas para eventos y diferentes funcionalidades que queramos probar en función de los mismos. He agregado algunas capturas para que se vea de qué va la cosa. Por cierto, que la referencia de eventos de Allegro5 está en la siguiente página: https://www.allegro.cc/manual/5/events.html. Experimenta con ellos, porque es la base de nuestra interacción con el usuario.

events.cpp
#include <iostream>
#include <cstdlib>
#include <string>
#include <allegro5/allegro.h>

int main(int argc, char** argv) {
    al_init();
    al_install_keyboard();
    al_install_mouse();
    
    std::string sAppTitle = "Allegro 5 events tutorial";
    ALLEGRO_DISPLAY* pDisplay = al_create_display(600, 600);
    al_set_window_title(pDisplay, sAppTitle.c_str());
    
    ALLEGRO_EVENT_QUEUE* pQueue = al_create_event_queue();
    al_register_event_source(pQueue, al_get_keyboard_event_source());
    al_register_event_source(pQueue, al_get_display_event_source(pDisplay));
    al_register_event_source(pQueue, al_get_mouse_event_source());    
    
    ALLEGRO_EVENT oEvent;
    
    while(true) {                
        al_wait_for_event(pQueue, &oEvent);
        int iEventType = oEvent.type;
        
        switch(iEventType) {
            
            case ALLEGRO_EVENT_KEY_DOWN: {
                int iKeyCode = oEvent.keyboard.keycode;                
                std::cout << "Se ha pulsado una tecla, el evento correspondiente es ALLEGRO_EVENT_KEY_DOWN" << std::endl;
                std::cout << "El código interno de Allegro para este caracter (scancode) es: " << iKeyCode << std::endl;
                std::cout << "El codigo ASCII correspondiente al caracter pulsado es: " << (iKeyCode + 96) << std::endl;
                std::cout << "Y el caracter correspondiente a ese codigo es: " << (char)(iKeyCode + 96) << std::endl << std::endl;
                
                if(oEvent.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
                    al_destroy_display(pDisplay);
                    al_destroy_event_queue(pQueue);
                    std::cout << "Pulsado ESC, saliendo del programa... " << std::endl;
                    return 0;
                }
                
            break; }
            
            case ALLEGRO_EVENT_KEY_UP: {
                int iKeyCode = oEvent.keyboard.keycode;                
                std::cout << "Se ha levantado una tecla pulsada, el evento correspondiente es ALLEGRO_EVENT_KEY_UP" << std::endl;
                std::cout << "El código interno de Allegro para este caracter (scancode) es: " << iKeyCode << std::endl;
                std::cout << "El codigo ASCII correspondiente al caracter pulsado es: " << (iKeyCode + 96) << std::endl;
                std::cout << "Y el caracter correspondiente a ese codigo es: " << (char)(iKeyCode + 96) << std::endl << std::endl;
            break; }
            
            case ALLEGRO_EVENT_MOUSE_AXES: {
                int iX = oEvent.mouse.x;
                int iY = oEvent.mouse.y;
                std::cout << "Se ha movido el ratón, a la posición (x,y)  (" << iX << "," << iY << ")" << std::endl << std::endl;                             
            break; }
            
            case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: {
                int iX = oEvent.mouse.x;
                int iY = oEvent.mouse.y;
                unsigned int iButton = oEvent.mouse.button;
                std::cout << "Se ha pulsado el ratón, en la coordenada (x,y) (" << iX << "," << iY << "), siendo el botón " << iButton << " el que se ha pulsado " << std::endl << std::endl;   
            break; }
            
            case ALLEGRO_EVENT_MOUSE_BUTTON_UP: {
                int iX = oEvent.mouse.x;
                int iY = oEvent.mouse.y;
                unsigned int iButton = oEvent.mouse.button;
                std::cout << "Se ha dejado de pulsar un botón del ratón, en la coordenada (x,y) (" << iX << "," << iY << "), siendo el botón " << iButton << " el que se ha pulsado " << std::endl << std::endl;
            break; }
            
            case ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY: {
                int iX = oEvent.mouse.x;
                int iY = oEvent.mouse.y;
                std::cout << "El ratón ha entrado en la ventana de la aplicación, en la coordenada (x,y) (" << iX << "," << iY << ")" << std::endl << std::endl;
            break; }
            
            case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: {
                int iX = oEvent.mouse.x;
                int iY = oEvent.mouse.y;
                std::cout << "El ratón ha salido de la ventana de la aplicación, en la coordenada (x,y) (" << iX << "," << iY << ")" << std::endl << std::endl;
            break; }
            
            /* default: {
            
            break; } */
        } 
    }
    
    al_destroy_display(pDisplay);
    al_destroy_event_queue(pQueue);
    return 0;    
}

  

Llegados a este punto, es importante entender que muchos juegos, especialmente aquellos que suceden en tiempo real, funcionan de la siguiente forma, a muy grandes rasgos:

  1. Inicializar el programa
  2. Poner a escuchar la cola de eventos
  3. Cada X tiempo, redibujar la pantalla, con los valores actuales
  4. Con cada nuevo evento, se altera el estado del programa.

Eso, como repito, a muy grandes rasgos. No obstante, en nuestro programa, ese código es realmente así, hace lo que está listado anteriormente. Veamos a continuación algo más de código de interés en nuestra aplicación:

main.cpp
al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP);
ALLEGRO_BITMAP* pBitmap = al_load_bitmap("spaceship_2.png");	
unsigned int iEnemies = 24;	
ALLEGRO_BITMAP** arrPtrEnemies = new ALLEGRO_BITMAP*[iEnemies];	
float* fEnemiesPosX = new float[iEnemies];
float* fEnemiesPosY = new float[iEnemies];

int xPlus = 1;
int yPlus = 1;
int iEnemiesPerRow = 8;

for (unsigned int i = 0; i < iEnemies; i++) {		
	if (i % iEnemiesPerRow == 0) { ++yPlus; xPlus = 1; }
	fEnemiesPosX[i] = xPlus * 75.0f;
	fEnemiesPosY[i] = yPlus * 75.0f;
	arrPtrEnemies[i] = al_load_bitmap("spaceship_enemy_2.png");
	++xPlus;
}
	
ALLEGRO_BITMAP* pDoubleBuffer = al_create_bitmap(gbl_iAppWidth, gbl_iAppHeight);
ALLEGRO_COLOR oBlack = al_map_rgb(0, 0, 0);
  

La función al_set_new_bitmap_flags establece como se crean todos los bitmaps cuando se llama a al_create_bitmap y al_load_bitmap. Esto es importante, porque podemos establecer muchas características, por ejemplo, en este caso, que las imágenes se carguen en la memoria de vídeo. En realidad, la línea es innecesaria, porque esta es la manera en la que se crean los bitmaps por defecto en Allegro5, pero ahí la dejo.

Inicializamos las imágenes de la nave y las de los enemigos, de las que creamos 24 bitmaps, en tres filas de ocho. Estos enemigos se moverán hacia arriba o hacia abajo, y, cuando lleguen al límite de la pantalla, volverán a aparecer por el otro lado. Fijémemos ahora en el resto del código de la aplicación:

main.cpp
ALLEGRO_EVENT oEvent;
while (true) {

	al_wait_for_event(pQueue, &oEvent);

	if (oEvent.type == ALLEGRO_EVENT_TIMER && oEvent.timer.source == pTimer) {
              // ....
        }

        if (oEvent.type == ALLEGRO_EVENT_KEY_DOWN) {
              // ....
        }

        if (oEvent.type == ALLEGRO_EVENT_KEY_UP) {
              // ....
        }

        if (oEvent.type == ALLEGRO_EVENT_KEY_CHAR) {
              // ....
        }
}	
  

Ahí está nuestra cola de eventos, escuchando cualquier cosa que le hayamos dicho que escuche. Esto se hace llamando a al_wait_for_event, siendo el primer parámetro la cola de eventos en la que queremos que se registre el evento, y una referencia a una estructura de tipo ALLEGRO_EVENT. Tras su paso por esta función, la estructura muta, y adquiere una serie de campos, siendo el más importante, type, pues todos los eventos lo tienen, y nos permite identificar de qué tipo es.

Por último, para ilustrar que podemos filtrar mas con los eventos, especifico como ejemplo que nos aseguremos de que el contador del que viene el evento es el que hemos inicializado. Más adelante veremos por qué hacemos esto, pues tendremos múltiples contadores en el código, y aunque la propiedad type de todos ellos es de tipo ALLEGRO_EVENT_TIMER, no querremos procesarlos todos igual.

Y por último, una cosa curiosa pero importante. El evento ALLEGRO_EVENT_KEY_CHAR registra las pulsaciones continuas, pero tiene un retardo para empezar a detectarlas. Eso lo podemos comprobar dejando pulsadas las teclas + o - del teclado numérico, y viendo que, pasado un tiempo, se registra la pulsación constante.

Para poder mover la nave, y dado como se juegan los juegos, el usuario dejará pulsada la tecla DERECHA cuando quiera mover la nave hacia allá, y lo que esperará es que, en cuanto pulsa la tecla, la nave se mueva, y que en cuanto la suelte, la nave se pare. Esto no se puede lograr con el evento ALLEGRO_EVENT_KEY_CHAR, porque, aunque detecta bien cuando la tecla se ha dejado de pulsar, el retardo inicial es letal para la interacción con el usuario y que el propio juego sea jugable. Así que lo que hacemos es: Declaramos cuatro variable boolean que nos dicen cual es la dirección del movimiento. El evento ALLEGRO_EVENT_KEY_DOWN registra una única pulsación de tecla, así que ponemos esa variable a true. Al dejar de pulsar el usuario la tecla, se genera el evento ALLEGRO_EVENT_KEY_UP, con lo que la volvemos a poner a false. Este pequeño truco funciona muy bien, pero si empezamos a incrementar demasiado la resolución, veremos como el programa se va volviendo cada vez mas lento.

El resto del código fuente debería ser muy sencillo de entender si se sigue el tutorial paso a paso, y se puede bajar del siguiente repositorio de GitHub: https://github.com/aalmunia/allegro5-timers-events

Llegamos al final de la primera parte de este largo tutorial. Recomiendo trastear y modificar el código al gusto de cada uno, para ir experimentando con las distintas funciones de la librería Allegro5, cuya referencia podemos encontrar aqui: https://www.allegro.cc/manual/5/index.html. En la segunda parte del tutorial, veremos como implementar todo esto en el paradigma POO de C++, e integrar utilidades a nuestra librería.

Leave a Comment

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