Skip to content

Allegro Vivace: Basic game structure

Doug Thompson edited this page Aug 27, 2019 · 18 revisions

◀ Introduction


We'll begin by reminding ourselves of the "Hello World" program from the Quickstart:

View source
#include <allegro5/allegro5.h>
#include <allegro5/allegro_font.h>

int main()
{
    al_init();
    al_install_keyboard();

    ALLEGRO_TIMER* timer = al_create_timer(1.0 / 30.0);
    ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
    ALLEGRO_DISPLAY* disp = al_create_display(320, 200);
    ALLEGRO_FONT* font = al_create_builtin_font();

    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_display_event_source(disp));
    al_register_event_source(queue, al_get_timer_event_source(timer));

    bool redraw = true;
    ALLEGRO_EVENT event;

    al_start_timer(timer);
    while(1)
    {
        al_wait_for_event(queue, &event);

        if(event.type == ALLEGRO_EVENT_TIMER)
            redraw = true;
        else if((event.type == ALLEGRO_EVENT_KEY_DOWN) || (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE))
            break;

        if(redraw && al_is_event_queue_empty(queue))
        {
            al_clear_to_color(al_map_rgb(0, 0, 0));
            al_draw_text(font, al_map_rgb(255, 255, 255), 0, 0, 0, "Hello world!");
            al_flip_display();

            redraw = false;
        }
    }

    al_destroy_font(font);
    al_destroy_display(disp);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);

    return 0;
}

There's already quite a lot going on here, so let's explain it. Copy the above code into your text editor or IDE and view it in parallel to this page if you can; this'll help you keep track and try out your own modifications.

Header files

#include <allegro5/allegro5.h>
#include <allegro5/allegro_font.h>

Allegro consists of a core library with various addons. Most games written with Allegro make use of several of the addons.

Allegro's core doesn't include the facility to render text, so for our "hello world" program, we include the font addon.

Initialization

The first portion of the code sets up the bits of Allegro that are necessary to display the window, show the "hello world" text, and then quit when the user presses a key.

al_init();

Allegro has to set up some bare essentials before you use any of its functions - with some exceptions. al_init() does this.

al_install_keyboard();

Believe it or not, accepting keyboard input to your program isn't mandatory! al_install_keyboard() enables keyboard input.

ALLEGRO_TIMER* timer = al_create_timer(1.0 / 30.0);
ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();

We need a timer and an event queue to ensure the game runs at a consistent speed; we'll come back to this later on.

For bonus points, figure out what 1.0 / 30.0 is referring to...

ALLEGRO_DISPLAY* disp = al_create_display(320, 200);

al_create_display() tells Allegro to create a 320x200 pixel window. Try doubling the numbers, then recompile and note the change in size.

If you're on a newer Mac with a retina display, the window - and the "hello world" text - may be very small! Fret not, we'll remedy this later on.

ALLEGRO_FONT* font = al_create_builtin_font();

Allegro can read in various font formats (including TTF) - but for simplicity's sake, we've used the built-in pixel font that comes with it.

al_register_event_source(queue, al_get_keyboard_event_source());
al_register_event_source(queue, al_get_display_event_source(disp));
al_register_event_source(queue, al_get_timer_event_source(timer));

We'll get on to Allegro's events system shortly. Our calls to al_register_event_source() effectively say that we're interested in reacting to keyboard and display events in addition to the timer we set up earlier.

The main loop

The vast majority of games implement a main loop. True to its word, it's a piece of code that loops continuously and has the following responsibilities:

  • Accept input from the player (if given).
  • Advance the game's variables by a single frame - this is often called the game logic.
  • Render the graphics according to the variables.

These don't always happen in precisely the same order. The important thing is that the game calculates what'll happen within the next frame, and - ideally - renders that frame to the screen before it's time to calculate the frame after that.

If the draw isn't quick enough, you'll get frameskip. This won't be a concern when we're just writing "hello" to the screen over and over again though!

bool redraw = true;
ALLEGRO_EVENT event;

