Cross-Platform Game Development for C++ Developers, Part II: The Allegro Platform

Good news, would-be game developers: I am extending my introduction to cross-platform game development (http://www.codeguru.com/Cpp/misc/samples/games/article.php/c10339/) into a weekly series. This week, I highlight the Allegro (Allegro Low LEvel Game ROutines) open source library, going into technical depth and providing a brief demo, so you can determine whether it's the right platform for you.

An Engine for Many Environments

Allegro began on the venerable Atari ST platform in the late 1980s and quickly migrated to the popular DJGPP environment (a 32-bit MS-DOS extender popular in the early 90s). Since then, Allegro has been ported to most popular Windows C++ development environments including VS, MinGW, Cygwin, and Borland C++. Other platforms that support it include Linux, BeOS, QNX, Mac OSX, and nearly any other Unix platform with X11 libraries.

Allegro can render into a variety of bitmap and hardware-accelerated environments, such as DirectX, XWindows, SVGAlib, FreeBE/AF, CGDirectDisplay, QuickDraw, and others. Allegro does not attempt to provide its own 3D environment or emulation thereof, but OpenGL can be easily integrated using the AllegroGL library, which provides a GLUT-like interface (including extension management).

An Extremely Brief Feature Overview

Before delving directly into APIs, a quick look under the hood is in order:

  • Drawing functions down to the pixel level, including flat shaded, gouraud shaded, texture mapped, and z-buffered polygons and circles, floodfill, bezier splines, patterned fills, sprites, blitting, bitmap scaling and rotation, translucency/lighting, and text output with proportional fonts
  • FLI/FLC animation player (a format with better compression characteristics than MPEG for computer-generated animations)
  • Plays background MIDI music and up to 64 simultaneous sound effects, and can record sample waveforms and MIDI input (Sound platform support includes WaveOut, DirectSound, OSS, ESD, CoreAudio, and QuickTime, just to name a few)
  • Easy access to the mouse, keyboard, joystick, and high-resolution timer interrupts, including a vertical retrace interrupt simulator in the DOS version
  • Routines for reading and writing LZSS compressed files
  • Math functions, including fixed point arithmetic, lookup table trig, and 3D vector/matrix/quaternion manipulation
  • GUI dialog manager and file selector
  • Built-in support for 16-bit and UTF-8 format Unicode characters

Let's Get This Engine Started!

In Allegro use, as in many other game scenarios, the overall structure consists of pre-game initialization, the game loop, and post-game cleanup. Initialization means both the Allegro startup code and the basics of loading or generating your game level at the starting location. Allegro imposes very little overhead in terms of writing your initialization code (see Figure 1).

If you need a lot of screen real estate, it is polite to first inquire with get_gfx_mode_list() to find the largest mode available:

#include <allegro.h>       // must come AFTER system headers
   set_color_depth(32);    // Defaults to 8-bit color otherwise
   if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0) {
      abort_on_error("Couldn't set a 32 bit color resolution");
   }

The last two parameters of set_gfx_mode() specify the size of the virtual buffer within which your graphics screen lies. This makes easy work of writing a side-scroller game where the terrain is continuously in motion. For example, you might make the virtual buffer, say, 20% wider to give you room to smoothly scroll in new sprites and terrain.

A Complete Allegro Example

This tutorial uses at Kee-Yip Chan's "SnookerClone" demo, which is based on another demo of the same name by James Lohr. Figure 1 shows the demo's basic screen shot.

Figure 1. Kee-Yip Chan's "SnookerClone" Demo

This project shows off a variety of Allegro techniques, including animation, keyboard and mouse input, collision, and gameplay physics (for example, gravity). It utilizes three primary elements: a rotating wheel with eight arms, a big red circle controlled by the arrow keys, and blue circles that drop from the ceiling. The wheel pushes the red ball on contact, and the red ball interacts realistically when colliding with the blue ones. The whole thing is kind of a cross between a pinball machine and one of those executive toys with falling beads suspended in a clear solution.

