Basic game structure
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:
- 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!
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:
- ALLEGRO_EVENT_KEY_DOWN: the user just pressed a key.
- ALLEGRO_EVENT_DISPLAY_CLOSE: normally this means the user clicked the × button on the window.
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:
- ...the background and text colors to something other than black.
- ...the text itself.
- ...where the text appears in the window.
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.