al_start_timer(timer);
while(1)
{
    // ...

The loop begins here. You'll notice that we started the timer just before the loop began.

al_wait_for_event(queue, &event);

At this point, your program's running process will block until something relevant happens.

You'll recall that we used al_register_event_source() above; this was to ensure that when the user presses a key or that it's time to advance a frame, al_wait_for_event() will stop blocking and return. The event variable will then contain useful information on what just happened.

if(event.type == ALLEGRO_EVENT_TIMER)
    redraw = true;

So, now we can see what the event was. In this case, if the timer fired an event - which it does once every 1/30th of a second - it's time to advance a frame. This means our game will aim to run at 30 frames per second.

But, because absolutely nothing in the "hello world" program changes between frames, we only need to set the redraw flag. We'll use that later on.

else if((event.type == ALLEGRO_EVENT_KEY_DOWN) || (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE))
    break;

As you can see, there are two conditions here that will cause the loop to break:

Have a look at the while loop in context; you'll notice that once the loop is broken, the program more or less immediately quits. That's our "press any key to exit" mechanism sorted!

if(redraw && al_is_event_queue_empty(queue))
{
    al_clear_to_color(al_map_rgb(0, 0, 0));
    al_draw_text(font, al_map_rgb(255, 255, 255), 0, 0, 0, "Hello world!");
    al_flip_display();

    redraw = false;
}

If the program hasn't rendered a frame since its "advance-one-frame" logic last executed - and there aren't any other events left to deal with - it's time to execute what is traditionally the most compute-intensive part of the game: the rendering itself.

Here, al_clear_to_color() clears the screen to black, al_draw_text() draws Hello world! in white to the top-left corner of the window, and finally al_flip_display() commits the result. We then reset the redraw flag until it's time to draw the next frame.

Also worth mentioning is al_map_rgb() - this is used to pick colors. The three values passed to it are (trivially) red, green, blue on a scale of 0 to 255.

Try changing:

  • ...the background and text colors to something other than black.
  • ...the text itself.
  • ...where the text appears in the window. (Hint)

Shutdown

al_destroy_font(font);
al_destroy_display(disp);
al_destroy_timer(timer);
al_destroy_event_queue(queue);

return 0;

Once the user has pressed a key or hit the × button on the window, all that's left to do is clean up the resources we created during the initialization. Nothing more to see here. return 0;


We've covered quite a lot with the "hello world" example, but there are already some things that'll need changing if we want to go beyond a simple greeting.

Perhaps most importantly, we aren't performing any checks in our initialization - which means the program could crash without explanation in the case of a minor error!

So, let's spruce it up:

View source
#include <stdio.h>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_font.h>

int main()
{
    if(!al_init())
    {
        printf("couldn't initialize allegro\n");
        return 1;
    }

    if(!al_install_keyboard())
    {
        printf("couldn't initialize keyboard\n");
        return 1;
    }

    ALLEGRO_TIMER* timer = al_create_timer(1.0 / 30.0);
    if(!timer)
    {
        printf("couldn't initialize timer\n");
        return 1;
    }

    ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
    if(!queue)
    {
        printf("couldn't initialize queue\n");
        return 1;
    }

    ALLEGRO_DISPLAY* disp = al_create_display(640, 480);
    if(!disp)
    {
        printf("couldn't initialize display\n");
        return 1;
    }

    ALLEGRO_FONT* font = al_create_builtin_font();
    if(!font)
    {
        printf("couldn't initialize font\n");
        return 1;
    }

    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_display_event_source(disp));
    al_register_event_source(queue, al_get_timer_event_source(timer));

    bool done = false;
    bool redraw = true;
    ALLEGRO_EVENT event;

    al_start_timer(timer);
    while(1)
    {
        al_wait_for_event(queue, &event);

        switch(event.type)
        {
            case ALLEGRO_EVENT_TIMER:
                // game logic goes here.
                redraw = true;
                break;

            case ALLEGRO_EVENT_KEY_DOWN:
            case ALLEGRO_EVENT_DISPLAY_CLOSE:
                done = true;
                break;
        }

        if(done)
            break;

        if(redraw && al_is_event_queue_empty(queue))
        {
            al_clear_to_color(al_map_rgb(0, 0, 0));
            al_draw_text(font, al_map_rgb(255, 255, 255), 0, 0, 0, "Hello world!");
            al_flip_display();

            redraw = false;
        }
    }

    al_destroy_font(font);
    al_destroy_display(disp);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);

    return 0;
}

We're already up to a beefy 91 lines of code! The eagle-eyed among you may have noticed that we've also increased the resolution to 640x480, introduced a niftier switch statement to deal with events, and added a done variable.

Bonus points: why do we now need that variable?

I do hope you're still with us. Read on.

Graphics ▶

You can’t perform that action at this time.