The following is the code for the demo:

  1 #include <allegro.h>
  2 vector<Point> g_points;    // List of points aka balls. aka
                               // ball centers?
  3 vector<Joint> g_joints;    // List of physical objects,
                               // e.g. wheel and bumpers.
  4 kVec   g_accControl;
  5
  6 int main(void)
  7 {
  8    allegro_init();         // Initialize allegro.
  9    install_keyboard();     // Enable keyboard.
 10    install_mouse();        // Enable mouse.
 11    install_timer();        // Needed for show_mouse();
 12
 13    // Create a 800x600 non-fullscreen window.
 14    set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0);
 15
 16    set_window_title("Kee-Yip Chan's Snooker Clone");
 17    text_mode(-1);    // Text will be drawn on transparent
                         // background.
 18
 19    BITMAP* buffer = create_bitmap(SCREEN_W, SCREEN_H);
       // Create a bitmap for double buffering.
 20
 21     // Initialize data.
 22    create_joints(g_joints);    // register hardcoded screen
                                   // positions for wheel,
 23                                // floor, and bumpers
 24
 25    // Create points aka balls: player ball and three blue
 26    // balls position, velocity, size, and mass.
 27    g_points.push_back(Point(kVec(100, 300),kVec(0, 0),16, 10));
      // Player.
 28    g_points.push_back(Point(kVec(50, 40), kVec(0, 0),12, 5));
       // medium ball.
 29    g_points.push_back(Point(kVec(80, 40), kVec(0, 0) 12, 5));
       // medium ball.
 30    g_points.push_back(Point(kVec(110, 40),kVec(0, 0),6, 1));
       // small ball.
 31
 32    // Main loop, exit when ESC is pressed.
 33    while(!key[KEY_ESC]) {            // Check input.
 34       if(key[KEY_UP])
 35           g_accControl.y = -0.07;    // Jet pack. Accelerate
                                         // upwards
 36       if(key[KEY_LEFT])
 37           g_accControl.x = -0.07;    // Walk left. Accelerate
                                         // to the left.
 38       if(key[KEY_RIGHT])
 39           g_accControl.x = 0.07;     // Walk right. Accelerate
                                         // to the right.
 40
 41       static bool leftMousePressed = false,
                      rightMousePressed = false;
 42       if(mouse_b & 1) { // Left mouse button pressed.
 43           if(!leftMousePressed){
 44              leftMousePressed = true;    // Create a new ball.
 45              g_points.push_back(Point(kVec(mouse_x, mouse_y),
                                    kVec(0, 0), 12, 5));
 46              }
 47           }
 48       if(!(mouse_b & 1))
 49         // Ensures that it doesn't repeat the mouse press;
 50         // otherwise, streams of new balls would gush out.
 51           leftMousePressed = false;
 52       if(mouse_b & 2) {    // Right mouse button pressed.
 53             if(!rightMousePressed){
 54                 rightMousePressed = true;    // Create a new
                                                 // ball.
 55                 g_points.push_back(Point(kVec(mouse_x, mouse_y),
                                             kVec(0, 0), 6, 1));
 56             }
 57         }
 58       if(!(mouse_b & 2))
 59         // Ensures that it doesn't repeat the mouse press;
 60         // otherwise, streams of new balls would gush out.

 61         rightMousePressed = false;
 62
 63       doPhysics();
 64
 65       // Rendering: Erase the buffer so we can use it again;
          // otherwise, old images will linger.
 66       // White color for clarity.
 67       clear_to_color(buffer, makecol(255, 255, 255));
 68       for(unsigned i = 0; i < g_points.size(); i++) {
            // Draw points.
 69         // Draw a solid ball.
 70         circlefill(buffer,    // Draw to buffer.
 71                  g_points[i].position.x,
                     g_points[i].position.y,
                     // Point's position aka ball's center.
 72                  g_points[i].size,    // Radius.
 73                  (i == 0) ? makecol(255, 0, 0) :
                                makecol(0, 0, 255));
                     // Red if player; otherwise, blue.
 74
 75         // Draw an outlined ball.
 76         circle(buffer,    // Draw to buffer.
 77                   g_points[i].position.x,
                      g_points[i].position.y,
            // Point's position aka ball's center.
 78                   g_points[i].size,     // Radius.
 79                   makecol(0, 0, 0));    // Red if player;
 80                                         // otherwise, blue.
 81         }
 82
 83         // Draw joints.
 84         for (unsigned i = 0; i < g_joints.size(); i++)
 85            line(buffer,  // Draw to buffer.
 86                   g_joints[i].p1.x, g_joints[i].p1.y,
                      // Point 1.
 87                   g_joints[i].p2.x, g_joints[i].p2.y,
                      // Point 2.
 88                   makecol(0, 0, 0));    // Black color.
 89               );
 90
 91         // Print instructions.
 92         textout(buffer, font, "Left Mouse Button - new big ball
                                   Right Mouse Button - new small ball",
 93               125, 1, makecol(0, 0, 0));
 94
 95         textout(buffer, font, "Arrow Keys - move red ball",
 96               300, 592, makecol(0, 0, 0));
 97
 98         show_mouse(buffer);    // Draw the mouse cursor.
 99
