Allegro.pas

Basic game structure

◀ Introduction | Graphics ▶

The "Hello, World!" example'

We'll begin with a "Hello, World!" program:

PROGRAM HelloWorld;
(* First Allegro program. *)

USES
  allegro5, al5font;

VAR
  Timer: ALLEGRO_TIMERptr;
  Queue: ALLEGRO_EVENT_QUEUEptr;
  Event: ALLEGRO_EVENT;
  Display: ALLEGRO_DISPLAYptr;
  Font: ALLEGRO_FONTptr;

  Redraw, EndProgram: BOOLEAN;

BEGIN
  al_init;
  al_install_keyboard;

  Timer := al_create_timer (1 / 30);
  Queue := al_create_event_queue;
  Display := al_create_display (320, 200);
  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 (Display));
  al_register_event_source (Queue, al_get_timer_event_source (Timer));

  Redraw := TRUE;
  EndProgram := FALSE;

  al_start_timer (Timer);
  REPEAT
    al_wait_for_event (Queue, @Event);

    IF Event.ftype = ALLEGRO_EVENT_TIMER THEN
      Redraw := TRUE
    ELSE IF (Event.ftype = ALLEGRO_EVENT_KEY_DOWN)
    OR (Event.ftype = ALLEGRO_EVENT_DISPLAY_CLOSE)
    THEN
      EndProgram := TRUE;

    IF Redraw AND al_is_event_queue_empty (Queue) THEN
    BEGIN
      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
    END
  UNTIL EndProgram;

  al_destroy_font (Font);
  al_destroy_display (Display);
  al_destroy_timer (Timer);
  al_destroy_event_queue (Queue)
END.

If you're using either Lazarus or Delphi, create a new simple command line application.  Don't create a GUI application or one that uses a TApplication object or it won't work correctly!

Do not forget to indicate where Allegro.pas is installed.  On Lazarus and Delphi you must look at the Project configuration.  If you're using Free Pascal in command-line use the -Fu command like this:

fpc -Fuallegro5 helloworld.pas

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.

Used units

USES
    allegro5, al5font;

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. 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.

Timer := al_create_timer (1 / 30);
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 / 30 is referring to...

Display := 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.

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 (Display));
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:

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!

Redraw := TRUE;
EndProgram := FALSE;

al_start_timer(timer);
REPEAT
    ...

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.ftype = ALLEGRO_EVENT_TIMER THEN
  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.ftype = ALLEGRO_EVENT_KEY_DOWN)
OR (Event.ftype = ALLEGRO_EVENT_DISPLAY_CLOSE)
THEN
  EndProgram := TRUE;

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

They set the EndProgram variable to TRUE.  We'll see why later.

IF Redraw AND al_is_event_queue_empty (Queue) THEN
BEGIN
  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
END

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:

UNTIL EndProgram;

Remember the variable we set when we catched the keyboard or the window events?  Here we use it to know if the loop shoud be terminated.  That's our "press any key to exit" mechanism sorted!

Shutdown

  al_destroy_font (Font);
  al_destroy_display (Display);
  al_destroy_timer (Timer);
  al_destroy_event_queue (Queue)
END.

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.

An improved example

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:

PROGRAM HelloWorld;
(* First Allegro program. *)

USES     allegro5, al5font;

VAR   Timer: ALLEGRO_TIMERptr;   Queue: ALLEGRO_EVENT_QUEUEptr;   Event: ALLEGRO_EVENT;   Display: ALLEGRO_DISPLAYptr;   Font: ALLEGRO_FONTptr;

  Redraw, EndProgram: BOOLEAN;

BEGIN   IF NOT al_init THEN   BEGIN     WriteLn ('Couldn''t initialize Allegro.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;

  IF NOT al_install_keyboard THEN   BEGIN     WriteLn ('Couldn''t initialize keyboard.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;

  Timer := al_create_timer (1.0 / 30.0);   IF NOT Assigned (Timer) THEN   BEGIN     WriteLn ('Couldn''t initialize timer.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;

  Queue := al_create_event_queue;   IF NOT Assigned (Queue) THEN   BEGIN     WriteLn ('Couldn''t initialize queue.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;   Display := al_create_display (640, 480);

  IF NOT Assigned (Display) THEN   BEGIN     WriteLn ('Couldn''t initialize display.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;

  Font := al_create_builtin_font;   IF NOT Assigned (Font) THEN   BEGIN     WriteLn ('Couldn''t initialize font.');     WriteLn ('Press [Enter] to close.');     Halt (1)   END;

  al_register_event_source (Queue, al_get_keyboard_event_source);   al_register_event_source (Queue, al_get_display_event_source(Display));   al_register_event_source (Queue, al_get_timer_event_source (Timer));

  Redraw := TRUE;   EndProgram := FALSE;

  al_start_timer (Timer);   REPEAT     al_wait_for_event (Queue, @Event);

    CASE Event.ftype OF     ALLEGRO_EVENT_TIMER:     { Game logic goes here. }       Redraw := TRUE;     ALLEGRO_EVENT_KEY_DOWN, ALLEGRO_EVENT_DISPLAY_CLOSE:       EndProgram := TRUE;     END;

    IF Redraw AND al_is_event_queue_empty (Queue) THEN     BEGIN       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     END   UNTIL EndProgram;

al_destroy_font (Font); al_destroy_display (Display); al_destroy_timer (Timer); al_destroy_event_queue (Queue) END.

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

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

◀ Introduction | Graphics ▶