100         draw_sprite(screen, buffer, 0, 0);
            // Draw the buffer onto the screen.
101       }    // end while
102
103     return 0;
104
105 }END_OF_MAIN();

Cross-Platform Game Development for C++ Developers, Part II: The Allegro Platform

Lines 33-101 comprise the classic game loop model of programming. Play continues until the player hits the ESC key to exit. Simultaneous keyboard input is supported in lines 34-39, because you can hold down both up and left keys to get roughly diagonal motion.

In lines 41-61, the mouse action is captured in global variables mouse_b (for buttons), mouse_x, and mouse_y. If you had been using a scroll mouse, you would have mouse_z available too. A bit of hand-coded de-bouncing logic also ensures that only one ball drops per mouse-down event.

Line 63 calls doPhysics(), whose purpose is to spin the wheel's line segments, update ball positions, detect ball collisions, and alter their direction vectors appropriately. This is a somewhat intense module (350 lines of math code), but it is an elegant implementation that you should study on your own.

The remaining code, lines 65-101, does the rendering and would certainly belong in its own function body in a non-trivial demo. This renders in the classic double-buffer paradigm, whereby the next incremental screen change is calculated and drawn off-screen then swapped in at the last millisecond (line 100). This both insures visual continuity and reduces annoying flicker where objects would appear to be drawn randomly. Within the rendering code, the line() and circlefill() calls are relatively straightforward: circlefill() takes x, y, radius, and fill color as parameters.

The textout_ex() function can do a bit more than textout() (shown in lines 92-96), allowing you to specify foreground and background colors. Allegro provides routines for loading fonts directly from GRX format .fnt files, 8x8 or 8x16 BIOS format .fnt files, bitmap images, and datafiles. Alternatively, you can import a multiple-range Unicode font by writing a .txt script that specifies a number of different source files for each range of characters. If you want TrueType support, you need the AllegroTTF or equivalent add-in.

Last, but certainly not least, draw_sprite() on line 100 does a masked copy of the newly rendered bitmap to the screen object you created way back on Line 14. A masked copy means that only the non-transparent colored pixels are copied. In this example, I'm fairly sure it devolves to a "blit" (block copy) transfer.

Audio for Allegro

The snooker demo looked at some of the most rudimentary graphics and I/O functions but did not touch on Allegro's audio toolkit. The MIDI mixing, sound effects, and recording API meet or exceed almost every other dedicated sound library I've ever run across. Allegro audio apps abound, including WinCab, an MP3 and OGG Vorbis music jukebox, and LouTronic Rhythm Box, a drumbeat generator synth with fully tweakable snare, bass drum, and hi hat. I'll review just a small portion of the Allegro audio API here.

Every program that uses audio should call reserve_voices() to specify the number of voices that the digital and MIDI sound drivers use, respectively. Later on, you can control the mixing of these audio channels.

You can insert an audio track as easily as this:

MIDI *midFile =  load_midi("myfile.mid');
play_midi(midFile, TRUE);    // loop continuously

For more sophisticated needs, you can install one of three hook functions that allow you to intercept MIDI player events. If set to anything other than NULL, these routines will be called for each MIDI message, meta-event, and system-exclusive data block, respectively.

Allegro's digital audio system is designed from the ground up to be extensible. You can easily install readers and writers to handle new or variant audio file types, for example:

register_sample_file_type("mp3", load_mp3, NULL);    // install my
                                                     // MP3 reader

Digital audio can be edited on the fly, while it is playing. The following code alters the parameters of a sample while it is playing (useful for manipulating looped sounds):

void adjust_sample(const SAMPLE *spl, int vol, int pan, int freq,
                   int loop);

You can alter the volume, pan, and frequency, and clear the loop flag, which will stop the sample when it next reaches the end of its loop. If several copies of the same sample are playing, this will adjust the first one it comes across. If the sample is not playing, it has no effect.

Learn More About Allegro

[VictorV3.jpg]>  

If you prefer a step-by-step approach to learning Allegro, you can't do better than Game Programming All in One, 2nd Ed by Jonathan Harbour (ISBN 1-59200-383-4). It works through the core features of the Allegro game library and demonstrates writing code to load images, manipulate sprites, scroll the background, use double-buffering, read a joystick, detect collisions, and implement other core features of any game. Each new chapter builds on the game already in progress.

Along the way, you'll learn about game theory, including artificial intelligence, game physics, mathematics, algorithms, and multiplayer programming, which are integrated into the featured game. The publisher even includes a CD-ROM so you have everything you need to get started.

About the Author

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to sysop@HAL9K.com.



About the Author

Victor Volkman

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to sysop@HAL9K.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds