Coding Classes
Coding Classes
PROGRAMMING
DOI: [Link]
ISBN: 9781998944217 (paper) | 9781998944224 (pdf) |
9781998944231 (epub)
Remix name, Remix logo, and Remix book covers are not
subject to the Creative Commons license and may not be
reproduced without the prior and express written consent
of Athabasca University.
Acknowledgements ix
1. Introduction 1
1.1. Introduction to Video Games and Game
Programming 1
1.2. History of Video Games 3
1.3. Types of Video Games 10
1.4. Road Map of Game Development 26
1.5. Set Up Game Development IDE 40
1.6. The Allegro 5 Game Library 51
Exercises, Homework Questions, and Projects 78
2. Essentials of Game Programming with
Allegro 83
2.1. Common Structure of a C/C++ Program
with Allegro 5 83
2.2. Handling User Input 89
2.3. Collision Detection 103
2.4. Adding Sound Effects to Games 120
2.5. Timers and Game Timing 132
2.6. Using Files for Games 139
Exercises, Homework Questions, and Projects 152
3. Using Graphics in Games 155
3.1. Bitmap Fundamentals 156
3.2. Bitmaps Creation and Use in Allegro 5 159
3.3. Utilizing Bitmaps in a Program 163
3.4. Scaling and Rotating Bitmaps 167
3.5. Displaying a Section of a Bitmap and the
Concept of a Viewport 175
3.6. Advanced Graphics Techniques in Allegro 5 184
vi Practical Game Programming
[Link]
This page intentionally left blank
1
Introduction
[Link]
2 Introduction
• 2D graphics
• Event handling
• Keyboard, mouse, and joystick input
• Sound and audio streaming
• Timers and animation
• File operations
• Add-ons for images, fonts, primitives, and more
[Link]
Introduction 3
[Link]
4 Introduction
Home Consoles
Magnavox Odyssey (1972): The first home video game console,
developed by Ralph Baer and his team, featured simple games like
table tennis and shooting games.
Atari 2600 (1977): This console introduced interchangeable game
cartridges, allowing players to switch games easily. It became one
of the most popular consoles of the era.
Iconic Games
Space Invaders (1978): Developed by Tomohiro Nishikado, this game
became a cultural phenomenon and significantly boosted the
arcade industry.
Asteroids (1979): Created by Atari, this game featured vector graphics
and became one of the best-selling arcade games of all time.
Technological Advancements
Microprocessors: The introduction of microprocessors in the late
1970s allowed for more complex games and better graphics.
ROM Cartridges: These enabled consoles like the Atari 2600 to offer a
variety of games without needing built-in hardware for each one.
1.2.3. Boom and Bust Cycles Between the 1970s and 1980s
The period between the 1970s and 1980s was a pivotal time for the video game
industry, marked by the following significant advancements and the emergence
of iconic games and consoles:
[Link]
Introduction 5
Arcade Boom: The late 1970s saw the rise of arcade games, with titles
like Space Invaders (1978) by Tomohiro Nishikado becoming a
massive hit and sparking the golden age of arcade video games.
Home Consoles: The Atari 2600 was released in 1977, revolutionizing
home gaming with its use of interchangeable cartridges.
Pac-Man (1980): Created by Namco, Pac-Man became a cultural
phenomenon and one of the most famous arcade games of all
time.
Donkey Kong (1981): Developed by Nintendo, this game introduced
the world to Mario and became a major success.
Video Game Crash of 1983: An oversaturation of low-quality
games led to a significant downturn in the video game market,
particularly in North America.
Nintendo Entertainment System (NES): Released in 1985, the NES
revitalized the home console market and introduced iconic games
like Super Mario Bros. and The Legend of Zelda.
Graphics and Sound: The transition from simple, blocky graphics to
more detailed and colourful visuals, along with improved sound
capabilities, enhanced the gaming experience.
Home Computers: The rise of home computers like the Commodore
64 and ZX Spectrum provided new platforms for game
development and distribution.
[Link]
6 Introduction
The crash and subsequent recovery reshaped the video game industry, leading
to the establishment of practices and standards that continue to influence game
development today.
Late 1980s
Nintendo Entertainment System (NES): The NES continued to
dominate the home console market, with games like Super Mario
Bros. 3 (1988) and The Legend of Zelda (1986) becoming massive hits.
Sega Genesis (1988): Known as the Mega Drive outside North
America, the Sega Genesis introduced 16-bit graphics and became
a strong competitor to the NES.
Handheld Gaming: The release of the Game Boy in 1989 by Nintendo
revolutionized portable gaming. It came bundled with Tetris, which
became a global phenomenon.
[Link]
Introduction 7
Early 1990s
Super Nintendo Entertainment System (SNES): Released in 1990
in Japan and 1991 in North America, the SNES brought advanced
graphics and sound capabilities, with iconic games like Super
Mario World and The Legend of Zelda: A Link to the Past.
Sega Genesis vs. SNES: The early 1990s saw the height of the console
wars between Sega and Nintendo, with both companies releasing
numerous hit games and marketing campaigns.
PC Gaming: The early 1990s also saw significant growth in PC gaming,
with titles like Doom (1993) and Myst (1993) pushing the boundaries
of graphics and gameplay.
Technological Advancements
16-bit Graphics: The transition from 8-bit to 16-bit graphics allowed
for more detailed and colourful visuals.
Sound and Music: Improved sound chips enabled more complex and
immersive audio experiences in games.
CD-ROM Technology: The introduction of CD-ROMs provided greater
storage capacity, allowing for more extensive and detailed games,
and CD-ROMs were incorporated into consoles, impacting Sega and
Nintendo.
Late 1990s
3D Graphics: The transition from 2D to 3D graphics was a major leap.
Games like Super Mario 64 (1996) and The Legend of Zelda: Ocarina of
Time (1998) showcased the potential of 3D environments.
PlayStation (1994): Sony’s PlayStation revolutionized gaming with
its CD-ROM format, allowing for larger and more complex games.
Titles like Final Fantasy VII (1997) and Metal Gear Solid (1998)
became iconic.
PC Gaming: The late 1990s saw the rise of influential PC games like
Half-Life (1998) and StarCraft (1998), which pushed the boundaries
of storytelling and multiplayer gaming.
[Link]
8 Introduction
Early 2000s
Online Gaming: The advent of broadband internet enabled online
multiplayer games. World of Warcraft (2004) and Halo 2 (2004) were
pivotal in popularizing online gaming.
New Consoles: The release of the PlayStation 2 (2000), Xbox (2001),
and Nintendo GameCube (2001) brought enhanced graphics and
new gameplay experiences.
Mobile Gaming: The early 2000s also saw the rise of mobile gaming
with devices like the Game Boy Advance (2001) and the Nintendo
DS (2004).
[Link]
Introduction 9
The video game industry is set to continue its expansion, driven by innov-
ation and the increasing integration of gaming into everyday life.
[Link]
10 Introduction
[Link]
Introduction 11
[Link]
12 Introduction
[Link]
Introduction 13
Short Play Sessions: Arcade games are typically designed for short
play sessions, making them ideal for quick entertainment.
[Link]
14 Introduction
[Link]
Introduction 15
[Link]
16 Introduction
[Link]
Introduction 17
[Link]
18 Introduction
The Witcher 3: Wild Hunt: Praised for its storytelling and detailed
world.
Final Fantasy VII Remake: A modern take on a classic JRPG.
[Link]
Introduction 19
[Link]
20 Introduction
[Link]
Introduction 21
Grand Strategy
Crusader Kings III: Focuses on dynasties, politics, and medieval
intrigue.
Europa Universalis IV: Deep global strategy spanning centuries.
Hearts of Iron IV: A WWII-focused grand strategy emphasizing
logistics and warfare.
Tower Defense
Plants vs. Zombies: Accessible and humorous tower defense
gameplay.
Kingdom Rush: Known for its polished mechanics and challenging
levels.
Bloons TD Series: A long-running, highly popular tower defense
franchise.
[Link]
22 Introduction
These utilities can be incredibly useful for creative projects, productivity, and
personalizing your digital workspace.
[Link]
Introduction 23
[Link]
24 Introduction
[Link]
Introduction 25
Legal Considerations
While emulators themselves are legal, downloading and using game ROMs
(the game files) can be legally complex. It’s important to only use ROMs (or
the software image file) for games that you own or that are legally avail-
able for download. Always respect copyright laws and the rights of game
developers.
[Link]
26 Introduction
[Link]
Introduction 27
Each of these stages requires collaboration and creativity, and the process
can vary depending on the size of the team and the scope of the game. Are you
interested in any specific part of game development?
C++: Known for its performance and control over system resources,
C++ is widely used in game development, especially for high-
performance games. It’s used in many game engines like Unreal
Engine.
C#: This language is popular because of its use in the Unity game
engine, which is one of the most widely used game development
platforms. C# is known for being relatively easy to learn and use.
Java: Java is versatile and can be used for both desktop and mobile
game development. It’s particularly popular for Android game
development.
Python: While not as common for high-performance games, Python
is great for beginners and is used in some game development
frameworks like pygame.
JavaScript: Essential for web-based games, JavaScript works well
with HTML5 and CSS3 to create interactive games that run within
browsers.
Lua: Often used as a scripting language in game engines like Unity,
Corona SDK, and Roblox, Lua is lightweight and easy to embed.
Each of these languages has its own community and resources, making it
easier to find tutorials and support as you learn.
To be an effective game developer, one also needs to be familiar with inte-
grated development environments (IDEs) and game engines.
There are several popular IDEs and game engines that developers use to
create games. Here are some of the commonly used ones.
[Link]
28 Introduction
Game Engines
Unity: One of the most popular game engines, Unity is known for
its versatility and ease of use. It supports both 2D and 3D game
development and is widely used for mobile, PC, and console
games.
Unreal Engine: Renowned for its high-quality graphics and realistic
physics, Unreal Engine is a favorite for AAA game development. It
features a visual scripting system called Blueprints, which allows
developers to create game logic without extensive coding.
Godot Engine: An open-source game engine that is gaining popularity,
especially among indie developers. Godot offers a powerful 2D
and 3D rendering engine and is known for its flexibility and ease
of use.
GameMaker Studio: Ideal for 2D game development, GameMaker
Studio is user-friendly and allows developers to create games with
minimal coding. It’s popular for indie and mobile games.
IDEs
Visual Studio: A powerful IDE used by many professional game
developers. It supports multiple programming languages and
integrates well with popular game engines like Unity and Unreal
Engine.
JetBrains Rider: Known for its robust code analysis and debugging
tools, Rider supports multiple languages and integrates with Unity
and Unreal Engine.
MonoDevelop: An open-source IDE that is simple and easy to use. It
supports multiple languages and integrates with Unity and Godot
Engine.
These tools provide a range of features that help streamline the game develop-
ment process, from coding and debugging to designing and testing.
Clear Goals and Objectives: Players should always know what they
need to achieve. Clear goals provide direction and motivation,
keeping players engaged.
[Link]
Introduction 29
These principles help ensure that a game not only is fun to play but also
provides a satisfying and memorable experience.
[Link]
30 Introduction
One may also use some specific tools to produce 2D or 3D animation, such
as the following:
[Link]
Introduction 31
[Link]
32 Introduction
[Link]
Introduction 33
[Link]
34 Introduction
Skills Involved
Sound Design: Creating and implementing sound effects that match
the game’s visual and interactive elements.
Music Composition: Composing original music that enhances the
game’s narrative and emotional impact.
Audio Scripting: Using scripts to control when and how sounds are
played, ensuring they align with game events and player actions.
[Link]
Introduction 35
Version Control Systems: Tools like Git are essential for managing
code changes and collaborating with other team members. They
help track progress and revert to previous versions if needed.
[Link]
36 Introduction
[Link]
Introduction 37
Knowledge Areas
Understanding Game Performance Metrics
Frame Rate (fps): Ensuring a high and stable frame rate for smooth
gameplay
CPU and GPU Usage: Balancing the workload between the CPU and
GPU to avoid bottlenecks
Memory Consumption: Efficiently managing memory to prevent
leaks and excessive usage
Optimization Techniques
Memory Management: Techniques like memory pooling and data
structure optimization to reduce overhead
Asset Streaming: Loading only necessary assets on-the-fly to reduce
load times and memory usage
Frame Rate Stabilization: Using methods like dynamic resolution
scaling and optimizing rendering paths
Skills
Programming Skills
Efficient Coding: Writing optimized code that runs efficiently on
various hardware
Knowledge of Game Engines: Understanding how to use and optimize
popular game engines like Unity and Unreal Engine
System Design
Asynchronous Loading: Implementing asynchronous loading to
improve user experience by reducing perceived load times
[Link]
38 Introduction
Tools
Profilers: Tools like Unity Profiler, Unreal Engine Profiler, and
external tools like NVIDIA Nsight
Debuggers: Tools like Visual Studio Debugger, GDB, and platform-
specific debuggers
[Link]
Introduction 39
Game Engines
A game engine is an extensive framework specifically designed for creating
games. It goes beyond libraries and frameworks by providing a comprehensive
solution.
Components of a game engine typically include:
• Unreal Engine
• Unity Engine
• Source Engine
• Quake Engine
• Reality Engine
[Link]
40 Introduction
1.5.1. Visual Studio Code with MinGW GCC, CMake, and Ninja
Visual Studio Code
Visual Studio Code (VS Code) is a popular, open-source code editor developed
by Microsoft. It’s known for its versatility, ease of use, and extensive features
that cater to a wide range of programming needs.
Those features include extensibility, with an integrated terminal, debug-
ging tools, IntelliSense tools for efficient coding, version controls for project
development, and customization options for the entire IDE, as well as Live
Share, which allows you to collaborate with others across the globe, such as
through GitHub.
MinGW GCC
MinGW-w64 is a popular and comprehensive development environment for
building native Windows applications. It offers the following key features:
[Link]
Introduction 41
CMake
CMake is a powerful and widely used open-source tool designed to manage the
build process of software projects.
Key Features
Here are some key aspects of CMake:
[Link]
42 Introduction
cmake_minimum_required(VERSION 3.10)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
add_executable(MyProject [Link])
cmake .
cmake --build .
CMake is a versatile tool that can significantly streamline the build pro-
cess for complex projects.
Ninja
Ninja is a small, high-speed build system designed to handle the build process
for large software projects efficiently.
Key Features
Here are some key aspects of Ninja:
Speed: Ninja is optimized for speed, making it ideal for large projects
that require fast incremental builds. Using it on top of CMake
[Link]
Introduction 43
Speed
Fast Builds: Ninja is designed for speed, focusing on quick
dependency checking and parallel execution. This makes it
significantly faster than traditional build systems like Make,
especially for incremental builds.
Efficient Dependency Management: Ninja efficiently handles
dependencies, ensuring that only the necessary parts of the
project are rebuilt, which further reduces build times.
[Link]
44 Introduction
Install MinGW-w64
GCC can be installed through MinGW installer:
[Link]
Introduction 45
4. Add the path of your MinGW-w64 bin folder to the Windows PATH
environment variable:
• Open Windows Settings and search for “Edit environment variables
for your account.”
• In the User variables section, select the Path variable and click Edit.
• Add the path to the MinGW-w64 bin folder (e.g., C:\msys64\ucrt64\
bin).
Install CMake
1. Download CMake from the official website.
2. Run the installer and follow the on-screen instructions to complete
the installation.
3. Add CMake to your system PATH during the installation process.
Install Ninja
Install Ninja on Windows
1. Download the binary from the Ninja releases page, or download Ninja
from the official GitHub repository.
2. Extract the downloaded file.
3. Add it to your system PATH.
cmake -
G Ninja.
ninja
EXAMPLE [Link]
cmake_minimum_required(VERSION 3.10)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
add_executable(MyProject [Link])
[Link]
46 Introduction
Install Allegro 5
1. Download Allegro 5
Visit the Allegro 5 download page and download the prebuilt binaries
for MinGW.
2. Extract Allegro 5
Extract the Allegro 5 archive to a directory of your choice—for
example, C:\allegro5.
3. Set Up Environment Variables
Add the path to Allegro’s bin directory (e.g., C:\allegro5\bin) to your
system’s PATH environment variable.
Install GCC
1. Update Package Lists
2. Install GCC
This command installs GCC, G++, and other essential development tools
([Link]
[Link]
Introduction 47
Install CMake
1. Install CMake
Alternatively, you can download the latest version from the CMake
website and follow the installation instructions ([Link]
.com/how-to-install-cmake-on-ubuntu-linux/).
Install Ninja
1. Install Ninja
Alternatively, you can download the latest version from the Ninja
releases page ([Link] and follow the
installation instructions.
Install Allegro 5
1. Install Allegro 5
Install GCC
1. Install Homebrew
Open the Terminal and run the following command to install
Homebrew, a package manager for macOS:
[Link]
48 Introduction
2. Install GCC
Once Homebrew is installed, run:
Install CMake
1. Install CMake
Use Homebrew to install CMake by running:
Install Ninja
1. Install Ninja
Use Homebrew to install Ninja by running:
Install Allegro 5
1. Install Allegro 5
Use Homebrew to install Allegro 5 by running:
Install Extensions
1. Open VS Code and go to the Extensions view (⇧⌘X or Ctrl + Shift + X).
2. Search and install the following extensions:
• C/C++ by Microsoft
• CMake Tools by Microsoft
[Link]
Introduction 49
HelloAllegro/
[Link]
src/
[Link]
2. Open your project folder in VS Code.
3. Open the [Link] file and add the following to the file and save:
# [Link]
cmake_minimum_required(VERSION 3.10)
project(HelloAllegro)
# Add executable
add_executable(hello_allegro src/[Link])
4. Create a [Link] file in the src directory with the following code:
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
[Link]
50 Introduction
int main() {
if (!al_init()) {
return—1;
}
al_init_font_addon();
al_init_ttf_addon();
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255, 255, 255),
320, 240, ALLEGRO_ALIGN_CENTER, "Hello, Allegro!");
al_flip_display();
al_rest(2.0);
al_destroy_font(font);
al_destroy_display(display);
return 0;
}
[Link]
Introduction 51
cd HelloAllegro
ninja
[Link]
52 Introduction
[Link]
Introduction 53
[Link]
54 Introduction
[Link]
Introduction 55
Initialization
Before using Allegro, you must call al_init. Some add-ons have their own
initialization: for example, al_init_image_addon, al_init_font_addon, and
al_init_ttf_addon.
To receive input, you need to initialize some subsystems like al_install_key-
board, al_install_mouse, and al_install_joystick.
Opening a Window
A call to al_create_display will open a window and return an ALLEGRO_DISPLAY.
To clear the display, call al_clear_to_color. Use al_map_rgba or al_map_
rgba_f to obtain an ALLEGRO_COLOR parameter.
Drawing operations are performed on a backbuffer. To make the operations
visible, call al_flip_display.
Display an Image
To load an image from disk, you need to have initialized the image I/O add-on
with al_init_image_addon. Then use al_load_bitmap, which returns an
ALLEGRO_BITMAP.
Use al_draw_bitmap, al_draw_scaled_bitmap, or al_draw_scaled_rotated_
bitmap to draw the image to the backbuffer. Remember to call al_flip_display.
[Link]
56 Introduction
Drawing Primitives
The primitives add-on provides some handy routines to draw lines (al_draw_
line), rectangles (al_draw_rectangle), circles (al_draw_circle), and so on.
[Link]
Introduction 57
Blending
To draw translucent or tinted images or primitives, change the blender state
with al_set_blender.
As with al_set_target_bitmap, this changes Allegro’s internal state (for the
current thread). Often, you’ll want to save some part of the state and restore it
later. The functions al_store_state and al_restore_state provide a convenient
way to do that.
Sound
Use al_install_audio to initialize sound. To load any sample formats, you will
need to initialize the acodec add-on with al_init_acodec_addon.
After that, you can simply use al_reserve_samples and pass the number of
sound effects typically playing at the same time. Then load your sound effects
with al_load_sample and play them with al_play_sample. To stream large pieces
of music from disk, you can use al_load_audio_stream so the whole piece will
not have to be preloaded into memory.
If this all sounds too simple and you can’t help but think about clipping and
latency issues, don’t worry. Allegro gives you full control over how much or little
you want its sound system to do. The al_reserve_samples function mentioned
here only sets up a default mixer and a number of sample instances, but you
don’t need to use it.
Instead, to get a “direct connection” to the sound system, you would use
an ALLEGRO_VOICE, but depending on the platform, only one such voice is
guaranteed to be available, and it might require a specific format of audio data.
Therefore, all sound can be first routed through an ALLEGRO_MIXER that is
connected to such a voice (or another mixer) and will mix all sample data fed to it.
You can then directly stream real-time sample data to a mixer or a voice using
an ALLEGRO_AUDIO_STREAM or play complete sounds using an ALLEGRO_
SAMPLE_INSTANCE. The latter simply points to an ALLEGRO_SAMPLE and
will stream it for you.
Code Samples
Text and Font
Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <cstdio>
[Link]
58 Introduction
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return—;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF addon!\n");
return—;
}
Create a display:
Load a font:
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255, 255, 255), 400, 300,
ALLEGRO_ALIGN_CENTRE, "Hello, Allegro!");
[Link]
Introduction 59
al_flip_display();
al_rest(5.0);
Clean up:
al_destroy_font(font);
al_destroy_display(display);
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <cstdio>
int main() {
if (!al_init()) {
fprintf(stderr, "Failed to initialize
Allegro!\n");
return—;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF
addon!\n");
return—;
}
[Link]
60 Introduction
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255, 255, 255), 400,
300, ALLEGRO_ALIGN_CENTRE, "Hello, Allegro!");
al_flip_display();
al_rest(5.0);
al_destroy_font(font);
al_destroy_display(display);
return 0;
}
This example initializes Allegro, sets up the font add-ons, creates a display,
loads a TrueType font, and draws text to the screen. It then waits for five seconds
before cleaning up and closing the display.
Graphics
Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <cstdio>
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return—1;
}
if (!al_init_image_addon()) {
[Link]
Introduction 61
Create a display:
Load an image:
// Draw a circle
al_draw_filled_circle(400, 300, 50, al_map_rgb(0, 255,
0));
[Link]
62 Introduction
Clean up:
al_destroy_bitmap(image);
al_destroy_display(display);
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <csdtio>
int main() {
if (!al_init()) {
fprintf(stderr, "Failed to initialize
Allegro!\n");
return—1;
}
if (!al_init_primitives_addon()) {
fprintf(stderr, "Failed to initialize primitives
addon!\n");
return—1;
}
if (!al_init_image_addon()) {
fprintf(stderr, "Failed to initialize image
addon!\n");
return—1;
}
[Link]
Introduction 63
return—1;
}
// Draw a circle
al_draw_filled_circle(400, 300, 50, al_map_rgb(0,
255, 0));
al_destroy_bitmap(image);
al_destroy_display(display);
return 0;
}
This example initializes Allegro, sets up the image add-on, creates a display,
loads an image, and draws both shapes and the image to the screen. It then
waits for five seconds before cleaning up and closing the display.
Using a Keyboard
// Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <cstdio>
[Link]
64 Introduction
return—1;
}
if (!al_install_keyboard()) {
fprintf(stderr, "Failed to initialize the
keyboard!\n");
return—1;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—1;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF
addon!\n");
return—1;
}
ALLEGRO_EVENT_QUEUE *event_queue =
al_create_event_queue();
if (!event_queue) {
fprintf(stderr, "Failed to create event queue!\n");
al_destroy_display(display);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
// Load a font:
[Link]
Introduction 65
while (running) {
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
case ALLEGRO_KEY_UP:
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255, 255,
255), 400, 300, ALLEGRO_ALIGN_CENTRE, "Up Key Pressed");
al_flip_display();
break;
case ALLEGRO_KEY_DOWN:
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255,
255, 255), 400, 300, ALLEGRO_ALIGN_CENTRE, "Down Key
Pressed");
al_flip_display();
break;
// Add more cases for other keys as needed
}
}
}
[Link]
66 Introduction
// Clean up:
al_destroy_font(font);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <csdtio>
int main() {
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return—1;
}
if (!al_install_keyboard()) {
fprintf(stderr, "Failed to initialize the
keyboard!\n");
return—1;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—1;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF
addon!\n");
return—1;
} ALLEGRO_DISPLAY *display = al_create_display(800,
600);
if (!display) {
fprintf(stderr, "Failed to create display!\n");
return—;
}
ALLEGRO_EVENT_QUEUE *event_queue =
al_create_event_queue();
[Link]
Introduction 67
if (!event_queue) {
fprintf(stderr, "Failed to create event
queue!\n");
al_destroy_display(display);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
while (running) {
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
case ALLEGRO_KEY_UP:
al_clear_to_color(al_map_rgb(0, 0,
0));
al_draw_text(font, al_map_rgb(255,
255, 255), 400, 300, ALLEGRO_ALIGN_CENTRE, "Up Key
Pressed");
[Link]
68 Introduction
al_flip_display();
break;
case ALLEGRO_KEY_DOWN:
al_clear_to_color(al_map_rgb(0, 0,
0));
al_draw_text(font, al_map_rgb(255,
255, 255), 400, 300, ALLEGRO_ALIGN_CENTRE, "Down Key
Pressed");
al_flip_display();
break;
// Add more cases for other keys as
needed
}
}
}
al_destroy_font(font);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
This example initializes Allegro, sets up the keyboard input, creates a dis-
play, and handles key-press events to display different messages based on the
key pressed. It then waits for the user to close the window or press the Esc key
before cleaning up and exiting.
Using a Joystick
// Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_joystick.h>
#include <cstdio>
[Link]
Introduction 69
return—1;
}
if (!al_install_joystick()) {
fprintf(stderr, "Failed to initialize the
joystick!\n");
return—1;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—1;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF
addon!\n");
return—1;
}
ALLEGRO_EVENT_QUEUE *event_queue =
al_create_event_queue();
if (!event_queue) {
fprintf(stderr, "Failed to create event
queue!\n");
al_destroy_display(display);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
[Link]
70 Introduction
al_register_event_source(event_queue,
al_get_joystick_event_source());
// Load a font:
ALLEGRO_FONT *font = al_load_ttf_font("[Link]", 36,
0);
if (!font) {
fprintf(stderr, "Could not load '[Link]'.\n");
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return—1;
}
while (running) {
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_AXIS) {
printf("Joystick axis %d moved to %f\n",
[Link], [Link]);
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_BUTTON_
DOWN) {
printf("Joystick button %d pressed\n",
[Link]);
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_BUTTON_
UP) {
printf("Joystick button %d released\n",
[Link]);
}
}
// Clean up:
al_destroy_font(font);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
[Link]
Introduction 71
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/joystick.h>
#include <cstdio>
int main() {
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return—1;
}
if (!al_install_joystick()) {
fprintf(stderr, "Failed to initialize the
joystick!\n");
return—1;
}
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font
addon!\n");
return—1;
}
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize TTF
addon!\n");
return—1;
}
ALLEGRO_EVENT_QUEUE *event_queue =
al_create_event_queue();
[Link]
72 Introduction
if (!event_queue) {
fprintf(stderr, "Failed to create event
queue!\n");
al_destroy_display(display);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_joystick_event_source());
while (running) {
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_
AXIS) {
printf("Joystick axis %d moved to %f\n",
[Link], [Link]);
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_
BUTTON_DOWN) {
printf("Joystick button %d pressed\n",
[Link]);
} else if ([Link] == ALLEGRO_EVENT_JOYSTICK_
BUTTON_UP) {
[Link]
Introduction 73
return 0;
}
Using Audio
// Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <cstdio>
if (!al_install_audio()) {
fprintf(stderr, "Failed to initialize audio!\n");
return—1;
}
if (!al_init_acodec_addon()) {
fprintf(stderr, "Failed to initialize audio
codecs!\n");
return—1;
}
[Link]
74 Introduction
if (!al_reserve_samples()) {
fprintf(stderr, "Failed to reserve samples!\n");
return—1;
}
// Clean up:
al_destroy_sample(sample);
al_uninstall_audio();
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <cstdio>
int main() {
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return—1;
}
[Link]
Introduction 75
if (!al_install_audio()) {
fprintf(stderr, "Failed to initialize
audio!\n");
return—1;
}
if (!al_init_acodec_addon()) {
fprintf(stderr, "Failed to initialize audio
codecs!\n");
return—1;
}
if (!al_reserve_samples(1)) {
fprintf(stderr, "Failed to reserve samples!\n");
return—1;
}
al_destroy_sample(sample);
al_uninstall_audio();
return 0;
}
[Link]
76 Introduction
This example initializes Allegro, sets up the audio add-ons, loads an audio
sample, plays it, and then waits for five seconds to ensure the sample plays
completely before cleaning up and exiting.
Using Files
// Include the necessary headers:
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <cstring>
#include <cstdio>
// Initialize Allegro:
if (!al_init()) {
al_show_native_message_box(NULL, "Error",
"Error", "Failed to initialize Allegro!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
return—1;
}
[Link]
Introduction 77
return—1;
}
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <cstring>
#include <cstdio>
int main() {
if (!al_init()) {
al_show_native_message_box(NULL, "Error",
"Error", "Failed to initialize Allegro!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
return—1;
}
[Link]
78 Introduction
al_fclose(file);
return 0;
}
This example initializes Allegro, opens a file for writing, writes a string
to the file, closes it, then reopens the file for reading, reads the content, and
prints it to the console. It also includes error handling to display messages if
any operation fails.
Some projects in this section of each chapter, including those using axis-
aligned bounding boxes (AABB), extend slightly beyond the textbook’s core
coverage. They are included to introduce widely used techniques in practical
game development and to provide learners with opportunities for further
exploration.
[Link]
Introduction 79
[Link]
80 Introduction
[Link]
Introduction 81
Creative Challenges
25. Interactive Art Tool
Build a pixel-art editor with tools (e.g., brush, colour picker) and save/
load functionality.
26. Procedural Music Visualizer
Analyze audio input (e.g., microphone) and generate real-time visual
effects (e.g., bars, particles) synchronized to the beat.
27. Physics-Based Puzzle Game
Create a game where players stack objects to reach a goal. Use
Allegro’s physics primitives or implement basic rigid body dynamics.
28. Roguelike Prototype
Design a procedurally generated dungeon crawler with permadeath,
loot, and enemy AI. Use turn-based movement.
29. 3D Rendering Experiment
Use Allegro’s OpenGL integration to render a rotating 3D cube. Add
texture mapping and lighting.
30. Final Project: Complete Game
Develop a polished game (e.g., platformer, puzzle, RPG) with menus,
sound, scoring, and at least three levels. Present a demo and submit
source code.
[Link]
This page intentionally left blank
2
Essentials of Game
Programming with Allegro
[Link]
84 Practical Game Programming
int main() {
initAllegro(); // call the defined function
ALLEGRO_DISPLAY* display = al_create_display(SCREEN_
WIDTH, SCREEN_HEIGHT);
[Link]
Essentials of Game Programming with Allegro 85
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_text(font, al_map_rgb(255, 255, 255), SCREEN_
WIDTH / 2, SCREEN_HEIGHT / 2, ALLEGRO_ALIGN_CENTER,
"Hello, World!");
al_flip_display();
al_destroy_font(font);
al_destroy_display(display);
return 0;
}
To understand the structure of this simple program, let’s explain the code
section by section.
[Link]
86 Practical Game Programming
Allegro consists of a core library with various functional add-ons. Most games
written with Allegro will need to make use of several add-on libraries to handle
functions that Allegro’s core doesn’t. This includes the facility to render text,
so for our “Hello World!” program, for an example, we needed to include the
Allegro font add-on.
Additionally, here, as we are doing input/output (I/O) file functions, we will
use an example of a proper C++ coding style, as seen in the use of std:: in our
error-checking line and as in this line:
The reason for defining these two constants, instead of directly using the
numbers 800 and 600, respectively, is to make the code more readable and easy
to maintain. For example, the width and height may appear in many places in
the program. When the constants are defined and used in place of the numbers
in the program, if you want to change the screen size, you only need to change
the definitions here. Otherwise, you would have to change the numbers in
every individual instance.
[Link]
Essentials of Game Programming with Allegro 87
key. In the following we will explain piece by piece what is needed to initialize
the system for this intended purpose.
al_init();
Allegro has to set up some bare essentials before you use any of its functions
in the specific Allegro library, and this is done with al_init(), except in the
following cases:
al_show_native_message_box
al_install_keyboard();
al_install_keyboard() enables keyboard input. Believe it or not, accepting key-
board input to your program isn’t mandatory!
int main() {
initAllegro(); // call the defined function
ALLEGRO_DISPLAY* display = al_create_display(SCREEN_
WIDTH, SCREEN_HEIGHT);
// the above is to create a window with defined the
width and height
[Link]
88 Practical Game Programming
Reset the screens colourmap (this is covered later in the textbook, so don’t
worry about it now):
al_clear_to_color(al_map_rgb(0, 0, 0));
Properly shut down the program by destroying the fonts and window used
to release the resources for other uses. This is done with the following:
al_destroy_font(font);
al_destroy_display(display);
return 0;
}
[Link]
Essentials of Game Programming with Allegro 89
2.2.1. Keyboard
Among the input devices, the keyboard is the most commonly used, espe-
cially for video games. Games running on computers may often use joysticks
for a better gameplay experience, but the game programs must handle user
input from a keyboard because almost all computers have a keyboard attached.
This section covers basic keyboard input handling using Allegro 5 for games
or interactive applications.
#include <allegro5/allegro.h>
int main() {
al_init(); // Initialize Allegro
al_install_keyboard(); // Enable keyboard input
ALLEGRO_EVENT_QUEUE* event_queue =
al_create_event_queue();
al_register_event_source(event_queue,
al_get_keyboard_event_source());
//...(create display, timers, etc.)
}
Event-Based Input
In Allegro 5, event-based input refers to handling input through an event-driven
system. This means that instead of continuously checking the state of input
devices, the program responds to events as they occur. Here’s how it works:
[Link]
90 Practical Game Programming
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
int main() {
if (!al_init()) return—
1;
if (!al_install_keyboard()) return—
1;
if (!al_install_mouse()) return—
1;
if (!al_init_primitives_addon()) return—
1;
[Link]
Essentials of Game Programming with Allegro 91
al_register_event_source(queue,
al_get_display_event_source(display));
al_register_event_source(queue,
al_get_keyboard_event_source());
al_register_event_source(queue,
al_get_mouse_event_source());
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
if ([Link] == ALLEGRO_KEY_
ESCAPE) running = false;
} else if ([Link] == ALLEGRO_EVENT_MOUSE_BUTTON_
DOWN) {
if ([Link] == 1) { // left mouse
button
last_click = { (float)[Link].x, (float)
[Link].y };
has_click = true;
}
}
[Link]
92 Practical Game Programming
al_destroy_event_queue(queue);
al_destroy_display(display);
return 0;
}
Explanation
Initialization: Initializes Allegro and its add-ons for keyboard, mouse,
and primitives.
Display and Event Queue: Creates a display and an event queue, then
registers the display, keyboard, and mouse as event sources.
Event Loop: Continuously waits for events and processes them. For
example, it exits the loop if the display is closed or the Esc key is
pressed.
Drawing: Clears the screen and draws a red circle where the mouse is
clicked, then updates the display.
ALLEGRO_KEYBOARD_STATE key_state;
al_get_keyboard_state(&key_state);
[Link]
Essentials of Game Programming with Allegro 93
WASD ALLEGRO_KEY_W
ALLEGRO_KEY_A
ALLEGRO_KEY_S
ALLEGRO_KEY_D
Space/Enter ALLEGRO_KEY_SPACE
ALLEGRO_KEY_ENTER
Modifiers ALLEGRO_KEY_SHIFT
ALLEGRO_KEY_CTRL
int main() {
al_init();
al_install_keyboard();
ALLEGRO_DISPLAY* display = al_create_display(800,
600);
ALLEGRO_EVENT_QUEUE* event_queue =
al_create_event_queue();
al_register_event_source(event_queue,
al_get_keyboard_event_source());
while (game_running) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
// Event-
based exit (ESC)
if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
if ([Link] == ALLEGRO_KEY_
ESCAPE) {
[Link]
94 Practical Game Programming
game_running = false;
}
}
// State-
based movement (arrow keys)
ALLEGRO_KEYBOARD_STATE state;
al_get_keyboard_state(&state);
if (al_key_down(&state, ALLEGRO_KEY_RIGHT)) x +=
2;
if (al_key_down(&state, ALLEGRO_KEY_LEFT)) x—
= 2;
// Draw
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_filled_rectangle(x, y, x+50, y+50, al_
map_rgb(255, 0, 0));
al_flip_display();
}
al_destroy_display(display);
return 0;
}
With the knowledge we have just learned about handling keyboard input,
we now can move to our next code example, which creates a bouncing ball
simulation, with position displayed on the upper-right corner of the win-
dow. The ball stays within a container box and the program exits when Q
is pressed. This will draw a colourful square on the screen and hold it until
the key is pressed:
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_primitives.h>
#include <sstream>
[Link]
Essentials of Game Programming with Allegro 95
int main() {
// Initialize Allegro and components
al_init();
al_init_font_addon();
al_init_ttf_addon();
al_init_primitives_addon();
al_install_keyboard();
// Ball properties
float ball_x = SCREEN_W / 2;
float ball_y = SCREEN_H / 2;
float dx = 4, dy = 4;
bool done = false;
bool redraw = true;
al_start_timer(timer);
// Main loop
while (!done) {
[Link]
96 Practical Game Programming
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
switch ([Link]) {
case ALLEGRO_EVENT_TIMER:
// Update ball position
ball_x += dx;
ball_y += dy;
case ALLEGRO_EVENT_KEY_DOWN:
if ([Link] == ALLEGRO_
KEY_Q) {
done = true;
}
break;
case ALLEGRO_EVENT_DISPLAY_CLOSE:
done = true;
break;
}
[Link]
Essentials of Game Programming with Allegro 97
// Draw ball
al_draw_filled_circle(ball_x, ball_y,
BALL_RADIUS,
al_map_rgb(255, 0, 0));
al_flip_display();
}
}
// Cleanup
al_destroy_font(font);
al_destroy_timer(timer);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
We need the fonts and primitives add-ons now. As long as your IDE has
been set up by following the instructions given in the previous section, you
can now directly build with VS Code, or compile the program with Allegro
dependencies in a terminal:
[Link]
98 Practical Game Programming
Compile and run the program. You should see a screen with a bouncing
object, a boundary box outline, and some text presenting the x and y position
variables.
2.2.2. Mouse
Mice are essential peripherals in computer gaming, offering precision and con-
trol across various game genres. Here’s a closer look at their role and features.
Key Features
DPI (Dots Per Inch): Higher DPI settings allow for more sensitive and
precise movements, which is crucial in fast-paced games.
Polling Rate: This measures how often the mouse reports its position
to the computer. A higher polling rate (e.g., 000 Hz) means more
responsive performance.
Programmable Buttons: Extra buttons can be customized for specific
in-game actions, enhancing gameplay efficiency.
Ergonomics: Comfortable design is essential for long gaming sessions
to prevent strain and injury.
[Link]
Essentials of Game Programming with Allegro 99
Mouse-Only Games
There are also many games designed to be played primarily with a mouse.
These include point-and-click adventures, puzzle games, and idle clickers.
Websites like CrazyGames and Poki offer a variety of mouse-only games that
you can enjoy.
Generally speaking, there are two ways you can choose to interpret mouse
movement:
if (!al_install_mouse()) {
fprintf(stderr, "Failed to initialize the
mouse!\n");
return—;
}
[Link]
100 Practical Game Programming
ALLEGRO_MOUSE_STATE state;
al_get_mouse_state(&state);
al_show_mouse_cursor(display);
2.2.3. Joystick
Joysticks are versatile input devices used in various types of games, especially
those that require precise control, such as flight simulators, space shooters,
and racing games. Here’s a bit more about them:
Types of Joysticks
Digital Joysticks: These are the simplest form, where the joystick can
be moved in four or eight directions. They are often used in arcade
games.
Analog Joysticks: These provide a range of motion and are more
precise, making them ideal for flight simulators and racing games.
HOTAS (Hands-On Throttle-And-Stick): These are advanced setups
that include a joystick and a separate throttle control, often used in
flight simulators for a more immersive experience.
[Link]
Essentials of Game Programming with Allegro 101
[Link]
102 Practical Game Programming
#include <allegro5/allegro.h>
#include <allegro5/allegro_joystick.h>
#include <stdio.h>
int main() {
al_init();
al_install_joystick();
if (!al_is_joystick_installed()) {
printf("Joystick not installed!\n");
return—1;
}
ALLEGRO_JOYSTICK_STATE state;
al_get_joystick_state(joystick, &state);
printf("Joystick name—%s\n",
al_get_joystick_name(joystick));
printf("Number of sticks—
%d\n",
al_get_joystick_num_sticks(joystick));
printf("Number of buttons—
%d\n",
al_get_joystick_num_buttons(joystick));
[Link]
Essentials of Game Programming with Allegro 103
}
}
al_uninstall_joystick();
return 0;
}
[Link]
104 Practical Game Programming
Here a case for a circle might be better, but as you see in figure 2.3,
it’s still not perfect, and there would be the extra math to determine
the edge of the circle. What this goes to show, though, is sometimes
when creating your graphics, you might want to consider their design
or their size to help with these issues. So, for example, I could have
made the bitmap more rectangular, like in figure 2.4. In that case, the
rectangular bounding box would be somewhat more accurate, and we
could still utilize the simpler code.
[Link]
Essentials of Game Programming with Allegro 105
Figure 2.1: Collision detection using bounding boxes. Illustrated by Kyle Flemmer.
Figure 2.2: Saucer image with a green bounding box. Illustrated by Walter Ridgewell.
[Link]
106 Practical Game Programming
Figure 2.3: Saucer image with a round bounding box. Illustrated by Walter Ridgewell.
Figure 2.4: Saucer image cropped with a rectangular bounding box. Illustrated by Walter
Ridgewell.
2. Narrow Phase
The narrow phase performs detailed collision checks on the
remaining pairs of objects. This phase uses precise algorithms to
determine if and where the objects intersect.
[Link]
Essentials of Game Programming with Allegro 107
while (true) {
// Update positions (example)
box.x += .0;
box2.y—= .0;
// Render
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(sprite, box.x, box.y, 0);
[Link]
108 Practical Game Programming
Since many games will need to utilize collision in some way, let’s look at
another example.
As with previous code examples, we have the usual basic constructs of code:
we have our setup and initialization phase: We load sprites, set initial positions,
and define movement parameters.
We then have our game loop functions, which here will do the following:
[Link]
Essentials of Game Programming with Allegro 109
Figure 2.5: Collision response with a print message. Image by Walter Ridgewell.
game we have a sprite at position (100, 150) on our screen with size (50 × 50)
and velocity (2, 3). The memory representation of that Sprite struct information
is then as shown in table 3.
In each iteration of our game loop, movement-wise, this sprite—we’ll
call it Sprite A—will move 2 pixels right and 3 pixels down per animation
frame.
[Link]
110 Practical Game Programming
x 100
y 150
width 50
height 50
dx 2
dy 3
This function checks if two sprites overlap (collide) on the screen. It uses
axis—aligned bounding box (AABB) collision detection, which is a fast method
for detecting rectangle-based collisions and returns a simple true or false con-
dition. Note the use of the logical AND operator (&&) in the return logic. If all
four conditions are true, then the sprites overlap, and the collision detection
function returns a “true” condition.
[Link]
Essentials of Game Programming with Allegro 111
But our Sprite A was moving diagonally downward, and Sprite B is not moving.
So let’s assume the movement path creates an imminent collision event.
With a complete overlap of Sprite B by Sprite A, the conditions then change
to
Since all conditions are now true, a collision is detected, and our function
returns true.
Figure 2.6: Sprites positioned apart: No collision detected. Illustrated by Kyle Flemmer.
[Link]
112 Practical Game Programming
Figure 2.7: Sprites are overlapping: Collision is detected. Illustrated by Kyle Flemmer.
Here, we check the four edge detections, and we are now using OR logic for
the return value, so if one of the edges of the sprite object is touching another’s,
we return a true. The detections are as follows:
[Link]
Essentials of Game Programming with Allegro 113
Figure 2.8: Sprites positioned apart, no collision detected. Illustrated by Kyle Flemmer.
Our function currently returns false, but our Sprite A is moving downward,
and Sprite B is stationary, so they will imminently touch.
With the contact of the top edge of Sprite B and the bottom edge Sprite A,
the conditions then change to
[Link]
114 Practical Game Programming
Figure 2.9: Sprites touch at edges: Collision is detected. Illustrated by Kyle Flemmer.
We now have a true condition, a collision is detected, and our function returns
true.
Here’s the example implementation in Allegro 5 using the overlap bound-
ing box collision detection. This program creates two moving rectangles and
detects collisions between them:
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <stdbool.h>
#include <stdio.h>
[Link]
Essentials of Game Programming with Allegro 115
int main() {
al_init();
al_init_primitives_addon();
al_init_font_addon();
al_init_ttf_addon();
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
while (true) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
if ([Link] == ALLEGRO_EVENT_TIMER) {
// Update sprite positions
sprite1.x += [Link];
[Link]
116 Practical Game Programming
sprite1.y += [Link];
sprite2.x += [Link];
sprite2.y += [Link];
// Collision detection
bool currently_colliding = check_
collision(&sprite1, &sprite2);
collided = currently_colliding;
was_colliding = currently_colliding;
redraw = true;
}
else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
break;
}
[Link]
Essentials of Game Programming with Allegro 117
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_filled_rectangle(sprite1.x,
sprite1.y,
sprite1.x + [Link], sprite1.y +
[Link], color1);
al_draw_filled_rectangle(sprite2.x,
sprite2.y,
sprite2.x + [Link], sprite2.y +
[Link], color2);
if (collided) {
al_draw_text(font, al_map_rgb(255, 255,
255), 400, 300,
ALLEGRO_ALIGN_CENTER, "We Collided");
}
al_flip_display();
}
}
// Cleanup
al_destroy_font(font);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
al_destroy_timer(timer);
return 0;
}
Collision detection is vital for creating interactive and realistic game environ-
ments. By using techniques like AABB, circle collision, and SAT, you can handle
various collision scenarios in your games.
[Link]
118 Practical Game Programming
Circle Collision
For circular objects, the collision detection is based on the distance between
their centers. The equation is:
{Collision} = sqrt{(A.x—
B.x)^2 + (A.y—
B.y)^2} < ([Link]
+ [Link])
[Link]
Essentials of Game Programming with Allegro 119
2. Overlap Check
For two polygons, project all vertices onto the axis and find the
minimum and maximum values for each polygon. If the intervals
overlap on all axes, the polygons collide.
d = P − V0 ∙ N
d2 = P2 − V0 ∙ N
These equations form the basis of many collision detection algorithms used
in games. For more detailed information, you can ask an AI assistant about
lectures on collision detection algorithms in video games.
[Link]
120 Practical Game Programming
[Link]
Essentials of Game Programming with Allegro 121
To use sound and music in Allegro games, you’ll need to initialize the audio
system, load audio files, and play them. Here’s a step-by-step guide.
Setting Up
1. Initialize Allegro and Audio Add-Ons
if (!al_init()) {
fprintf(stderr, "Allegro failed to init\n");
return—1;
}
al_install_keyboard();
al_install_audio();
al_init_acodec_addon();
al_reserve_samples(8);
[Link]
122 Practical Game Programming
Note also the use of two new functions here related to playback in
Allegro: the audio stream mode and the audio stream mixer. Here,
our music file is a larger continuous audio event (unlike a short
sound sample), and so it is a “stream” of audio. We denote “music”
as our “stream” and so set the play mode for that to LOOP. We then
take that music stream and feed it to the default Allegro sound
mixer. The Allegro mixer function lets us modify the playback
characteristics if we wish; here, we don’t make any changes to the
[Link]
Essentials of Game Programming with Allegro 123
audio and use the defaults. In a future chapter, we’ll look at some
of the uses of the mixer and its effect, but for now, just consider it a
requirement for the output of the sound—the software speaker, as it
were.
EXAMPLE CODE
Here’s a complete example demonstrating how to set up and play sound and
music in an Allegro game. It will start the music playback with a quick sound
effect at the beginning, then continue to play. At the end of the music, after a
brief pause, it will play again but with no sound effect and continue to do so
until the example is ended:
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_native_dialog.h> // Optional:
error dialogs
#include <cstdio>
int main() {
if (!al_init()) {
fprintf(stderr, "Allegro failed to init\n");
return—1;
}
al_install_keyboard();
al_install_audio();
al_init_acodec_addon();
al_reserve_samples(8);
[Link]
124 Practical Game Programming
return—1;
}
[Link]
Essentials of Game Programming with Allegro 125
// Cleanup
al_destroy_audio_stream(music);
al_destroy_sample(sfx);
al_destroy_display(display);
al_destroy_event_queue(queue);
al_uninstall_audio();
return 0;
}
2.4.3. Streaming
Streaming in video games can refer to three distinct concepts: live streaming
gameplay, game streaming services, and streaming media (the playback of
audio or video) within a game. In this subsection, we describe all three uses
of streaming in games, although only streaming media is directly related to
game programming and development.
1. Equipment
Good Gaming PC: Ensure your PC can handle both gaming and
streaming simultaneously.
Webcam and Microphone: Use for better interaction with your audience.
Capture Card: Use if you’re streaming from a console.
2. Software
OBS Studio: A free and open-source software for video recording and
live streaming.
Streamlabs OBS: A user-friendly version of OBS with additional features
for streamers.
3. Setting Up
Create an account on your chosen streaming platform.
Configure your streaming software with your platform’s stream key.
Set up scenes and sources in OBS to include your game, webcam, and
overlays.
4. Engage with Your Audience
Interact with viewers through chat.
Use alerts and notifications to acknowledge new followers,
subscribers, and donations.
[Link]
126 Practical Game Programming
Steam Remote Play: Stream games from your PC to other devices like
phones, tablets, and TVs using Steam Link.
NVIDIA GeForce NOW: Play your PC games from the cloud on
various devices. It supports a wide range of games from different
platforms.
Xbox Cloud Gaming (xCloud): Part of Xbox Game Pass Ultimate,
allowing you to stream Xbox games to your PC, phone, or tablet.
PlayStation Now: Stream a library of PlayStation games to your PC or
PlayStation console.
Streaming Audio
To stream audio in Allegro, you can use ALLEGRO_AUDIO_STREAM. This is
useful for playing large audio files or continuous audio streams without loading
[Link]
Essentials of Game Programming with Allegro 127
the entire file into memory. We saw the use of this in the previous example
code, but the steps here, once more, are:
al_install_audio();
al_init_acodec_addon();
al_reserve_samples(8);
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
[Link]
128 Practical Game Programming
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
if ([Link] == ALLEGRO_KEY_ESCAPE) {
running = false;
} else if ([Link] == ALLEGRO_KEY_
SPACE) {
playing = !playing;
al_set_audio_stream_playing(stream,
playing);
draw_ui(display, font, playing);
}
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_EXPOSE) {
// Redraw if the window is exposed
draw_ui(display, font, playing);
}
}
Streaming Video
For video streaming, Allegro does not have built-in support, but you can use
external libraries like FFmpeg to decode video frames and display them using
Allegro.
1. Set Up FFmpeg
Install FFmpeg and include it in your project.
2. Decode Video Frames
Use FFmpeg to decode video frames. This involves setting up FFmpeg
to read the video file and decode each frame.
3. Display Frames with Allegro
Convert the decoded frames to ALLEGRO_BITMAP and display them:
[Link]
Essentials of Game Programming with Allegro 129
[Link]
130 Practical Game Programming
#include <stdio.h>
#include <stdbool.h>
// Centered message
const char *msg = playing ? "Playing (SPACE to
pause)" : "Paused (SPACE t
ALLEGRO_COLOR col = playing ? al_map_rgb(30, 220,
120) : al_map_rgb(240,
int w = al_get_display_width(display);
int h = al_get_display_height(display);
int main() {
// Init core
if (!al_init()) { fprintf(stderr, "Failed to
initialize Allegro!\n"); ret
if (!al_install_audio()) { fprintf(stderr, "Failed to
initialize audio!\n
if (!al_init_acodec_addon()) { fprintf(stderr,
"Failed to init acodec add
if (!al_install_keyboard()) { fprintf(stderr, "Failed
to install keyboard
al_init_font_addon();
al_reserve_samples(16);
[Link]
Essentials of Game Programming with Allegro 131
// Built-
in font (no TTF dependency)
ALLEGRO_FONT *font = al_create_builtin_font();
if (!font) { fprintf(stderr, "Failed to create
font!\n"); al_destroy_disp
al_attach_audio_stream_to_mixer(stream,
al_get_default_mixer());
al_set_audio_stream_playmode(stream,
ALLEGRO_PLAYMODE_LOOP);
// Event queue
ALLEGRO_EVENT_QUEUE *queue = al_create_event_queue();
al_register_event_source(queue,
al_get_keyboard_event_source());
al_register_event_source(queue,
al_get_display_event_source(display));
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
[Link]
132 Practical Game Programming
// Cleanup
al_destroy_audio_stream(stream);
al_destroy_font(font);
al_destroy_event_queue(queue);
al_destroy_display(display);
al_uninstall_audio();
return 0;
}
[Link]
Essentials of Game Programming with Allegro 133
Types of Timers
1. Real-Time Timers
These timers measure actual elapsed time and are used for tasks that
need to happen at specific intervals, such as spawning enemies or
updating the game state.
2. Frame-Based Timers
These timers are based on the number of frames rendered. They
are useful for animations and movements that need to be consistent
regardless of the frame rate.
3. Event Timers
These timers trigger specific events after a set period. They are often
used for delayed actions, such as power-ups or timed events.
[Link]
134 Practical Game Programming
al_register_event_source(queue,
al_get_keyboard_event_source());
al_register_event_source(queue,
al_get_timer_event_source(timer));
al_start_timer(timer);
while (true) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
// Update game state
// Render frame
al_flip_display();
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
break;
}
}
while (true) {
double current_time = al_get_time();
if (current_time—
start_time >= delay) {
// Trigger event
start_time = current_time; // Reset timer
}
[Link]
Essentials of Game Programming with Allegro 135
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
[Link]
136 Practical Game Programming
int main(void) {
//——Init Allegro——
if (!al_init()) { fprintf(stderr, "Failed to init
Allegro\n"); return—
1; }
if (!al_install_keyboard()) { fprintf(stderr, "Failed
to install keyboard\n"); return—
1; }
if (!al_init_primitives_addon()) { fprintf(stderr,
"Failed to init primitives\n"); return—
1; }
al_init_font_addon(); // for builtin font
//——Ball state——
float radius = 20.0f;
float x = SCREEN_W * 0.5f, y = SCREEN_H * 0.5f;
float vx = 220.0f, vy = 180.0f; // pixels per second
//——Control state——
FpsMode mode = FPS_60;
[Link]
Essentials of Game Programming with Allegro 137
while (running) {
ALLEGRO_EVENT ev;
if (mode == FPS_UNLIMITED) {
// In unlimited mode, don't block waiting for
a timer
if (al_get_next_event(queue, &ev)) {
// process any pending event
} else {
// no events pending: mark to update/
render immediately
redraw = true;
}
} else {
al_wait_for_event(queue, &ev);
}
[Link]
138 Practical Game Programming
if (!al_get_timer_started(timer))
al_start_timer(timer);
}
// reset time reference to avoid a big dt
jump
last_time = al_get_time();
redraw = true;
}
} else if ([Link] == ALLEGRO_EVENT_TIMER) {
// Timer tick—
> redraw/update
if ([Link] == timer) {
redraw = true;
}
}
[Link]
Essentials of Game Programming with Allegro 139
//——Render——
al_clear_to_color(al_map_rgb(18, 18, 22));
al_draw_filled_circle(x, y, radius, al_map_
rgb(80, 180, 255));
al_draw_textf(font, al_map_rgb(230, 230,
230), 10, 10, 0,
"FPS: %s | SPACE to
toggle | ESC to quit", fps_label(mode));
al_flip_display();
redraw = false;
}
}
//——Cleanup——
al_destroy_event_queue(queue);
al_destroy_timer(timer);
al_destroy_font(font);
al_destroy_display(display);
return 0;
}
[Link]
140 Practical Game Programming
2. Configuration Files
These files store settings and preferences for the game. Common
formats include INI, JSON, and XML.
3. Save Files
Save files store the player’s progress and game state. They can be in
various formats, often custom to the game.
4. Log Files
Log files record events and errors that occur during gameplay, useful
for debugging and support.
[Link]
Essentials of Game Programming with Allegro 141
}
fclose(file);
} else {
fprintf(stderr, "Failed to open config
file!\n");
}
4. Logging Events
Write log messages to a file for debugging purposes:
[Link]
142 Practical Game Programming
Behavior
The program attempts to read from a configuration file ([Link]) located
in the same directory as the executable. This file would typically contain user-
defined settings for various subsystems. On each execution, the program logs
the success or failure of initializing these components to [Link].
Since the required assets (e.g., sound files, graphics, and [Link]) are
deliberately omitted, the program will launch and then exit immediately.
After execution, you can inspect [Link] to see which components failed to
initialize.
How to Test
To observe changes in behavior:
This approach helps illustrate how initialization routines and logging mech-
anisms behave under failure conditions and how they can be used to diagnose
missing or misconfigured resources.
Here’s the complete example demonstrating how to load an image, read a
configuration file, save game state, and log events in an Allegro game:
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <stdio.h>
#include <time.h>
[Link]
Essentials of Game Programming with Allegro 143
typedef struct {
int level;
int score;
} GameState;
int main() {
/*—
—open log first so errors get recorded even if
early failures
FILE *log_file = fopen("[Link]", "a");
if (!log_file) {
fprintf(stderr, "Failed to open log file!\n");
} else {
time_t t = time(NULL);
struct tm *tmv = localtime(&t);
char ts[64] = {0};
if (tmv) strftime(ts, sizeof(ts), "%Y-
%m-
%d
%H:%M:%S", tmv);
fprintf(log_file, "[%s] Game started\n", ts[0] ?
ts : "unknown-
fflush(log_file);
}
al_init();
al_init_image_addon();
if (!al_install_audio()) {
fprintf(stderr, "Failed to initialize audio!\n");
if (log_file) fprintf(log_file, "Failed to
initialize audio!\n"
}
if (!al_init_acodec_addon()) {
if (log_file) fprintf(log_file, "Failed to init
acodec addon!\n
}
if (!al_reserve_samples(16)) {
if (log_file) fprintf(log_file, "Failed to
reserve samples!\n")
}
[Link]
144 Practical Game Programming
if (!display) {
fprintf(stderr, "Failed to create display!\n");
if (log_file) fprintf(log_file, "Failed to create
display!\n");
/* continue so we can still read config and write
log */
}
[Link]
Essentials of Game Programming with Allegro 145
if (image) al_destroy_bitmap(image);
if (sound) al_destroy_sample(sound);
if (display) al_destroy_display(display);
al_uninstall_audio();
return 0;
}
2.6.4. PhysicsFS
Using PhysicsFS in Allegro 5 games allows you to manage files and archives
more efficiently. PhysicsFS provides abstract access to various archives (like
ZIP files), making it easier to handle game assets.
Here’s how you can integrate and use PhysicsFS with Allegro 5.
[Link]
146 Practical Game Programming
Setting Up PhysicsFS
1. Install PhysicsFS
Download and install PhysicsFS from the official website.
2. Include PhysicsFS in Your Project
Make sure to include the PhysicsFS header and link to the PhysicsFS
library in your project.
if (PHYSFS_init(NULL) == 0) {
fprintf(stderr, "Failed to initialize
PhysicsFS—%s\n",
PHYSFS_getErrorByCode(PHYSFS_
getLastErrorCode()));
return—1;
}
if (PHYSFS_mount("[Link]", NULL, 1) == 0) {
fprintf(stderr, "Failed to mount archive—
%s\n",
PHYSFS_getErrorByCode(PHYSFS_
getLastErrorCode()));
PHYSFS_deinit();
return—1;
}
al_set_physfs_file_interface();
[Link]
Essentials of Game Programming with Allegro 147
al_destroy_display(display);
PHYSFS_deinit();
return—1;
}
ALLEGRO_SAMPLE *sound = al_load_sample("[Link]");
if (!sound) {
fprintf(stderr, "Failed to load sound!\n");
al_destroy_bitmap(image);
al_destroy_display(display);
PHYSFS_deinit();
return—1;
}
EXAMPLE CODE
In this example, we’ll use PhysicsFS with Allegro 5 to open a data file, [Link],
and extract an image to display ([Link]) and a sound to play ([Link]).
The program will get the data files from [Link], play a sound, and display the
image for 15 seconds:
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_physfs.h>
#include <physfs.h>
#include <stdio.h>
int main() {
al_init();
al_init_image_addon();
al_install_audio();
al_init_acodec_addon();
al_reserve_samples(8); // specify number of
samples
if (PHYSFS_init(NULL) == 0) {
fprintf(stderr, "Failed to initialize
PhysicsFS—%s\n",
PHYSFS_getErrorByCode(PHYSFS_
getLastErrorCode()));
[Link]
148 Practical Game Programming
return—1;
}
if (PHYSFS_mount("[Link]", NULL, 1) == 0) {
fprintf(stderr, "Failed to mount
archive—%s\n",
PHYSFS_getErrorByCode(PHYSFS_
getLastErrorCode()));
PHYSFS_deinit();
return—1;
}
al_set_physfs_file_interface();
[Link]
Essentials of Game Programming with Allegro 149
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(image, 0, 0, 0);
al_flip_display();
al_play_sample(sound, 1.0, 0.0, 1.0, ALLEGRO_
PLAYMODE_ONCE, NULL);
al_rest(15.0);
al_destroy_bitmap(image);
al_destroy_sample(sound);
al_destroy_display(display);
PHYSFS_deinit();
return 0;
}
2.6.5. Memfiles
Using memfiles in Allegro 5 allows you to treat a block of memory as a file,
which can be very useful for handling data stored in memory rather than on
disk. Here’s how you can use memfiles in your Allegro 5 games.
Setting Up
1. Include the memfile Header
Make sure to include the allegro_memfile.h header in your project:
#include <allegro5/allegro.h>
#include <allegro5/allegro_memfile.h>
[Link]
150 Practical Game Programming
Read from the memfile: You can read from the memfile using Allegro’s
file I/O functions:
char buffer[256];
al_fread(memfile, buffer, sizeof(data));
printf("Read from memfile—
%s\n", buffer);
Write to the memfile: If the memfile is opened in write mode, you can
write to it:
Close the memfile: Always close the memfile when you’re done:
al_fclose(memfile);
al_fclose(writable_memfile);
EXAMPLE CODE
Here’s a complete example demonstrating how to create, read from, and write
to a memfile in Allegro 5:
#include <allegro5/allegro.h>
#include <allegro5/allegro_memfile.h>
#include <stdio.h>
#include <string.h>
int main() {
al_init();
[Link]
Essentials of Game Programming with Allegro 151
char buffer[256];
al_fread(memfile, buffer, sizeof(data));
printf("Read from memfile—
%s\n", buffer);
al_fclose(memfile);
al_fclose(writable_memfile);
printf("Updated data—
%s\n", data);
return 0;
}
[Link]
152 Practical Game Programming
[Link]
Essentials of Game Programming with Allegro 153
[Link]
154 Practical Game Programming
Comprehensive Projects
22. 2D Arcade Game
Build a complete game with scoring, sound effects, collision, and a
high-score system (think Space Invaders, Breakout).
23. Multiplayer Quiz Game
Create a quiz game where two players answer questions using
keyboards. (Optional: Use timers for question limits and network
sockets for communication.)
24. Procedural Terrain Generator
Generate random terrain using Perlin noise, save/load maps to files,
and allow players to explore with mouse/keyboard controls.
25. Roguelike Prototype
Develop a dungeon-crawling game with grid-based movement, enemy
AI, and permadeath. Use AABB collision for combat.
26. Music Visualizer
Analyze audio streams (e.g., beat detection) and render real-time
visual effects synchronized to the music.
27. Physics Sandbox
Simulate gravity and collisions between objects (circles, rectangles).
Let users spawn objects with mouse clicks and adjust physics
parameters.
28. AI Racing Game
Implement a car-racing game with AI opponents. Use state-based
input for steering and event-based sounds for collisions.
29. Interactive Storybook
Combine mouse input, animations, and voice acting to create an
interactive story with branching narratives. Save progress between
sessions.
30. Final Project: Portfolio Game
Integrate all concepts (input, collision, sound, timing, files) into a
polished game of your design. Include a README explaining technical
choices.
[Link]
3
In the beginning, video games made use of the technologies available to them;
the methods of input and output framed the development of games and how
they operated. From the earliest text-only games to today’s realistic three-
dimensional world renderings, programmers have advanced along with the
latest hardware implementations. Changes tend to be incremental in nature;
in adventure games, for example, we began with the use of textual descriptions
for the scene/area, which then evolved into the use of simple character-based
ASCII or extended ASCII graphics to illustrate scenes, or created top-down
versions of worlds to explore (see figure 3.1).
Soon after simple primitive screen graphics hardware and colour displays
arrived (typically a television in many cases), the era of blocky and simple
graphic game styles began; this then was refined further into the familiar
8-bit blocky retro games. The 8-bit character evolved into the development and
Figure 3.1: Text-based The Colossal Cave Adventure (left) and Rogue, using ASCII graphics
(right). Images by William Crowther and Don Woods (left) and Artoftransformation (right),
published under a CC BY-SA 3.0 license and modified for size and fitting by Kyle Flemmer.
[Link]
156 Practical Game Programming
[Link]
Using Graphics in Games 157
RAM is associated with the graphics-chip hardware and designed for fast-access,
high-speed math (computer graphics use lots of that), and so we have graphics
processing units (GPUs) to do the heavy lifting. All of this comes together to
allow bytes of memory holding 1’s and 0’s to be displayed as a matrix of “bits”
to create an image—hence “bitmaps.”
The screen resolution refers to the dimensions of the screen in pixels (picture
elements), which are basically display bits. A standard screen of 1920 × 1024 is
1920 bits wide and 1024 bits tall. Each one of those bits has a memory location
and a colour depth (we’ll talk about that in a bit), which can be referenced in
most graphical programming languages by an x-and a y-coordinate. The top
leftmost location is 0,0, and the bottom rightmost is the screen resolution value
minus 1 (as we start at location 0)—so for our example, the screen coordinates
1919, 1023. While it may seem unintuitive to have a coordinate system that uses
the top leftmost location as 0,0, this is a legacy association with old video display
units (and our tendency to have a left-to-right frame of mind—for example,
writing) for creating a screen image. Old computer monitor systems utilizing
cathode ray tubes (CRTs) “painted” an image on-screen with an electron beam,
starting from the top left, scanning left to right, and moving down line by
line. This process then mapped well into “reading” and displaying computer
memory, and so early computer graphics had a typical memory layout with
locations in the left-to-right format.
Let’s say we have a simple 4 × 4 display screen; our memory might look
like table 4.
We could say our screen resolution here is 4 × 4: The top-left pixel coordin-
ate is 0,0 and the bottom right is then 3,3 (as mentioned previously, X-1, Y-1).
In our computer system this would be 16 bytes of VRAM—let’s say a memory
address location starting at $C000 in HEX and ending at $C00F (see table 5).
Note how the screen sequence of coordinates translates to the linear layout
of the memory here so that the 3,3 display location lands in the $C00F memory
address location. In its simplest description, game graphics is putting data onto
[Link]
158 Practical Game Programming
Let’s look back at our simple display example with a basic range of 256
colours—so a value of 64 is green, and let’s use 128 for red. We will plot a border
onto the screen, a green one with a red center—illustrating the VRAM memory
of a bitmap screen object derived from table 4.
In VRAM memory, then we would have the values shown in table 6.
This mechanism of writing data (values) to a screen (display) through
memory locations (RAM, VRAM) is the basic process for all screen graphics.
When we write programs, we are using arrays of data, representing a graphic
object—be it a bitmap screen image like a background, a defined area pattern
like a square, or a circle like in our example. We refer to mobile bit array objects
as “sprites,” a bitmap that we typically move around on a larger object like the
screen itself—the background, which could be a large static bitmap.
[Link]
Using Graphics in Games 159
[Link]
160 Practical Game Programming
#include <allegro5/allegro_image.h>
bool al_init_image_addon(void)
Here is a brief overview of the supported graphics formats and their features:
[Link]
Using Graphics in Games 161
The 32-bit colour depth is quite impressive but probably seldom used. (From the
human perspective, could one even tell 4 billion colour differences from 16 million?
And would the file size increase be worth it?)
Allegro 5 supports a wide range of screen resolutions as well. Here is a list
of some of the commonly used screen resolutions that Allegro 5 supports:
Note that this is not an exhaustive list; Allegro 5 may support other screen
resolutions as well.
[Link]
162 Practical Game Programming
generate an error, but when displayed, you won’t see anything. This situation
often leads one to believe that they have an error in their code, but it’s actually
a problem with the source image. Improper colour depth can also spoil the look
of your game. For example, a 24-bit image rendered in 8-bit will look nothing
like the original, losing both resolution and colour variation.
For this example, we will leave the colour depth at 24 but reduce the image
size to 1024 × 768. I’m on a Windows 10 system, and so I can use the Photos
program that comes with the system. The original image is in a .jpg format,
3008 × 2000, and 3 MB in size. To start with, I want to resize it and then convert
it to a bitmap format (.bmp). In the Photos program, under the resize option,
when I change the image size to 1024 × 768, it sets the height to 681 to maintain
the aspect ratio. I’ll live with that and see how it looks in the demo program,
so I select the “save as” function to export the image as [Link]. The
file size is now reduced somewhat to 2.75 MB (see figure 3.3).
Figure 3.3: [Link] for use as a background image. Photograph by Walter Ridgewell.
[Link]
Using Graphics in Games 163
Higher Resolution: Bitmaps are typically used for images that require high
resolution, such as logos, icons, and other graphical elements. Bitmaps
can have a higher resolution than JPEGs, which means they can display
more pixels per inch (PPI) and provide a clearer, sharper image.
Lossless Compression: Bitmaps use lossless compression, which
means that the original image data is preserved during compression.
This is important for images that need to be edited or manipulated
in the future, as the original data can be restored without any loss of
quality. JPEGs, however, use lossy compression, which means that
some of the original data is discarded during compression, and the
resulting image may not be as sharp or detailed as the original.
Transparency: Bitmaps can support transparency, which means
that certain parts of the image can be made transparent or
semitransparent. This is useful for creating images with overlays
or other visual effects that require transparency. JPEGs do not
support transparency.
Flexibility: Bitmaps can be used in a variety of contexts, including
web design, graphic design, and game development. They can be
scaled up or down to different sizes without losing quality, and
they can be used in a variety of software applications. JPEGs are
primarily used for web images and are not as versatile as bitmaps.
Smaller File Size: Bitmaps can be smaller in file size than JPEGs,
especially in images that do not require high resolution or
complex colour schemes. This is because bitmaps use a different
compression algorithm than JPEGs, which allows them to use
fewer bits per pixel and results in smaller file sizes. However, this
comes at the cost of lower resolution and less detail in the image.
[Link]
164 Practical Game Programming
[Link]
Using Graphics in Games 165
[Link]
166 Practical Game Programming
// Event loop—
keeps the display open and listens for
the ESC key press or window close event to exit the program
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
if ([Link] == ALLEGRO_KEY_
ESCAPE) {
running = false;
}
}
}
// Cleanup
al_destroy_bitmap(image);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
[Link]
Using Graphics in Games 167
• al_draw_scaled_bitmap
• al_draw_rotated_bitmap
• al_draw_tinted_bitmap
• al_draw_scaled_rotated_bitmap
• al_draw_tinted_scaled_bitmap
• al_draw_tinted_rotated_bitmap
• al_draw_tinted_scaled_rotated_bitmap
We’ll just consider the base routine calls (scaled, rotated, and tinted) here,
as the combined routines simply add parameters from those base calls.
sx: source x
sy: source y
sw: source width
sh: source height
dx: destination x
dy: destination y
dw: destination width
dh: destination height
flags: same as for al_draw_bitmap
[Link]
168 Practical Game Programming
where
cx: center
cy: center y
dx: destination x
dy: destination y
angle: angle by which to rotate
flags: same as for al_draw_bitmap
This draws a rotated version of the specified bitmap. Again, we see the use
of some familiar variables with respect to destination x-and y-coordinates. As
we are rotating an image about a point, we need the center x-and y-coordinates
and an amount to rotate the bitmap by.
In Allegro, we specify an “angle” described using radians rather than
degrees, with the rotation going clockwise (turning to the right . . .) around
the axis (center-point coordinates).
The point defined by cx/cy is the rotation point inside the bitmap; the image
will be drawn at dx/dy on the display, and the bitmap is rotated around this
point 90 degrees clockwise. If the cx/cy point is in the center of the object, it’s
rotated on that point; if it’s on an edge or corner, the object is rotated at the point.
Figure 3.5 shows two examples rotated 45 degrees.
[Link]
Using Graphics in Games 169
In these two examples, we see how the coordinates affect where the rotation
occurs. In the first we have different coordinates for the destination x,y of the
bitmap (where it’s drawn from dx and dy) and the rotation point in that object
(cx and cy, respectively). In the first example, the rotation point is at the center
of the object. In the second example, the destination dx,dy and the rotation
point cx,cy are the same. Now let’s advance the rotation to 90 degrees.
As you can see in figure 3.6, the location of the center point for rotation makes
a difference in the appearance of the image.
Let’s take a diversion here from the example and into the use of radians
for the angle, as opposed to degrees, which is what most people will be used
to. Degrees and radians are both units for measuring angles. Degrees divide
a circle into 360 parts, making them intuitive and easy to use in everyday
[Link]
170 Practical Game Programming
#include <stdio.h>
#define PI 3.14159265358979323846
int main(void) {
// Angle in degrees
double degrees = 45.0;
// Conversion: radians = degrees * (PI / 180)
double radians = degrees * (PI / 180.0);
// Output the result
printf("%.2f degrees is equal to %.5f radians.\n",
degrees, radians);
return 0;
}
We’ll see how this looks in the following demonstration program example
BitmapDisplayRotate.c. Here, every press of the space bar rotates the back-
ground image 90 degrees clockwise.
[Link]
Using Graphics in Games 171
[Link]
172 Practical Game Programming
if (!image) {
al_show_native_message_box(NULL,
"Error", "Error", "Failed to load image!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
al_destroy_display(display);
return—1;
}
[Link]
Using Graphics in Games 173
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
[Link]
174 Practical Game Programming
if ([Link] == ALLEGRO_KEY_
ESCAPE) {
running = false;
}
else if ([Link] == ALLEGRO_KEY_
SPACE) {
// Rotate the image 90° clockwise (90° =
PI/2 radians).
angle += ALLEGRO_PI / 2;
if (angle >= 2 * ALLEGRO_PI)
angle—
= 2 * ALLEGRO_PI;
// Cleanup resources.
al_destroy_bitmap(image);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
[Link]
Using Graphics in Games 175
allows for setting the opacity of the bitmap from solid to transparent. While
changing the transparency of a bitmap image in your game may not seem
useful immediately, it is a handy function for visual effects when utilized with
sprites in games (an example could be of a game character that gets “zapped”
by a laser and then disintegrates—fades away, a change in transparency). In
relation to the background, perhaps as you change scenes, you might fade out
from one and fade into the next. The routine’s structure is:
The “tint value” we’re talking about comes from the ALLEGRO_COLOR that you
create with the routine al_map_rgba_f(float r, float g, float b, float a). This routine
takes four floating-point numbers (each between 0 and 1) representing the red,
green, blue, and alpha (transparency) channels, respectively. Here is an example:
al_draw_tinted_bitmap(bitmap, al_map_rgba_f(1, 1, 1,
0.5), x, y, 0);
In this case, you’re drawing the bitmap with full intensity on the red, green,
and blue channels (that’s what the 1’s mean), but the alpha value is set to 0.5.
This results in the bitmap being rendered at 50% transparency.
Another example is:
Here, the tint is specified to use only the red component (1 for red, 0 for
green, 0 for blue) while keeping full opacity (alpha is 1). This means the bitmap
gets tinted red.
So essentially, by adjusting the values you pass to al_map_rgba_f, you control
both the transparency and the colour tint applied to your bitmap when you draw it.
[Link]
176 Practical Game Programming
Here, the main loaded bitmap image comprises the whole map or game
level, with sections of it outside the main display screen; this is your viewport.
We then add to that viewport a minimap, which might show all of the existing
map (scaled down), and then a highlighted section indicating which area is
currently being seen on the main display.
What follows is an example program, DisplayMap.c, which loads a back-
ground image, displays a section of it on the main screen (the viewport) with
a minimap showing a scaled-up version of the complete image.
if (!al_init_image_addon()) {
al_show_native_message_box(NULL, "Error",
"Error", "Failed to initialize image addon!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
return—1;
}
if (!al_init_primitives_addon()) {
al_show_native_message_box(NULL, "Error",
"Error", "Failed to initialize primitives addon!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
return—1;
}
[Link]
Using Graphics in Games 177
int mini_view_x = 0;
int mini_view_y = 0;
int mini_view_w = mini_w / 8;
int mini_view_h = mini_h / 8;
const int mini_scroll_speed = 5;
if (!al_install_keyboard()) {
al_show_native_message_box(display, "Error",
"Error", "Failed to install keyboard!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
al_destroy_bitmap(map);
al_destroy_display(display);
return—1;
}
[Link]
178 Practical Game Programming
ALLEGRO_EVENT_QUEUE* event_queue =
al_create_event_queue();
if (!event_queue) {
al_show_native_message_box(display, "Error",
"Error", "Failed to create event queue!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
al_destroy_bitmap(map);
al_destroy_display(display);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
while (running)
{
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
else if ([Link] == ALLEGRO_EVENT_KEY_DOWN)
{
if ([Link] ==
ALLEGRO_KEY_ESCAPE)
{
running = false;
}
else if ([Link] ==
ALLEGRO_KEY_LEFT)
[Link]
Using Graphics in Games 179
{
mini_view_x—= mini_scroll_speed;
if (mini_view_x < 0) mini_view_x = 0;
}
else if ([Link] ==
ALLEGRO_KEY_RIGHT)
{
mini_view_x += mini_scroll_speed;
if (mini_view_x > mini_w—
mini_view_w)
mini_view_x = mini_w—
mini_view_w;
}
else if ([Link] ==
ALLEGRO_KEY_UP)
{
mini_view_y—= mini_scroll_speed;
if (mini_view_y < 0) mini_view_y = 0;
}
else if ([Link] ==
ALLEGRO_KEY_DOWN)
{
mini_view_y += mini_scroll_speed;
if (mini_view_y > mini_h—
mini_view_h)
mini_view_y = mini_h—
mini_view_h;
}
}
else if ([Link] == ALLEGRO_EVENT_TIMER)
{
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(map, 0, 0, 0);
al_draw_scaled_bitmap(map,
mini_view_x * (map_w / mini_w), mini_
view_y * (map_h / mini_h),
mini_view_w * (map_w / mini_w), mini_
view_h * (map_h / mini_h),
mini_x, mini_y, mini_w, mini_h, 0);
[Link]
180 Practical Game Programming
al_draw_rectangle((mini_view_x * (map_w /
mini_w)), (mini_view_y * (map_h / mini_h)), ((mini_view_x
+ mini_view_w) * (map_w / mini_w)), ((mini_view_y + mini_
view_h) * (map_h / mini_h)), al_map_rgb(255, 255, 255),
2);
al_draw_textf(al_create_builtin_font(), al_
map_rgb(255, 255, 255), 10, 10, 0, "mini_x: %d, mini_y:
%d, mini_w: %d, mini_h: %d", mini_x, mini_y, mini_w,
mini_h);
al_flip_display();
}
}
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
al_destroy_bitmap(map);
al_destroy_display(display);
return 0;
}
if (!al_init()) {
al_show_native_message_box(NULL, "Error",
"Error", "Failed to initialize Allegro!", NULL,
ALLEGRO_MESSAGEBOX_ERROR);
return—1;
}
This ensures that Allegro is ready to be used. If it fails, an error message pops up.
[Link]
Using Graphics in Games 181
al_init_image_addon();
al_init_primitives_addon();
This creates a 1024 × 768 pixel window for the game. If it fails, the program
exits.
[Link]
182 Practical Game Programming
if ([Link] == ALLEGRO_KEY_LEFT)
{
mini_view_x—= mini_scroll_speed;
if (mini_view_x < 0) mini_view_x = 0;
}
Pressing the left arrow key moves the minimap viewport left.
The viewport is prevented from scrolling beyond the left boundary (0,0).
Similarly, the Right, Up, and Down keys move the viewport in different
directions.
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(map, 0, 0, 0);
al_draw_scaled_bitmap(map,
mini_view_x * (map_w / mini_w), mini_view_y * (map_h
/ mini_h),
[Link]
Using Graphics in Games 183
Instead of drawing the entire map, only a selected portion is drawn inside
the minimap.
This rectangle highlights the portion of the minimap that is currently being
viewed.
The example program displays the main graphic bitmap with a smaller
movable viewport indicator: here, a white rectangle and a static viewport in
the top-right corner showing an enlarged image of the bitmap in the rect-
angle area.
Figure 3.7: Display map with a minimap viewport. Image by Walter Ridgewell.
[Link]
184 Practical Game Programming
After that, one would optimize routines for screen coordinates to work with
the returned information. This can prove to be a complex process if you’re
wanting to work with a variety of screen sizes, keep aspect ratios, and so on.
In Allegro 5, one can use transformations to dynamically adapt one’s code to
the current system’s screen resolution. By using a transformation that considers
screen resolution, you can ensure that graphics scale properly and dynamically.
This would be implemented utilizing the following library functions:
al_identity_transform(&transform);
al_scale_transform(&transform, scaleX, scaleY);
al_use_transform(&transform);
where scaleX and scaleY are the scaling factors for width and height (respectively)
that take into account the commonly used screen_width and screen_height
information:
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
int main() {
if (!al_init()) {
[Link]
Using Graphics in Games 185
return—1;
}
al_init_image_addon();
al_init_primitives_addon();
ALLEGRO_DISPLAY *display = al_create_display(1024,
768);
if (!display) {
return—1;
}
// Query display dimensions
int screen_width = al_get_display_width(display);
int screen_height = al_get_display_height(display);
// Setup transformation for resolution independence
float scaleX = (float)screen_width / 1024.0;
float scaleY = (float)screen_height / 768.0;
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform);
al_scale_transform(&transform, scaleX, scaleY);
al_use_transform(&transform);
al_clear_to_color(al_map_rgb(0, 0, 0));
al_flip_display();
al_rest(2.0);
al_destroy_display(display);
return 0;
}
[Link]
186 Practical Game Programming
effectively, developers can greatly enhance the visual quality and interactivity
of their Allegro 5 projects.
[Link]
Using Graphics in Games 187
2. Performance Considerations
Using al_draw_rotated_bitmap
This is optimized for performance when all you need is rotation.
The routine is simpler and faster if you don’t need complex
transformations.
It’s great for simple sprite rotation, such as rotating a spaceship or a
top-down character.
Using al_rotate_transform
If you apply multiple transformations, using a transformation
matrix is often more efficient than applying multiple individual
drawing operations.
It reduces redundant calculations since transformations can be
precomputed and reused.
3. Transforming the Entire Scene vs. Individual Objects
Using al_use_transform with al_rotate_transform
If you need to apply transformations to the entire coordinate system,
using al_use_transform allows all subsequent drawing operations to
inherit the transformation.
Example: If you want to rotate everything in a scene (like a rotating
camera view), a transformation matrix is the correct approach.
Using al_draw_rotated_bitmap
If you only need to rotate one object while keeping everything else
unchanged, al_draw_rotated_bitmap is easier.
4. Example Comparisons
Using al_draw_rotated_bitmap (Simpler, Object-Specific)
This rotates only one bitmap at a time. It’s good for rotating sprites
but not for complex transformations.
Using al_rotate_transform with al_use_transform (More Advanced, Affects
All Drawing)
[Link]
188 Practical Game Programming
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform);
al_translate_transform(&transform,—100,—100); // Move
pivot point
al_rotate_transform(&transform, ALLEGRO_PI / 4); //
Rotate 45 degrees
al_translate_transform(&transform, 100, 100); // Move
back
al_use_transform(&transform);
As you can see, transforms are useful for more complex operations and can
affect a whole screen view, which brings us back to why we might want to use
them to achieve display resolution independence in our games.
[Link]
Using Graphics in Games 189
// Base resolution
float base_width = 800;
float base_height = 600;
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform);
al_scale_transform(&transform, scale_x, scale_y);
al_use_transform(&transform);
So here in this code segment, if the display size of the platform running the
code is 1600 × 1200, the values supplied to the transform would be scale_x = 2
and scale_y = 2, meaning everything is doubled in size.
Conversely, if the screen is 400 × 300 (like on a small mobile device plat-
form), the calculated values of scale_x = 0.5 and scale_y = 0.5 would be applied,
meaning everything is shrunk to fit. As a result, all your drawing code remains
the same regardless of screen size.
[Link]
190 Practical Game Programming
Here, the code ensures that even if the screen is wider or taller than your base
resolution, your game remains centered.
EXAMPLE
#define VIRTUAL_WIDTH 800
#define VIRTUAL_HEIGHT 600
al_identity_transform(&transform);
al_scale_transform(&transform, sx, sy);
al_use_transform(&transform);
[Link]
Using Graphics in Games 191
• Scale using the same factor for both axes (min(scale_x, scale_y)).
• Add black bars (letterboxing) to fill the remaining space:
al_identity_transform(&transform);
al_translate_transform(&transform, offset_x, offset_y);
al_scale_transform(&transform, scale_factor,
scale_factor);
al_use_transform(&transform);
[Link]
192 Practical Game Programming
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform); // Initialize with the
identity matrix
al_rotate_transform(&transform, angle); // Rotate
al_translate_transform(&transform, x, y); // Move
al_scale_transform(&transform, scaleX, scaleY); // Scale
al_use_transform(&transform); // Set as the current
transformation
[Link]
Using Graphics in Games 193
ALLEGRO_TRANSFORM transform;
al_rotate_transform(&transform, ALLEGRO_PI / 4);
al_use_transform(&transform);
If transform was already modified elsewhere in the code, this would continue
rotating based on the existing transformations, leading to an unexpected effect.
Now, using al_identity_transform() ensures we reset it first:
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform); // Reset to a clean
state
al_rotate_transform(&transform, ALLEGRO_PI / 4);
al_use_transform(&transform);
This ensures that the only transformation applied is the new 45-degree rotation.
The routine al_identity_transform(ALLEGRO_TRANSFORM *trans) is used
to reset a transformation matrix to the identity matrix. This means it sets
[Link]
194 Practical Game Programming
al_translate_transform(&transform,—100,—100); (Move
origin)
al_rotate_transform(&transform, ALLEGRO_PI / 4); (Rotate
45°)
al_translate_transform(&transform, 100, 100); (Move back)
al_use_transform(&transform);
al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR |
ALLEGRO_MAG_LINEAR);
These flags tell Allegro to use linear filtering for minification and magnifi-
cation, which results in smoother textures.
In Allegro 5, both antialiasing and texture smoothing are important for
creating more visually appealing and polished graphics in your applications.
[Link]
Using Graphics in Games 195
3.8.3. Antialiasing
Antialiasing is used to reduce the jagged edges that can appear on diagonal or
curved lines in digital images, a phenomenon often referred to as “aliasing.”
This effect is especially noticeable in lower-resolution graphics or when scaling
images up or down.
In Allegro 5, antialiasing can be applied in several ways, but one of the
most common methods is through the use of multisampling. Multisampling
works by taking multiple samples at different points within each pixel and then
averaging these samples to determine the pixel’s colour. This process smooths
out the edges of lines and curves.
al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 1,
ALLEGRO_SUGGEST);
al_set_new_display_option(ALLEGRO_SAMPLES, 4, ALLEGRO_
SUGGEST); // Set to 4 samples
ALLEGRO_DISPLAY* display = al_create_display(width, height);
al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR |
ALLEGRO_MAG_LINEAR);
ALLEGRO_BITMAP* bitmap = al_load_bitmap("[Link]");
[Link]
196 Practical Game Programming
3.9. Shaders
Shaders are programs that run on a computer’s GPU to control the rendering
of graphics. They are used for a variety of effects in real-time applications,
such as video games.
ALLEGRO_SHADER *shader =
al_create_shader(ALLEGRO_SHADER_GLSL);
al_attach_shader_source(shader, ALLEGRO_VERTEX_SHADER,
vertex_shader_source);
al_attach_shader_source(shader, ALLEGRO_PIXEL_SHADER,
pixel_shader_source);
al_build_shader(shader);
al_use_shader(shader);
1. Vertex Shaders
These process each vertex of a shape. They can manipulate attributes
like position, colour, and texture coordinate of each vertex.
2. Fragment Shaders (or Pixel Shaders)
These handle how pixels (fragments) are coloured. They’re used for
effects like lighting, colour blending, and texture mapping.
[Link]
Using Graphics in Games 197
EXAMPLE
Here’s an outline of the steps of using a shader in Allegro 5:
// Create a shader
ALLEGRO_SHADER *shader =
al_create_shader(ALLEGRO_SHADER_GLSL);
[Link]
198 Practical Game Programming
al_attach_shader_source(shader, ALLEGRO_PIXEL_SHADER,
fragment_shader_source);
// Clean up
al_destroy_shader(shader);
[Link]
Using Graphics in Games 199
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
EXAMPLE
// Initialize Allegro and create a display...
[Link]
200 Practical Game Programming
This example assumes the bitmap has premultiplied alpha. The al_set_blender
routine configures the correct blending mode. More details on Allegro 5 blend-
ing modes will be covered in the next chapter.
[Link]
Using Graphics in Games 201
[Link]
202 Practical Game Programming
[Link]
4
Programming Sprites
in Allegro 5
[Link]
204 Practical Game Programming
In the example program, we load two bitmaps: one to use as the background
and the other as the movable sprite. We’ll use our [Link] as the back-
ground and a simple flying saucer sprite, [Link] (see figure 4.1)
Now one of the issues we have to deal with for sprites is, What do we dis-
play and what do we hide? If we use the saucer image as is, we will see the full
image: the red square and the saucer. Oh, but it’s way too big for the size of our
screen, as you can see in figure 4.2.
Figure 4.2: Saucer image displayed on a background image. Image by Walter Ridgewell.
[Link]
Programming Sprites in Allegro 5 205
So we need to resize the saucer (we can do that in a graphics package like
GIMP) and hide the red areas.
al_convert_mask_to_alpha(sprite, transparentColor); //
Red color with 50% transparency
Alternately, one can embed the al_map_rgb routine right in the other routine:
We then need to call the blending routine in our code to enable the use of
the alpha channel and have transparency:
[Link]
206 Practical Game Programming
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
Then Allegro will handle the rest when we draw the sprite.
Transparency is a powerful tool for creating effects like ghostly figures,
smoke, shadows, or UI elements that need to overlay the game without com-
pletely obscuring the background. Translucent sprites are also useful for visual
cues, such as indicating that a character is in stealth mode or partially invisible,
or for effects like energy shields and force fields.
Our sprite’s red background, though, isn’t pure red. This becomes clear when
one tries to run a test program using the bitmap and setting the transparency
value of 255 for al_map_rgb. If one does that, you will still see the red square
background. Computers are precise when it comes to numerical values, and so
we need to find out what “shade” of red this is. As such it is necessary here to
use a graphics program (like the free GIMP graphics package) and then a tool
like the colour sampler (usually a small dropper type of icon), which will tell
you the RGB values for that point, for example.
From the information provided, we see the RGB values are 237, 28, 36 for
the red square area. We then would change those values in the code like so:
Figure 4.3: Saucer image open in a graphics editing program. Image by Walter Ridgewell.
[Link]
Programming Sprites in Allegro 5 207
Figure 4.4: Saucer image with alpha transparency applied. Image by Walter Ridgewell.
This allows the red areas to become transparent, as seen in figure 4.4.
Let’s look at the program code to do this. Here is a simple Allegro 5 program
that displays a spaceship sprite and lets one move it across a background image
using the keyboard keys.
[Link]
208 Practical Game Programming
if (!al_init()) {
fprintf(stderr, "Failed to initialize
Allegro!\n");
return—1;
}
if (!al_install_keyboard()) {
fprintf(stderr, "Failed to initialize the
keyboard!\n");
return—1;
}
if (!al_init_image_addon()) {
fprintf(stderr, "Failed to initialize image
addon!\n");
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
background = al_load_bitmap("[Link]");
if (!background) {
[Link]
Programming Sprites in Allegro 5 209
sprite = al_load_bitmap("[Link]");
if (!sprite) {
fprintf(stderr, "Failed to load sprite
image!\n");
al_destroy_bitmap(background);
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
event_queue = al_create_event_queue();
if (!event_queue) {
fprintf(stderr, "Failed to create
event_queue!\n");
al_destroy_bitmap(sprite);
al_destroy_bitmap(background);
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
[Link]
210 Practical Game Programming
al_start_timer(timer);
while (1) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
if (key[0] && sprite_y >= 4.0) {
sprite_y—= 4.0;
}
redraw = true;
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
break;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_UP:
key[0] = true;
break;
case ALLEGRO_KEY_DOWN:
key[1] = true;
break;
case ALLEGRO_KEY_LEFT:
key[2] = true;
break;
case ALLEGRO_KEY_RIGHT:
key[3] = true;
[Link]
Programming Sprites in Allegro 5 211
break;
}
} else if ([Link] == ALLEGRO_EVENT_KEY_UP) {
switch ([Link]) {
case ALLEGRO_KEY_UP:
key[0] = false;
break;
case ALLEGRO_KEY_DOWN:
key[1] = false;
break;
case ALLEGRO_KEY_LEFT:
key[2] = false;
break;
case ALLEGRO_KEY_RIGHT:
key[3] = false;
break;
}
}
al_draw_bitmap(background, 0, 0, 0);
// Enable blending
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_flip_display();
}
}
al_destroy_bitmap(sprite);
[Link]
212 Practical Game Programming
al_destroy_bitmap(background);
al_destroy_timer(timer);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
Now let’s walk through the structure so we understand how it all fits
together.
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
Here, we also define variables like the display, sprite positions, and event
handling objects (e.g., event_queue, timer).
Load Assets: The program loads the background and sprite images.
If the assets don’t load properly, we clean up and exit to avoid
crashes:
[Link]
Programming Sprites in Allegro 5 213
background = al_load_bitmap("[Link]");
sprite = al_load_bitmap("[Link]");
Events like key presses, timer ticks, and display closing are registered here.
Think of the event queue as a to-do list the program processes continuously.
[Link]
214 Practical Game Programming
Step 5. Cleanup
After the user closes the window or exits, the program cleans up by destroying
all created resources (e.g., the sprite, background, display, timer, and event
queue).
Key Highlights
The game logic ties sprite movement directly to key states, ensuring smooth,
responsive controls.
The al_set_blender() routine enables blending, making the sprite’s trans-
parency work correctly.
Safety checks are sprinkled throughout to handle failures gracefully (e.g.,
missing images).
In short, this program combines Allegro’s features to create a basic game
where a spaceship moves over a background. It’s modular, with clear steps for
initializing, handling events, updating positions, and rendering graphics. It’s
like building a foundation for a game you can expand on later—perhaps by
adding collisions, sound, or even enemies!
// Enable blending
[Link]
Programming Sprites in Allegro 5 215
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_draw_bitmap(sprite, sprite_x, sprite_y, 0);
al_flip_display();
}
}
To begin, we’ll be looking at what changes to this code segment line can be
implemented from our previous example, which draws a sprite at a specified
position (sprite_x and sprite_y) on the screen via the following:
al_draw_scaled_bitmap
void al_draw_scaled_bitmap(ALLEGRO_BITMAP *bitmap,float
sx, float sy, float sw, float sh, float dx, float dy,
float dw, float dh, int flags)
This draws a scaled version of the given bitmap to the target bitmap.
sx: source x
sy: source y
sw: source width
sh: source height
dx: destination x
dy: destination y
[Link]
216 Practical Game Programming
In the following code line, the sprite is scaled to twice its original size in both
dimensions, as indicated by these parameters in the routine call referencing
the destination width and height (dw and dh, respectively):
(al_get_bitmap_width(sprite) * 2, al_get_bitmap_
height(sprite) * 2).
with:
al_draw_scaled_bitmap(sprite, 0, 0, al_get_bitmap_
width(sprite), al_get_bitmap_height(sprite), sprite_x,
sprite_y, al_get_bitmap_width(sprite) * 2, al_get_
bitmap_height(sprite) * 2, 0);
You may also notice that we are utilizing two other Allegro routines here,
al_get_bitmap_width() and al_get_bitmap_height(), in place of simply source
width and source height—our previous sh and sw values, respectively.
Why do we want to use al_get_bitmap_width and al_get_bitmap_height? In some
of these bitmap manipulation routines, we need to specify information related to
the dimensions of the sprite in use. The routines al_get_bitmap_width(sprite) and
al_get_bitmap_height(sprite) retrieve the original width and height of the sprite,
respectively. These values are crucial because they ensure that you are scaling the
sprite based on its actual dimensions. Without these routines, you would need to
manually track the sprite’s size, which can lead to errors and inconsistencies, espe-
cially if the sprite’s dimensions change during development or if you are working
with multiple sprites of varying sizes. As such, we simplify the processes here by
getting that information from the sprite being used at that moment.
So going back to our code snippet, it would look like this for a scaled bitmap:
[Link]
Programming Sprites in Allegro 5 217
// Enable blending
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_draw_scaled_bitmap(sprite, 0, 0, al_get_bitmap_
width(sprite), al_get_bitmap_height(sprite), sprite_x,
sprite_y, al_get_bitmap_width(sprite) * 2, al_get_
bitmap_height(sprite) * 2, 0);
al_flip_display();
}
}
That example flips the sprite horizontally. You can also flip it vertically using
ALLEGRO_FLIP_VERTICAL. You can also flip both horizontally and vertically
in one function call by combining the flags (ALLEGRO_FLIP_HORIZONTAL |
ALLEGRO_FLIP_VERTICAL).
[Link]
218 Practical Game Programming
al_draw_rotated_bitmap
This draws a rotated version of the given bitmap to the target bitmap. The bit
map is rotated clockwise by the specified angle, measured in radians. The point
at cx/cy relative to the upper left corner of the bitmap will be drawn at dx/dy,
and the bitmap is rotated around this point. If cx,cy is 0,0, the bitmap will
rotate around its upper left corner:
with the new function, and so we might use something like the following:
al_draw_rotated_bitmap(sprite, al_get_bitmap_
width(sprite) / 2, al_get_bitmap_height(sprite) /
2,sprite_x, sprite_y, ALLEGRO_PI / 4, 0);
[Link]
Programming Sprites in Allegro 5 219
In this example, the sprite drawn on-screen is now rotated 45 degrees (π / 4 radians)
around its center.
Here once more we call some other Allegro routines to get required
information, making our coding a bit easier. We are using the al_get_bitmap_
width(sprite) / 2 and al_get_bitmap_height(sprite) / 2 routines to make sure
we find the center of rotation. These are the center x and center y (cx and cy
values). This ensures that the sprite rotates around its center point, which is
typically the desired behavior for most game objects. Without these routines,
you would need to manually calculate the center based on known dimensions,
which is prone to errors and less flexible if the sprite size changes.
Here, the sprite rotates 45 degrees around its top-left corner (0, 0).
In other cases, you might use the previously studied get width and height
routines to help you identify different pivot points. Determining the dimen-
sions of the sprite provides information that allows you to calculate relative
positions within the sprite.
For example, you might want to pivot around the sprite’s bottom-right corner
by implementing (al_get_bitmap_width(sprite), al_get_bitmap_height(sprite))
in the previous code:
al_draw_rotated_bitmap(sprite, (al_get_bitmap_
width(sprite), al_get_bitmap_height(sprite)),
sprite_x, sprite_y, ALLEGRO_PI / 4, 0);
[Link]
220 Practical Game Programming
Any of the three previously discussed routines can be substituted in the code
example in place of the original al_draw_bitmap(sprite, sprite_x, sprite_y, 0) routine:
// Enable blending
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_draw_rotated_bitmap(sprite,(al_get_bitmap_
width(sprite),al_get_bitmap_height(sprite)), sprite_x,
sprite_y, ALLEGRO_PI / 4, 0);
al_flip_display();
}
}
Here, the value for ALLEGRO_COLOR tint will be a call to the al_map_rgba_f
routine, which will allow us to vary the intensity of the red, green, and blue
values, as illustrated in the Allegro documents:
[Link]
Programming Sprites in Allegro 5 221
This will draw the bitmap at 50% transparency (the RGB values are premulti-
plied with the alpha component).
This alpha represents a value where 1 is solid and 0 is transparent. Values
between these two numbers then represent the opaqueness or transparency
level, so .5 is the 50%.
The concept of a premultiplied alpha is the RGB colour values with the alpha
value applied (multiplied by) so A * R, A * G, and A * B, where A would be the
same value, so for a half transparent value of 50%, we would have .5 * R, .5 * G
and .5 * B or just the 0.5 values seen in the code previously.
Once more using our code snippet as the example, we will replace the
basic draw bitmap line with this new routine, resulting in a semitransparent
bitmap image:
// Enable blending
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_draw_tinted_bitmap(bitmap, al_map_
rgba_f(0.5, 0.5, 0.5, 0.5), sprite_x, sprite_y, 0);
al_flip_display();
}
}
// Enable blending
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
[Link]
222 Practical Game Programming
which sets the function to use for blending for the current thread.
Blending means the source and destination colours are combined in draw-
ing operations.
Assume the source colour (e.g., colour of a rectangle to draw, or pixel of a
bitmap to draw) is given as its red/green/blue/alpha components (if the bitmap
has no alpha, it always is assumed to be fully opaque, so 255 for 8-bit or 1.0
for floating point): s = s.r, s.g, s.b, s.a. And this colour is drawn to a destination,
which already has a colour: d = d.r, d.g, d.b, d.a.
The conceptual formula used by Allegro to draw any pixel then depends
on the parameters:
ALLEGRO_ADD
r = d.r * df.r + s.r * sf.r
g = d.g * df.g + s.g * sf.g
b = d.b * df.b + s.b * sf.b
a = d.a * df.a + s.a * sf.a
ALLEGRO_DEST_MINUS_SRC
r = d.r * df.r - s.r * sf.r
g = d.g * df.g - s.g * sf.g
b = d.b * df.b - s.b * sf.b
a = d.a * df.a - s.a * sf.a
[Link]
Programming Sprites in Allegro 5 223
ALLEGRO_SRC_MINUS_DEST
r = s.r * sf.r - d.r * df.r
g = s.g * sf.g - d.g * df.g
b = s.b * sf.b - d.b * df.b
a = s.a * sf.a—
d.a * df.a
Valid values for the factors sf and df passed to this function are as follows,
where s is the source colour, d the destination colour, and cc the colour set with
al_set_blend_color (white by default):
ALLEGRO_ZERO
f = 0, 0, 0, 0
ALLEGRO_ONE
f = 1, 1, 1, 1
ALLEGRO_ALPHA
f = s.a, s.a, s.a, s.a
ALLEGRO_INVERSE_ALPHA
f = 1 - s.a, 1 - s.a, 1 - s.a, 1 - s.a
ALLEGRO_SRC_COLOR (since: 5.0.10, 5.1.0)
f = s.r, s.g, s.b, s.a
ALLEGRO_DEST_COLOR (since: 5.0.10, 5.1.8)
f = d.r, d.g, d.b, d.a
ALLEGRO_INVERSE_SRC_COLOR (since: 5.0.10, 5.1.0)
f = 1 - s.r, 1 - s.g, 1 - s.b, 1 - s.a
ALLEGRO_INVERSE_DEST_COLOR (since: 5.0.10, 5.1.8)
f = 1 - d.r, 1 - d.g, 1 - d.b, 1 - d.a
ALLEGRO_CONST_COLOR (since: 5.1.12, not supported on
OpenGLES 1.0)
f = cc.r, cc.g, cc.b, cc.a
ALLEGRO_INVERSE_CONST_COLOR (since: 5.1.12, not supported
on OpenGLES 1.0)
f = 1 - cc.r, 1 - cc.g, 1 - cc.b, 1 - ccc.a
BLENDING EXAMPLES
So for example, to restore the default of using premultiplied alpha blending,
you would use:
al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE,
ALLEGRO_INVERSE_ALPHA);
[Link]
224 Practical Game Programming
I’m not going to continue illustrating the formulaic processes for the fol-
lowing routines for the sake of brevity.
If you are using non-pre-multiplied alpha, you could use this:
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_set_blender(ALLEGRO_ADD, ALLEGRO_DEST_COLOR,
ALLEGRO_ZERO)
al_set_blender(ALLEGRO_ADD, ALLEGRO_CONST_COLOR,
ALLEGRO_ONE);
al_set_blend_color(al_map_rgb(0, 96, 255)); /* nice
Chrysler blue */
al_set_blender(ALLEGRO_ADD, ALLEGRO_CONST_COLOR,
ALLEGRO_CONST_COLOR);
al_set_blend_color(al_map_rgba_f(0.5, 0.5, 0.5, 0.5));
[Link]
Programming Sprites in Allegro 5 225
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
[Link]
226 Practical Game Programming
return—1;
}
if (!al_install_keyboard()) {
fprintf(stderr, "Failed to initialize the
keyboard!\n");
return—1;
}
if (!al_init_image_addon()) {
fprintf(stderr, "Failed to initialize image
addon!\n");
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
background = al_load_bitmap("[Link]");
if (!background) {
fprintf(stderr, "Failed to load background
image!\n");
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
sprite = al_load_bitmap("[Link]");
[Link]
Programming Sprites in Allegro 5 227
if (!sprite) {
fprintf(stderr, "Failed to load sprite
image!\n");
al_destroy_bitmap(background);
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
event_queue = al_create_event_queue();
if (!event_queue) {
fprintf(stderr, "Failed to create
event_queue!\n");
al_destroy_bitmap(sprite);
al_destroy_bitmap(background);
al_destroy_display(display);
al_destroy_timer(timer);
return—1;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
al_start_timer(timer);
while (1) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
[Link]
228 Practical Game Programming
if ([Link] == ALLEGRO_EVENT_TIMER) {
// Movement logic remains the same
if (key[0] && sprite_y >= 4.0) {
sprite_y—= 4.0;
}
if (key[1] && sprite_y <= 768—
36.0) {
sprite_y += 4.0;
}
if (key[2] && sprite_x >= 4.0) {
sprite_x—= 4.0;
}
if (key[3] && sprite_x <= 1024—
36.0) {
sprite_x += 4.0;
}
redraw = true;
}
else if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
{
break;
}
else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
[Link]
Programming Sprites in Allegro 5 229
case ALLEGRO_KEY_UP:
key[0] = true;
break;
case ALLEGRO_KEY_DOWN:
key[1] = true;
break;
case ALLEGRO_KEY_LEFT:
key[2] = true;
break;
case ALLEGRO_KEY_RIGHT:
key[3] = true;
break;
}
}
else if ([Link] == ALLEGRO_EVENT_KEY_UP) {
switch ([Link]) {
case ALLEGRO_KEY_UP:
key[0] = false;
break;
case ALLEGRO_KEY_DOWN:
key[1] = false;
break;
case ALLEGRO_KEY_LEFT:
key[2] = false;
break;
case ALLEGRO_KEY_RIGHT:
key[3] = false;
break;
}
}
// Enable blending
[Link]
230 Practical Game Programming
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
al_flip_display();
}
}
al_destroy_bitmap(sprite);
al_destroy_bitmap(background);
[Link]
Programming Sprites in Allegro 5 231
al_destroy_timer(timer);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}
Figure 4.5: Saucer image as semitransparent, rotated, and flipped. Image by Walter Ridgewell.
[Link]
232 Practical Game Programming
orientation, and position, the actual image—what the sprite looked like—stayed
the same. We learned how to move the sprite around the screen or change how
it appeared, but the sprite’s visual state itself didn’t evolve.
Now, when we talk about more advanced graphics, we introduce the idea of
making the sprite’s image change as well. This is where dynamic sprites come
into play. Instead of using a single image, we can swap between multiple images
to give the impression that the sprite is moving or performing an action. This
is what we call animation.
Animation is all about cycling through different images, or frames, to cre-
ate the illusion of movement. For example, if you have a character walking,
instead of just sliding the same image across the screen, we show different
frames where the character’s legs are in different positions. By displaying
these frames in quick succession, it looks like the character is actually walking.
We can combine this idea of changing the sprite image with the other trans-
formations we already learned—like adjusting its size or position—to create a
fully animated and interactive graphic. In the next steps, we’ll explore how to
manage these image sequences and control the speed at which they change.
This will allow us to create smooth animations, like making a character run,
jump, or even perform more complex actions.
So what does animating a sprite mean? It means swapping between a series
of images (or frames) over time to simulate movement. And how do we achieve
that? We’ll break it down step-by-step, starting with loading multiple frames for a
sprite and figuring out how to time their display to create a seamless animation.
[Link]
Programming Sprites in Allegro 5 233
then in the next image, he lifts a leg somewhat. In the following image, he
extends it forward, then he lowers the leg, and finally the character moves up
to that location. So in four images, you have a character take a step forward.
Since computers like doing things in power of 2, the typical sequence of frames
with some intermediate images will be some multiple, such as 4, 8, or 16. We
can refer to this collection of images as a “sprite sheet.”
In the context of a classical 8-bit sprite being displayed, we could look at
it as 4 different sprite positions displayed while moving the character around
the screen, as in figure 4.6, for example.
Figure 4.6: Sprite sheet for character animation. Illustrated by Jana Ochse for 2DPIXX,
published under a CC BY-SA 3.0 license and modified for size by Kyle Flemmer.
[Link]
234 Practical Game Programming
Now, we have a few choices in terms of the coding to do this. We start with
8 images that need to be displayed in sequence, so an easy process here would
be loading and storing our bitmaps into an array, then calling up the indexed
image as required. This could get complicated, though, depending on how many
bitmaps we have related to the animation complexity and the number of game
elements. It would be better to be able to somehow minimize the number of
individual images required while keeping them associated with a character
or object but still allowing us the animation ability that the multiple modified
bitmaps provide. So what if we can combine those 8 images into a single larger
image, a sprite sheet. Then the task coding-wise is to select a portion of that
single bitmap representing one of the images in the sequence of 8. If one does
this, then we only load the sprite sheet image once (as opposed to loading 8
images), and we use the array function to designate a location within the sprite
sheet, like the top left and bottom right coordinates of the individual image we
want. By making our sizes convenient, like 32 × 32, the math for selection can
be very easy to implement.
In the previous example, we illustrated a forward walking motion; however,
typically video game characters move in multiple directions. In this respect,
your sprite sheet will need to contain multiple rows of sprites, with each row
being utilized for a specific direction. Your images, then, might be arranged as
four rows of image sequences—up, left, right, and down. If we use 4 images
as the minimal number of changes (although there is nothing stopping you from
using only 3 or 2 if you really want a minimalist graphics set or for learning),
then that gives us a matrix of 16 images arranged in a 4 × 4 layout. Again, if you
keep the individual images’ sizes convenient multiples, the math is logical in its
implementation. Recall the array from table 4 in the previous chapter, shown
again here as table 9, and assume that each sprite image or cell is 32 × 32.
The x-and y-coordinates for the first row of images in terms of top-left cor-
ner and bottom right would be [(0,0), (32,32)]; the second cell, then, is [(32,0),
(64,32)]; the third cell [(64,0), (96,32)]; and finally [(96,0), (128,32)]. For the second
[Link]
Programming Sprites in Allegro 5 235
row, then, the first cell is [(0,32), (32,64)], the second cell [(32,32), (64,64)], and
so on—easy multiples of 32 to work with (see table 10).
Parameters
bitmap: The source image (sprite sheet) from which the region will be
drawn
sx (source x): The x-coordinate (top-left corner) of the region in the
source bitmap
sy (source y): The y-coordinate (top-left corner) of the region in the
source bitmap
[Link]
236 Practical Game Programming
Let’s go back to our example, the sprite sheet that contains 16 frames in a
4 × 4 grid, where each frame is 32 × 32 pixels. Let’s say you want to draw the
third frame in the first row, the one labelled 0,2 in the diagram (frame index
2 in zero-based counting):
Here, you see we’re calculating the sx and sy values based on the frame
index and cell size, allowing us to specify that image. In the case of an ani-
mation sequence, we can then increment or decrement values to achieve that
[Link]
Programming Sprites in Allegro 5 237
Row 0: Down
Row 1: Up
Row 2: Left
Row 3: Right
For the movement aspect, we then cycle through four frames (columns) for
each direction. For our sprite sheet, we’ll use an asset from OpenGameArt.org.
One of the things a game designer will need if they aren’t going to create their
own graphic and sound assets are open-source graphics and sounds. Many
sites can be found that provide these under the auspices of Creative Commons
licensing agreements. In many cases, all the artists request is proper acknow-
ledgement of their work as per the applicable Creative Commons licenses, and
as a video game designer, you should always seek to attribute and acknowledge
the work of others that you have used.
For this example, I’m using the sprite sheet RPG Hero (Walking Cycle) illus-
trated by Jana Ochse for 2DPIXX and published under a CC BY-SA 3.0 license (see
figure 4.6). Many thanks to Jana for providing artwork for those of us lacking
the graphical skill to create such necessary and integral assets in our quest to
create new games for others to enjoy.
// Constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
[Link]
238 Practical Game Programming
// Directions
enum Direction { UP = 1, LEFT = 3, RIGHT = 2, DOWN = 0 };
// Values match sprite sheet layout
int main() {
// Initialize Allegro
if (!al_init()) {
std::cerr << "Failed to initialize Allegro." <<
std::endl;
return—1;
}
if (!al_init_image_addon()) {
std::cerr << "Failed to initialize Allegro Image
Addon." << std::endl;
return—1;
}
if (!al_install_keyboard()) {
std::cerr << "Failed to initialize keyboard
input." << std::endl;
return—1;
}
// Create display
ALLEGRO_DISPLAY* display = al_create_display(SCREEN_
WIDTH, SCREEN_HEIGHT);
if (!display) {
std::cerr << "Failed to create display." <<
std::endl;
return—1;
}
[Link]
Programming Sprites in Allegro 5 239
ALLEGRO_EVENT_QUEUE* event_queue =
al_create_event_queue();
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
if (!sprite_sheet) {
std::cerr << "Failed to load sprite sheet." <<
std::endl;
al_destroy_display(display);
return—1;
}
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
[Link]
240 Practical Game Programming
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
if ([Link] == ALLEGRO_EVENT_TIMER) {
redraw = true;
if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_UP:
current_direction = UP;
sprite_y—= SPRITE_HEIGHT;
break;
case ALLEGRO_KEY_DOWN:
current_direction = DOWN;
sprite_y += SPRITE_HEIGHT;
break;
case ALLEGRO_KEY_LEFT:
current_direction = LEFT;
sprite_x—= SPRITE_WIDTH;
break;
case ALLEGRO_KEY_RIGHT:
current_direction = RIGHT;
sprite_x += SPRITE_WIDTH;
break;
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
}
}
[Link]
Programming Sprites in Allegro 5 241
// Clear screen
al_clear_to_color(al_map_rgb(0, 0, 0));
// Draw sprite
al_draw_bitmap_region(sprite_sheet, sx, sy,
SPRITE_WIDTH, SPRITE_HEIGHT, sprite_x, sprite_y, 0);
// Flip display
al_flip_display();
}
}
// Cleanup
al_destroy_bitmap(sprite_sheet);
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
al_destroy_display(display);
return 0;
}
Our result here is the character sprite on-screen changing orientation with
respect to the direction it’s moving in and a small animation sequence showing
its steps (see figure 4.7). All the graphics required to do this are in the single
loaded sprite sheet.
Pros
1. Greater Control
Using sprites gives you more control over every aspect of the animation,
including frame rate, sequence, and transitions. You can dynamically
change animations in response to user input or game events.
[Link]
242 Practical Game Programming
2. Better Quality
Since you’re not limited to the GIF format’s colour palette, you can use
full-colour images with higher quality.
3. Performance Optimization
Sprites can be more efficient, especially when combined with sprite
sheets. This reduces the memory footprint and can improve rendering
performance.
Cons
1. Complexity
Implementing sprite-based animations requires more coding effort.
You need to handle the logic for frame switching, timing, and possibly
interpolating between frames.
2. Asset Management
You’ll have to manage multiple image files or sprite sheets, which can
increase the complexity of asset management.
3. Development Time
More time is needed to develop and fine-tune sprite animations,
especially if you require sophisticated or interactive animations.
[Link]
Programming Sprites in Allegro 5 243
The choice between using animated GIFs and sprites in Allegro 5 largely
depends on your project’s specific needs. If simplicity and ease of use are your
primary concerns, you are experienced with utilizing/implementing external
libraries, and the limitations of GIFs are acceptable for your project, then using
animated GIFs might be the way to go. However, if you need higher-quality
animations, more control, and better performance in terms of using routines
that already exist in Allegro 5, then implementing animations with sprites is
usually the better choice.
Sprite Transformations
4. Theory: Compare al_draw_scaled_bitmap and al_draw_rotated_
bitmap. When would you use each?
5. Code Modification: Update SpriteMoveEffects.c to make the sprite
rotate counterclockwise instead of clockwise.
6. Project: Implement a sprite that scales up when the mouse hovers
over it and returns to normal when the mouse exits.
7. Theory: Explain how al_set_blender works and provide two blending
mode examples not covered in the chapter.
[Link]
244 Practical Game Programming
10. Theory: What are the advantages of using sprite sheets over individual
frame files?
Game Integration
14. Project: Develop a simple “collectathon” game where the player
controls a sprite to collect items (static sprites) scattered on the
screen.
15. Code Modification: Integrate sound effects into SpriteMoveEffects.c
(e.g., play a sound when the saucer changes direction).
Practical Challenges
20. Project: Create a “bullet hell” demo where the player sprite dodges
projectiles (rotating/scaling sprites) fired from the edges of the screen.
21. Code Modification: Implement smooth mouse-following movement
for the saucer in SpriteMove.c.
Creative Applications
22. Project: Design an interactive UI with button sprites that change
colour when hovered over and trigger actions (e.g., pause/resume).
[Link]
Programming Sprites in Allegro 5 245
Final Project
26. Capstone: Build a mini 2D platformer with the following:
• Animated player sprite (idle, running, jumping)
• Parallax-scrolling background
• Collectible items and enemies (dynamic sprites)
• Collision detection and health bar (using scaling sprites)
Experimental Tasks
27. Project: Simulate a “water reflection” effect by drawing a flipped,
translucent copy of the sprite below its original position.
28. Code Modification: Replace the static background in SpriteMove.c
with a dynamically generated starfield using al_draw_pixel.
[Link]
This page intentionally left blank
5
Programming
Backgrounds for
Video Games
Backgrounds in video games are essential for setting up the scene and enhan-
cing the visual appeal of the game world.
Backgrounds play the following important roles in video games:
[Link]
248 Practical Game Programming
1. Static Backgrounds
These are nonmoving images that provide a backdrop for the game.
They are commonly used in 2D games and can be highly detailed to
create a rich environment.
2. Scrolling Backgrounds
Often used in side-scrolling games, these backgrounds move
horizontally or vertically to give the illusion of depth and motion.
Parallax scrolling, where multiple layers move at different speeds,
enhances this effect.
3. Dynamic Backgrounds
These backgrounds change in response to game events or player
actions. For example, the background might shift from day to night or
change weather conditions.
4. 3D Backgrounds
In 3D games, backgrounds are part of the 3D environment and can
include complex structures and landscapes. These backgrounds are
rendered in real time and can interact with the game’s lighting and
physics.
Classic examples include Super Mario Bros., Mega Man, Sonic the Hedgehog,
The Legend of Zelda, and many visual novels where the background sets the
mood but does not move.
[Link]
Programming Backgrounds for Video Games 249
Full-Screen Static Image: A single image that fills the entire screen
• Common in puzzle games, RPG towns, menus, and narrative scenes
• Easiest type to implement
• Often painted or rendered as a complete scene
Tiled Backgrounds: Built from smaller repeated tiles (e.g., 16 × 16,
32 × 32, or 64 × 64 pixel tiles)
• Saves memory compared to large images
• Provides modularity and easy level creation
• Common in platformers (Mario, Metroid, Celeste)
Layered Static Backgrounds (Non-Parallax): Multiple static layers
(foreground, mid-ground, background), but none that scroll or move
• Used for atmospheric depth
• Allows artists to organize elements logically (e.g., walls, props, sky)
Static with Selective Animated Elements: A static background with a
few animated components (e.g., flickering lights, moving water)
• Still considered “static backgrounds” because the world behind
gameplay does not scroll
• Often implemented using sprites placed on top of the static image
• Good compromise between aesthetics and performance
Room or Scene Backgrounds (Adventure/Visual Novels): Highly
detailed illustrations representing rooms, landscapes, or story
locations
• The player character may not move relative to the background
• Widely used in visual novel engines and point-and-click adventures
UI-Integrated Backgrounds: Backgrounds forming part of the user
interface or menu system
• Main menus, inventory screens, pause menus
• Sometimes blurred or darkened gameplay screens
[Link]
250 Practical Game Programming
Using Allegro 5:
Best practices:
• Keep background resolution consistent with your game resolution
• Compress images appropriately
• Preload backgrounds during scene initialization to avoid in-game
stutter
2. Drawing the Background
Static backgrounds should always be drawn before all movable objects.
Basic method:
al_draw_bitmap(background, 0, 0, 0);
al_draw_scaled_bitmap(
background, 0, 0, original_w, original_h,
0, 0, SCREEN_W, SCREEN_H, 0);
4. Managing Layers
Static backgrounds may consist of multiple layers (foreground, middle
ground).
[Link]
Programming Backgrounds for Video Games 251
al_draw_bitmap(bg_far, 0, 0, 0);
al_draw_bitmap(bg_mid, 0, 0, 0);
al_draw_bitmap(bg_near, 0, 0, 0);
Even without parallax movement, this can create depth and easily
accommodate special effects.
5. Optimizing for Performance
Because static backgrounds do not change, the following can be used
for optimization:
• Load them once, not every frame
• Avoid redrawing complex tiles unless necessary
• Use Allegro’s bitmap flags for speed:
al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP);
al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR |
ALLEGRO_MAG_NEAREST);
Static backgrounds are ideal for levels or scenes where the environment
does not need to change or move. They are often used in side-scrolling games,
adventure games, and certain RPGs.
Classic games like Super Mario Bros. and The Legend of Zelda use static back-
grounds to create immersive worlds without the need for complex animations.
1. Initialize Allegro
Make sure you have Allegro5 installed and properly set up in your
project.
2. Load the Background Image
Use Allegro functions to load your background image.
3. Draw the Background
In your game loop, draw the background image before drawing other
game elements.
[Link]
252 Practical Game Programming
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
int main() {
// Initialize Allegro
al_init();
al_init_image_addon();
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
[Link]
Programming Backgrounds for Video Games 253
break;
}
}
// Clean up
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
}
The main elements from this template will be utilized in the full code
example provided next:
1. Initialization
Allegro and the image add-on are initialized.
2. Loading the Background
The background image is loaded using al_load_bitmap.
3. Drawing the Background
The background is drawn in the game loop using al_draw_bitmap.
4. End
Check to see if the Esc key is pressed to exit the example.
When you test the code, make sure to replace [Link] with the path
to your actual background image file if you don’t locate it in the same directory
as your executable code (which is where it expects to find the image currently):
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
int main() {
// Initialize Allegro and image addon
al_init();
al_init_image_addon();
al_install_keyboard(); // Still required to use
keyboard input
// Create display
[Link]
254 Practical Game Programming
// Main loop
bool running = true;
while (running) {
// Draw background
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(background, 0, 0, 0);
al_flip_display();
[Link]
Programming Backgrounds for Video Games 255
// Cleanup
al_destroy_event_queue(event_queue);
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
[Link]
256 Practical Game Programming
1. Initialize Allegro
Ensure Allegro and its add-ons are properly initialized.
[Link]
Programming Backgrounds for Video Games 257
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
int main() {
// Initialize Allegro
al_init();
al_init_image_addon();
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
[Link]
258 Practical Game Programming
// Clean up
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
}
The main elements from this template will be utilized in the full code
example provided next:
1. Initialization
Allegro and the image add-on are initialized.
2. Loading the Background
The background image is loaded using al_load_bitmap.
3. Scrolling Logic
The background’s x-coordinate (bg_x) is updated to create the
scrolling effect. When the background moves completely off-screen,
its position is reset.
4. Drawing the Background
The background is drawn twice to ensure seamless scrolling.
[Link]
Programming Backgrounds for Video Games 259
5. End
Check for a key press of the Esc key to end the example.
CODE EXAMPLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
int main() {
al_init();
al_init_image_addon();
al_install_keyboard();
ALLEGRO_EVENT_QUEUE *queue =
al_create_event_queue();
al_register_event_source(queue,
al_get_display_event_source(display))
al_register_event_source(queue,
al_get_keyboard_event_source());
[Link]
260 Practical Game Programming
/*——logic tick——*/
if ([Link] == ALLEGRO_EVENT_TIMER) {
bg_x—= scroll_speed;
if (bg_x <=—
al_get_bitmap_width(background))
bg_x = 0;
}
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
running = false;
al_flip_display();
}
}
al_destroy_timer(timer);
al_destroy_event_queue(queue);
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
}
[Link]
Programming Backgrounds for Video Games 261
[Link]
262 Practical Game Programming
1. Initialize Allegro
Ensure Allegro and its add-ons are properly initialized.
2. Load the Background Image
Load your background image using Allegro functions.
3. Implement Dynamic Elements
Add elements that change over time, such as moving objects or
changing colours.
4. Draw the Background
In your game loop, draw the background and update the dynamic
elements.
5. Cleanup
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
int main() {
// Initialize Allegro
al_init();
al_init_image_addon();
al_init_primitives_addon();
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
[Link]
Programming Backgrounds for Video Games 263
// Clean up
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
}
[Link]
264 Practical Game Programming
CODE EXAMPLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <cstdlib> // for rand()
#include <ctime> // for seeding rand()
int main() {
// Initialize Allegro and addons
al_init();
al_init_image_addon();
al_init_primitives_addon();
al_install_keyboard();
srand(static_cast<unsigned int>(time(nullptr))); //
seed RNG
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
if (!display) return—
1;
[Link]
Programming Backgrounds for Video Games 265
al_register_event_source(event_queue,
al_get_timer_event_source(t
al_start_timer(timer);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN &&
[Link] == ALLEGRO_KEY_
ESCAPE) {
running = false;
// Here the timer generates a 'move' event 60
times a sec
} else if ([Link] == ALLEGRO_EVENT_TIMER) {
// Randomly move the star a little bit
star_x += (rand() % 7—
3); // random move
between—3 an
star_y += (rand() % 7—
3);
// Redraw everything
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(background, 0, 0, 0);
al_draw_filled_circle(star_x, star_y, 5,
al_map_rgb(255,
al_flip_display();
}
}
// Cleanup
// Cleanup
[Link]
266 Practical Game Programming
al_destroy_bitmap(background);
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
al_destroy_display(display);
return 0;
}
CODE EXAMPLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
int main() {
// Initialize Allegro
al_init();
al_init_image_addon();
al_init_primitives_addon();
al_install_keyboard();
[Link]
Programming Backgrounds for Video Games 267
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
if (!display) return—
1;
// Load background image and sprite
ALLEGRO_BITMAP *background = al_load_
bitmap("[Link]");
ALLEGRO_BITMAP *star_sprite = al_load_bitmap("star.
png");
if (!background || !star_sprite) return—
1;
// Get star dimensions
int star_w = al_get_bitmap_width(star_sprite);
int star_h = al_get_bitmap_height(star_sprite);
[Link]
268 Practical Game Programming
// Clean up
al_destroy_event_queue(queue);
al_destroy_bitmap(star_sprite);
al_destroy_bitmap(background);
al_destroy_display(display);
return 0;
}
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <cstdlib>
#include <ctime>
struct Snowflake {
float x, y, speed, drift;
};
[Link]
Programming Backgrounds for Video Games 269
int main() {
// Initialize Allegro
al_init();
al_init_image_addon();
al_init_primitives_addon();
al_install_keyboard(); //Enable keyboard input
// Create display
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
al_set_window_title(display, "Star Sprite with
Snowfall");
// Initialize RNG
std::srand(static_cast<unsigned
int>(std::time(nullptr)));
// Create snowflakes
const int NUM_SNOWFLAKES = 150;
[Link]
270 Practical Game Programming
Snowflake snow[NUM_SNOWFLAKES];
for (int i = 0; i < NUM_SNOWFLAKES; ++i) {
snow[i].x = std::rand() % 800;
snow[i].y = std::rand() % 600;
snow[i].speed = 1 + std::rand() % 3;// Fall speed
snow[i].drift = ((std::rand() % 21)—
10) /
40.0f;// Gentle horizontal drift
}
// Update snowflakes
for (int i = 0; i < NUM_SNOWFLAKES; ++i) {
snow[i].y += snow[i].speed;
snow[i].x += snow[i].drift;
if (snow[i].y > 600) {
snow[i].y = 0;
snow[i].x = std::rand() % 800;
}
if (snow[i].x < 0) snow[i].x += 800;
if (snow[i].x > 800) snow[i].x—
= 800;
}
// Clear screen
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(background, 0, 0, 0);
[Link]
Programming Backgrounds for Video Games 271
(1.0f—
t) * 0.2f + t * 1.0f,
0.3f
);
al_draw_filled_rectangle(0, y, 800, y + 1,
gradient_color);
}
// Flip display
al_flip_display();
al_rest(0.01);
}
// Cleanup
al_destroy_event_queue(event_queue);
al_destroy_bitmap(background);
al_destroy_bitmap(star_sprite);
al_destroy_display(display);
[Link]
272 Practical Game Programming
return 0;
}
[Link]
Programming Backgrounds for Video Games 273
#include <allegro5/allegro.h>
#include <allegro5/allegro_opengl.h>
#include <GL/gl.h>
#include <GL/glu.h>
void initOpenGL() {
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
gluPerspective(45.0, 800.0 / 600.0, 1.0, 1000.0);
glMatrixMode(GL_MODELVIEW);
}
void draw3DBackground() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0);
[Link]
274 Practical Game Programming
glBegin(GL_QUADS);
glColor3f(0.0, 1.0, 0.0); // Green color
glVertex3f(-1.0,—1.0,—1.0);
glVertex3f(1.0,—1.0,—1.0);
glVertex3f(1.0, 1.0,—1.0);
glVertex3f(-1.0, 1.0,—1.0);
glEnd();
}
int main() {
// Initialize Allegro
al_init();
al_set_new_display_flags(ALLEGRO_OPENGL);
ALLEGRO_DISPLAY *display = al_create_display(800,
600);
// Initialize OpenGL
initOpenGL();
// Clean up
al_destroy_display(display);
[Link]
Programming Backgrounds for Video Games 275
return 0;
}
1. Initialization
Allegro and OpenGL are initialized.
2. OpenGL Setup
OpenGL settings are configured for 3D rendering.
3. Drawing the Background
A simple 3D cube is drawn as the background.
In the next example, we’ll build on the template to create a simple multi-
colour rotating cube.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, (double)w / (double)h, 0.1,
1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
[Link]
276 Practical Game Programming
glLoadIdentity();
// Camera
gluLookAt(0.0, 0.0, 5.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
// Rotate cube
glRotatef(angleDeg, 0.0f, 1.0f, 0.0f);
glRotatef(angleDeg * 0.6f, 1.0f, 0.0f, 0.0f);
// Back (z =—
1)
glColor3f(0, 1, 0); // green
glVertex3f( 1,—1,—1);
glVertex3f(-1,—1,—1);
glVertex3f(-1, 1,—1);
glVertex3f( 1, 1,—1);
// Left (x =—
1)
glColor3f(0, 0, 1); // blue
glVertex3f(-1,—1,—1);
glVertex3f(-1,—1, 1);
glVertex3f(-1, 1, 1);
glVertex3f(-1, 1,—1);
// Right (x = +1)
glColor3f(1, 1, 0); // yellow
glVertex3f( 1,—1, 1);
glVertex3f( 1,—1,—1);
glVertex3f( 1, 1,—1);
glVertex3f( 1, 1, 1);
[Link]
Programming Backgrounds for Video Games 277
// Top (y = +1)
glColor3f(0, 1, 1); // cyan
glVertex3f(-1, 1, 1);
glVertex3f( 1, 1, 1);
glVertex3f( 1, 1,—1);
glVertex3f(-1, 1,—1);
// Bottom (y =—
1)
glColor3f(1, 0, 1); // magenta
glVertex3f(-1,—1,—1);
glVertex3f( 1,—1,—1);
glVertex3f( 1,—1, 1);
glVertex3f(-1,—1, 1);
glEnd();
}
int main() {
al_init();
al_install_keyboard();
initOpenGL(W, H);
[Link]
278 Practical Game Programming
while (running) {
// Update rotation
double now = al_get_time();
double dt = now—
last_time;
last_time = now;
// Draw
drawCube(rotation_angle);
al_flip_display();
al_destroy_event_queue(queue);
al_destroy_display(display);
return 0;
}
[Link]
Programming Backgrounds for Video Games 279
[Link]
280 Practical Game Programming
Mixed Concepts
19. Hybrid Backgrounds: Combine a static foreground with a parallax-
scrolling middle ground and a dynamic sky background.
20. Game Jam Project: Build a minigame where the background changes
type (static to scrolling to dynamic) based on player progress.
21. Debugging Task: Fix a provided broken scrolling background code
that suffers from flickering and memory leaks.
Advanced Projects
25. Particle System: Integrate a particle engine into a dynamic
background to simulate fire, smoke, or magic effects.
[Link]
Programming Backgrounds for Video Games 281
Creative Challenges
29. Art Pipeline: Create a toolchain (e.g., Python script) to batch-convert
and optimize background assets for Allegro5.
30. Final Project: Build a complete game level with all four background
types (static, scrolling, dynamic, 3D) and a write-up explaining your
design choices.
[Link]
This page intentionally left blank
6
We learned in the first chapter of this book about different kinds of video
games, including action games, adventure games, arcade games, board games,
puzzle games, role-playing games, sports games, strategy games, and utilities
games, as well as game demos and emulators. This is in no way a complete
list, and from time to time, new types of games will be invented and added to
the list. For now, however, these genres at least can get you started thinking
about what type of game you would like to design and develop as your first
video game.
[Link]
284 Practical Game Programming
[Link]
Game Design and Development Fundamentals 285
Combining these elements can help you conceive and develop an amazing
video game that stands out in the competitive gaming industry.
[Link]
286 Practical Game Programming
[Link]
Game Design and Development Fundamentals 287
[Link]
288 Practical Game Programming
[Link]
Game Design and Development Fundamentals 289
By following these steps, you can effectively create a detailed design and
implement it, ensuring a smooth development process and a high-quality
final product.
[Link]
290 Practical Game Programming
10. Sound Integration: Add background music and sound effects (e.g.,
jump, collect) to a simple game using ALLEGRO_SAMPLE.
11. UI System: Build a main menu with clickable buttons (Start, Options,
Exit) using Allegro’s primitives and event handling.
12. HUD Implementation: Display a health bar, score counter, and timer
in a top-down shooter prototype.
13. State Management: Program a pause menu that toggles game states
(playing/paused) using finite state machines.
14. Particle Effects: Simulate rain or snow using Allegro’s particle system
or custom sprites.
15. Save/Load System: Serialize player progress (e.g., score, level) to a text
file and reload it on start-up.
16. Power-Up System: Implement collectible power-ups (e.g., speed
boost, invincibility) in a platformer.
17. Physics Simulation: Code basic platformer physics (gravity, jump
velocity) without using external libraries.
18. Procedural Generation: Generate randomized terrain for a dungeon
crawler using Perlin noise or cellular automata.
19. Multiplayer Prototype: Build a local two-player game (e.g., Pong)
using split-screen or shared keyboard input.
[Link]
Game Design and Development Fundamentals 291
[Link]
This page intentionally left blank
7
Advanced Topics
in Practical Game
Programming
7.1. Multithreading
Multithreading is essential in video games for several reasons, primarily related
to performance and responsiveness.
Improved Performance
This is achieved through:
[Link]
294 Practical Game Programming
Responsiveness
This is reflected as:
Scalability
Future-Proofing: As hardware evolves and more CPU cores become
available, multithreaded games can scale to take advantage of these
advancements, ensuring better performance on newer systems.
Complex Simulations: Multithreading enables more complex
simulations and interactions within the game world, allowing for
richer and more detailed environments.
Task Separation
Dedicated Threads for Specific Tasks: Different aspects of the
game—such as rendering, physics, AI, and audio—can be handled
on separate threads. This separation ensures that intensive tasks
do not interfere with one another, leading to a more stable and
efficient game.
[Link]
Advanced Topics in Practical Game Programming 295
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
[Link]
296 Practical Game Programming
#include <stdio.h>
#include <stdbool.h>
typedef struct {
int id; // 1 or 2 (for display)
int counter; // per-
thread counter
bool running; // true while thread loop is
active
} ThreadData;
// mark running
al_lock_mutex(mutex);
td-
>running = true;
al_unlock_mutex(mutex);
while (!al_get_thread_should_stop(thread)) {
// update this thread's own counter
al_lock_mutex(mutex);
td->counter++;
al_unlock_mutex(mutex);
// mark stopping
al_lock_mutex(mutex);
td-
>running = false;
al_unlock_mutex(mutex);
[Link]
Advanced Topics in Practical Game Programming 297
return NULL;
}
int main() {
// Allegro init
if (!al_init()) { fprintf(stderr, "Failed to init
Allegro\n"); return—
1; }
al_init_font_addon();
al_init_ttf_addon();
al_install_keyboard();
mutex = al_create_mutex();
[Link]
298 Practical Game Programming
al_register_event_source(event_queue,
al_get_keyboard_event_source());
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_start_timer(timer);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN
&&
[Link] == ALLEGRO_KEY_
ESCAPE) {
running = false;
}
if ([Link] == ALLEGRO_EVENT_TIMER ||
[Link] == ALLEGRO_EVENT_DISPLAY_EXPOSE) {
al_clear_to_color(al_map_rgb(0, 0, 0));
al_lock_mutex(mutex);
// Labels show which thread the numbers
belong to
al_draw_textf(font, al_map_rgb(255, 255,
255), 400, 220, ALLEGRO_ALIGN_CENTRE,
"Thread 1 Counter: %d", td[0].
counter);
al_draw_textf(font, al_map_rgb(200, 200,
0), 400, 260, ALLEGRO_ALIGN_CENTRE,
"Thread 1 Status: %s", td[0].
running ? "Running..." : "Stopping");
[Link]
Advanced Topics in Practical Game Programming 299
al_flip_display();
}
}
// Cleanup
al_destroy_thread(threads[0]);
al_destroy_thread(threads[1]);
al_destroy_mutex(mutex);
al_destroy_font(font);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
al_destroy_timer(timer);
return 0;
}
This example initializes Allegro, creates two threads that run concurrently,
and prints messages to the console. Each thread runs a loop that prints its
identifier and a counter value, simulating work by sleeping for one second
between iterations. The main program waits for both threads to finish before
cleaning up and exiting.
[Link]
300 Practical Game Programming
[Link]
Advanced Topics in Practical Game Programming 301
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_opengl.h>
int main() {
al_init();
al_init_image_addon();
al_install_keyboard();
void main() {
frag_uv = al_texcoord;
gl_Position = al_projview_matrix * vec4(al_pos, 0.0,
1.0);
}
[Link]
302 Practical Game Programming
void main() {
vec4 original = texture(tex, frag_uv);
color = original * (0.5 + 0.5 * sin(time));
}
[Link]
Advanced Topics in Practical Game Programming 303
while (running) {
// Update time for animation
time += 0.01;
al_flip_display();
al_clear_to_color(al_map_rgb(0, 0, 0));
}
// Cleanup
al_destroy_shader(shader);
al_destroy_bitmap(texture);
al_destroy_display(display);
[Link]
304 Practical Game Programming
void main() {
vec4 original = texture(tex, frag_uv);
float avg = (original.r + original.g + original.b) /
3.0;
color = vec4(avg, avg, avg, original.a);
}
Wave Distortion
Wave distortions are applied by the fragment shader code here:
void main() {
vec2 uv = frag_uv + 0.05 * sin(time + frag_uv.y *
10.0);
color = texture(tex, uv);
}
Next up is a complete example that will load a texture image on the OpenGL
fragment (here a rectangle) and slowly fade the image in and out.
[Link]
#version 330 core
in vec2 al_pos; // pixel coords from Allegro
in vec2 al_texcoord; // UVs from Allegro (usually
0..w, 0..h)
out vec2 frag_uv;
void main() {
frag_uv = al_texcoord;
// Convert pixels—
> Normalized Device Coordinates
// x: 0..W ->—1..+1
// y: 0..H -
> +1..-
1 (note the flip)
[Link]
Advanced Topics in Practical Game Programming 305
vec2 ndc;
ndc.x = (al_pos.x / u_view.x) * 2.0—
1.0;
ndc.y = 1.0—
(al_pos.y / u_view.y) * 2.0;
[Link]
#version 330 core
out vec4 color;
uniform sampler2D tex;
uniform float time;
void main() {
vec2 wh = vec2(textureSize(tex, 0));
vec2 uv = gl_FragCoord.xy / wh; // 0..1 across
the window/texture
vec4 original = texture(tex, uv);
color = original * (0.5 + 0.5 * sin(time));
}
[Link]
The following is a complete example of custom shaders.
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_opengl.h>
#include <cstdio>
#include <fstream>
#include <string>
[Link]
306 Practical Game Programming
int main() {
// Init Allegro
if (!al_init()) return—
1;
if (!al_init_image_addon()) return—
1;
al_install_keyboard();
// Display (OpenGL)
al_set_new_display_flags(ALLEGRO_OPENGL);
ALLEGRO_DISPLAY* display = al_create_display(800,
600);
if (!display) return—
1;
//—
—Required files present? (only checks we keep)—
—
if (!file_exists("[Link]")) {
std::fprintf(stderr, "Missing vertex.
glsl\n"); return—1; }
if (!file_exists("[Link]")) {
std::fprintf(stderr, "Missing [Link]\n");
return—1; }
if (!al_attach_shader_source_file(shader, ALLEGRO_
VERTEX_SHADER, "[Link]")) {
std::fprintf(stderr, "Vertex attach error: %s\n",
al_get_shader_log(shader));
return—1;
}
if (!al_attach_shader_source_file(shader, ALLEGRO_
PIXEL_SHADER, "[Link]")) {
[Link]
Advanced Topics in Practical Game Programming 307
// Events
ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
if (!queue) return—
1;
al_register_event_source(queue,
al_get_keyboard_event_source());
al_register_event_source(queue,
al_get_display_event_source(display));
// Main loop
bool running = true;
double start_time = al_get_time();
while (running) {
ALLEGRO_EVENT ev;
while (al_get_next_event(queue, &ev)) {
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
running = false;
if ([Link] == ALLEGRO_EVENT_KEY_DOWN &&
[Link] == ALLEGRO_KEY_
ESCAPE) running = false;
}
float t = static_cast<float>(al_get_time()—
start_
time);
[Link]
308 Practical Game Programming
// Draw
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(texture, 0, 0, 0);
// Cleanup
al_destroy_shader(shader);
al_destroy_bitmap(texture);
al_destroy_event_queue(queue);
al_destroy_display(display);
return 0;
}
[Link]
Advanced Topics in Practical Game Programming 309
Client Responsibilities
• Send player input to the server.
• Receive game state updates.
• Render the game locally.
[Link]
310 Practical Game Programming
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
int addrlen = sizeof(address);
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(12345);
close(client_fd);
close(server_fd);
return 0;
}
2. Define Protocols
Create message formats for player actions and game events. For example:
struct PlayerUpdate {
int id;
float x, y;
};
3. Handle Communication
Use send() and recv() to transmit serialized data.
[Link]
Advanced Topics in Practical Game Programming 311
5. Security Considerations
• Validate all incoming data.
• Use server-side authority to prevent cheating.
• Consider encryption for sensitive data.
[Link]
312 Practical Game Programming
al_start_timer(timer);
while (true) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
// Check for network updates
// Update game state
// Render frame
}
}
7.4. AI Programming
Artificial intelligence (AI) is crucial for creating engaging NPCs (nonplayer
characters) in games. In this section, we will learn how to implement path-
finding, finite state machines (FSM), and basic decision-making for enemies
in Allegro 5 using C/C++. We’ll create a simple game where an enemy chases
the player using the A* pathfinding algorithm.
[Link]
Advanced Topics in Practical Game Programming 313
EXAMPLE CODE
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
int main() {
al_init();
al_init_image_addon();
al_init_primitives_addon();
al_install_keyboard();
#define MAP_WIDTH 25
#define MAP_HEIGHT 18
// 0 = walkable, 1 = blocked
int grid[MAP_HEIGHT][MAP_WIDTH] = {
{0,0,0,1,0,0,...}, // Example map data
//...(fill with your own map)
};
A* Algorithm Implementation
We can use the A* algorithm to find the shortest path between two points. Think
of each walkable tile as a node. For every node, we store:
[Link]
314 Practical Game Programming
pop the best node from the open set → if it’s the goal, rebuild the
path via parents →
else relax its neighbors, which means:
compute tentative new cost g’ = g + move_cost, update
neighbor’s g,h,f,parent and
if this is better, and push/update it in the open set.
This process is repeated until we either reach the goal or the open set
empties.
SIMPLIFIED CODE
typedef struct Node {
int x, y;
int g, h, f; // g = cost from start, h = heuristic, f
= g + h
struct Node *parent;
} Node;
[Link]
Advanced Topics in Practical Game Programming 315
typedef enum {
STATE_IDLE,
STATE_CHASE,
STATE_PATROL
} AIState;
typedef struct {
int x, y; // Current grid position
AIState state;
ALLEGRO_PATH *path;
} Enemy;
State Transitions
Update the enemy’s state based on player proximity:
switch (enemy-
>state) {
case STATE_IDLE:
if (distance < 5) { // If player is close
enemy-
>state = STATE_CHASE;
enemy->path = find_path(enemy->x,
enemy-
>y, player_x, player_y);
}
break;
case STATE_CHASE:
if (distance > 10) { // If player is far
enemy-
>state = STATE_IDLE;
al_destroy_path(enemy->path);
}
break;
[Link]
316 Practical Game Programming
while (running) {
// Handle input (move player with arrow keys)...
// Update enemy AI
update_enemy(&enemy, player_x, player_y);
// Draw everything
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(player, player_x * GRID_SIZE, player_y
* GRID_SIZE, 0);
al_draw_bitmap(enemy, enemy.x * GRID_SIZE, enemy.y *
GRID_SIZE, 0);
al_flip_display();
}
In the following example, only the Idle and Chase states are implemented.
The student can add and experiment with adding code for Patrol and changing
[Link]
Advanced Topics in Practical Game Programming 317
the grid matrix to add more blocked (not walkable) tiles. Arrow keys move the
player, and if moved in close proximity to the enemy object, it will immediately
intercept.
GRIDAI EXAMPLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <queue>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm> // for std::reverse
#define GRID_SIZE 32
#define MAP_WIDTH 25
#define MAP_HEIGHT 18
#define SCREEN_WIDTH (MAP_WIDTH * GRID_SIZE)
#define SCREEN_HEIGHT (MAP_HEIGHT * GRID_SIZE)
typedef struct {
int x, y;
int g, h;
int f;
int came_from_x, came_from_y;
} Node;
typedef enum {
STATE_IDLE,
[Link]
318 Practical Game Programming
STATE_CHASE
} AIState;
typedef struct {
float x, y;
AIState state;
std::vector<std::pair<int, int>> path;
} Enemy;
int grid[MAP_HEIGHT][MAP_WIDTH] = {
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
[Link]
Advanced Topics in Practical Game Programming 319
std::priority_queue<Node, std::vector<Node>,
NodeCompareMinF> open_set;
bool closed[MAP_HEIGHT][MAP_WIDTH] = {false};
Node came_from[MAP_HEIGHT][MAP_WIDTH];
while (!open_set.empty()) {
Node current = open_set.top();
open_set.pop();
closed[current.y][current.x] = true;
[Link]
320 Practical Game Programming
return path;
}
switch ([Link]) {
case STATE_IDLE:
if (distance < 5) {
[Link] = STATE_CHASE;
[Link] = find_path((int)enemy.x,
(int)enemy.y, player_x, player_y);
}
break;
case STATE_CHASE:
if (distance > 10) {
[Link] = STATE_IDLE;
[Link]();
}
break;
}
}
int main() {
al_init();
al_install_keyboard();
al_init_image_addon();
al_init_primitives_addon();
[Link]
Advanced Topics in Practical Game Programming 321
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
case ALLEGRO_KEY_UP:
if (in_bounds(player_x, player_y—
1)
&& grid[player_y—
1][player_x] == 0) player_y—
;
break;
case ALLEGRO_KEY_DOWN:
if (in_bounds(player_x, player_y + 1)
&& grid[player_y + 1][player_x] == 0) player_y++;
break;
case ALLEGRO_KEY_LEFT:
if (in_bounds(player_x—
1, player_y)
&& grid[player_y][player_x—
1] == 0) player_x—
;
break;
case ALLEGRO_KEY_RIGHT:
if (in_bounds(player_x + 1, player_y)
&& grid[player_y][player_x + 1] == 0) player_x++;
break;
}
} else if ([Link] == ALLEGRO_EVENT_TIMER) {
update_enemy(enemy, player_x, player_y);
[Link]
322 Practical Game Programming
redraw = true;
}
al_clear_to_color(al_map_rgb(0, 0, 0));
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
ALLEGRO_COLOR color = grid[y][x] == 1
? al_map_rgb(100, 100, 100) : al_map_rgb(50, 50, 50);
al_draw_filled_rectangle(x * GRID_
SIZE, y * GRID_SIZE, (x + 1) * GRID_SIZE, (y + 1) *
GRID_SIZE, color);
}
}
al_draw_filled_circle(player_x * GRID_SIZE
+ GRID_SIZE / 2, player_y * GRID_SIZE + GRID_SIZE / 2,
GRID_SIZE / 2—
2, al_map_rgb(0, 255, 0));
al_draw_filled_circle(enemy.x * GRID_SIZE +
GRID_SIZE / 2, enemy.y * GRID_SIZE + GRID_SIZE / 2, GRID_
SIZE / 2—
2, al_map_rgb(255, 0, 0));
al_flip_display();
}
}
al_destroy_display(display);
al_destroy_event_queue(queue);
al_destroy_timer(timer);
return 0;
}
[Link]
Advanced Topics in Practical Game Programming 323
if (distance > 0) {
enemy-
>x += (dx / distance) * speed;
enemy-
>y += (dy / distance) * speed;
}
}
Behavior Trees
For more complex AI, we can use behavior trees to manage decision hierarch-
ies, emulating a decision process. Here, we give the AI the choice to chase the
user (seek), but if the user moves out of range (flees), the AI returns to a patrol
pattern:
// Example: Patrol—
> Check for Player—
> Chase
bool should_chase(Enemy *enemy) {
return (distance_to_player < 5);
}
[Link]
324 Practical Game Programming
// AI_smooth_seek.cpp
// Patrol (square, 5 tiles per edge)—
> Check player
distance—> Chase
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <queue>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm> // reverse
#define GRID_SIZE 32
#define MAP_WIDTH 25
#define MAP_HEIGHT 18
#define SCREEN_WIDTH (MAP_WIDTH * GRID_SIZE)
#define SCREEN_HEIGHT (MAP_HEIGHT * GRID_SIZE)
// ──────────────────────────────────────────────────────────
// AI state & enemy
// ──────────────────────────────────────────────────────────
typedef enum {
STATE_PATROL,
STATE_CHASE
} AIState;
typedef struct {
float x, y; // grid coords
(integers stored in float)
AIState state;
std::vector<std::pair<int, int> > path; // used
during chase
// Patrol bookkeeping
int patrol_dir; // 0=right, 1=down, 2=left, 3=up
[Link]
Advanced Topics in Practical Game Programming 325
// ──────────────────────────────────────────────────────────
// Grid/map helpers
// ──────────────────────────────────────────────────────────
int grid[MAP_HEIGHT][MAP_WIDTH] = {
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
// Manhattan heuristic
inline int heuristic(int x1, int y1, int x2, int y2) {
return abs(x1—
x2) + abs(y1—
y2);
}
[Link]
326 Practical Game Programming
// ──────────────────────────────────────────────────────────
// Safe A* (fully initialized parents, guarded
reconstruction)
// ──────────────────────────────────────────────────────────
std::vector<std::pair<int,int> > find_path(int sx, int
sy, int ex, int ey) {
std::vector<std::pair<int,int> > path;
struct QN { int x, y, g, f; };
struct Cmp { bool operator()(const QN& a, const QN&
b) const { return a.f > b.f; } };
int g_cost[MAP_HEIGHT][MAP_WIDTH];
bool closed[MAP_HEIGHT][MAP_WIDTH];
Parent parent[MAP_HEIGHT][MAP_WIDTH];
[Link]
Advanced Topics in Practical Game Programming 327
while (![Link]()) {
QN cur = [Link](); [Link]();
if (closed[cur.y][cur.x]) continue;
closed[cur.y][cur.x] = true;
[Link]
328 Practical Game Programming
// ──────────────────────────────────────────────────────────
// Behavior tree–
style AI helpers
// ──────────────────────────────────────────────────────────
static inline float dist_to_player(const Enemy* e, int
px, int py) {
float dx = float(e-
>x)—
float(px);
float dy = float(e-
>y)—
float(py);
return sqrtf(dx*dx + dy*dy);
}
[Link]
Advanced Topics in Practical Game Programming 329
int nx = (int)enemy-
>x;
int ny = (int)enemy-
>y;
switch (enemy-
>patrol_dir) {
case 0: nx++; break; // right
case 1: ny++; break; // down
case 2: nx—
; break; // left
case 3: ny—
; break; // up
}
if (is_free(nx, ny)) {
enemy-
>x = nx;
enemy-
>y = ny;
enemy->patrol_steps++;
} else {
// If blocked or out of bounds, force a turn next
tick
enemy-
>patrol_steps = 5;
}
}
[Link]
330 Practical Game Programming
// ──────────────────────────────────────────────────────────
// Demo loop
// ──────────────────────────────────────────────────────────
int main() {
al_init();
al_install_keyboard();
al_init_image_addon();
al_init_primitives_addon();
Enemy enemy;
enemy.x = 10; enemy.y = 10;
[Link] = STATE_PATROL;
enemy.patrol_dir = 0;
enemy.patrol_steps = 0;
[Link]();
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
[Link]
Advanced Topics in Practical Game Programming 331
switch ([Link]) {
case ALLEGRO_KEY_ESCAPE: running = false;
break;
case ALLEGRO_KEY_UP: if (is_
free(player_x, player_y-1))—player_y; break;
case ALLEGRO_KEY_DOWN: if (is_
free(player_x, player_y+1)) ++player_y; break;
case ALLEGRO_KEY_LEFT: if (is_
free(player_x-1, player_y))—player_x; break;
case ALLEGRO_KEY_RIGHT: if (is_
free(player_x+1, player_y)) ++player_x; break;
}
} else if ([Link] == ALLEGRO_EVENT_TIMER) {
// Update AI (BT root)
update_ai(&enemy, player_x, player_y);
[Link]
332 Practical Game Programming
}
}
redraw = true;
}
al_clear_to_color(al_map_rgb(0, 0, 0));
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
ALLEGRO_COLOR color = grid[y][x] == 1
? al_map_rgb(100, 100, 100)
:
al_map_rgb(50, 50, 50);
al_draw_filled_rectangle(x * GRID_
SIZE, y * GRID_SIZE,
(x + 1) *
GRID_SIZE, (y + 1) * GRID_SIZE, color);
}
}
al_draw_filled_circle((int)enemy.x * GRID_
SIZE + GRID_SIZE / 2,
(int)enemy.y * GRID_
SIZE + GRID_SIZE / 2,
GRID_SIZE / 2—
2, al_
map_rgb(255, 0, 0));
[Link]
Advanced Topics in Practical Game Programming 333
al_flip_display();
}
}
al_destroy_display(display);
al_destroy_event_queue(queue);
al_destroy_timer(timer);
return 0;
}
[Link]
334 Practical Game Programming
section of the larger image containing the item you want. So here, you load
a single resource and utilize parts of it rather than needing multiple loads.
EXAMPLE
ALLEGRO_BITMAP *atlas = al_load_bitmap("texture_atlas.
png");
Batch Drawing
Group similar draw calls to minimize state changes. Here, I’m talking about
drawing as many things as possible that share the same GPU state—same texture
(atlas), same shader, same blend mode, same transforms—in one go. Every time
we switch textures, bind a new shader, tweak blending, or swap target bitmaps,
the driver has to reconfigure the pipeline, and that costs time. So instead of
drawing one tiny piece at a time and constantly flipping states, we sort our work
(e.g., by texture/atlas) and feed the GPU batches of vertices at once.
In Allegro, that process could be, for example, building an array of ALLEGRO_
VERTEX that all reference the same atlas, then issuing a single al_draw_prim()
to cover the whole set.
Why do this? Because batching keeps the GPU busy doing the fast part
(rasterizing triangles) while avoiding the slow part (state churn on the CPU/
driver side).
EXAMPLE
ALLEGRO_VERTEX vertices[4]; // Define vertices for a quad
//...populate vertices...
[Link]
Advanced Topics in Practical Game Programming 335
[Link]
336 Practical Game Programming
about backend differences. In short, the flag isn’t needed for performance,
but it’s useful whenever you care about shader compatibility or you want your
fragment processing code to behave the same way across platforms.
Specify OpenGL with the flag:
Reuse Objects
Cache frequently used assets (e.g., fonts, particle effects):
#define CELL_SIZE 64
int grid[SCREEN_WIDTH / CELL_SIZE][SCREEN_HEIGHT /
CELL_SIZE];
[Link]
Advanced Topics in Practical Game Programming 337
[Link]
338 Practical Game Programming
7.5.5. Multithreading
Offload Nonrendering Tasks
Use Allegro’s threading API for tasks like pathfinding or AI:
// Start thread
ALLEGRO_THREAD *thread = al_create_thread(ai_thread,
NULL);
al_start_thread(thread);
[Link]
Advanced Topics in Practical Game Programming 339
In the area of debugging code, this can be as simple as printing out to the
console screen variable values, including lines to indicate “I’m in this part of
the program code,” “Now executing,” or other status-indicating displays.
Compress Textures
Use compressed formats like .png with al_load_bitmap_flags:
[Link]
340 Practical Game Programming
Downsample Audio
Convert audio to lower bitrates if high quality isn’t critical:
void adjust_graphics_quality() {
if (system_slow) {
al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR); //
Lower filtering quality
disable_shaders();
}
}
[Link]
Advanced Topics in Practical Game Programming 341
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_primitives.h>
int main() {
al_init();
al_init_font_addon();
al_init_ttf_addon();
al_init_primitives_addon();
al_install_keyboard();
al_install_mouse();
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_mouse_event_source());
al_destroy_display(display);
return 0;
}
[Link]
342 Practical Game Programming
typedef struct {
int x, y; // Position
int width, height; // Size
char label[50]; // Text label
bool hovered; // Hover state
bool clicked; // Click state
} Button;
al_draw_filled_rectangle(
button->x, button->y,
button->x + button->width,
button->y + button->height,
bg_color
);
al_draw_text(
font, al_map_rgb(255, 255, 255),
button->x + button->width/2,
button->y + button->height/2—12,
ALLEGRO_ALIGN_CENTRE,
button->label
);
}
[Link]
Advanced Topics in Practical Game Programming 343
button-
>hovered = (
mouse_x >= button-
>x &&
mouse_x <= button-
>x + button-
>width &&
mouse_y >= button-
>y &&
mouse_y <= button-
>y + button-
>height
);
button-
>clicked = button-
>hovered && mouse_clicked;
}
ALLEGRO_EVENT event;
bool mouse_clicked = false;
int mouse_x = 0, mouse_y = 0;
while (running) {
al_wait_for_event(event_queue, &event);
// Handle exit
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
running = false;
[Link]
344 Practical Game Programming
else
mouse_clicked = false;
// Update buttons
update_button(&start_button, mouse_x, mouse_y,
mouse_clicked);
update_button(&options_button, mouse_x, mouse_y,
mouse_clicked);
// Draw
al_clear_to_color(al_map_rgb(0, 0, 0));
draw_button(&start_button, font);
draw_button(&options_button, font);
al_flip_display();
}
typedef struct {
int x, y; // Position
int width; // Length
float value; // 0.0 to 1.0
bool dragging; // Interaction state
} Slider;
[Link]
Advanced Topics in Practical Game Programming 345
al_draw_filled_circle(thumb_x, slider-
>y, 10, al_map_
rgb(200, 200, 200));
}
if (slider-
>dragging) {
slider-
>value = (mouse_x—
slider-
>x) / (float)
slider->width;
slider-
>value = (slider-
>value < 0) ? 0 :
(slider-
>value > 1) ? 1 : slider-
>value;
}
}
[Link]
346 Practical Game Programming
The following example demonstrates these concepts but is missing one ele-
ment, the ability to go “back” to the main screen if you select options. That
we’ll leave for you, the student, to implement.
GUI EXAMPLE
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
typedef struct {
int x, y;
int width, height;
[Link]
Advanced Topics in Practical Game Programming 347
char label[50];
bool hovered;
bool clicked;
} Button;
typedef struct {
int x, y;
int width;
float value;
bool dragging;
} Slider;
al_draw_filled_rectangle(button->x, button->y,
button-
>x + button-
>width, button-
>y + button-
>height,
bg_color);
al_draw_text(font, al_map_rgb(255, 255, 255),
button-
>x + button-
>width/2, button-
>y + button-
>height/2—12, ALLEGRO_ALIGN_CENTRE, button->label);
}
[Link]
348 Practical Game Programming
al_draw_filled_circle(thumb_x, slider-
>y, 10, al_map_
rgb(200, 200, 200));
}
if (slider-
>dragging) {
slider-
>value = (mouse_x—
slider-
>x) / (float)
slider->width;
if (slider-
>value < 0) slider-
>value = 0;
if (slider-
>value > 1) slider-
>value = 1;
}
}
int main() {
al_init();
al_init_font_addon();
al_init_ttf_addon();
al_init_primitives_addon();
al_install_keyboard();
al_install_mouse();
[Link]
Advanced Topics in Practical Game Programming 349
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_mouse_event_source());
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
running = false;
if ([Link] == ALLEGRO_EVENT_MOUSE_AXES) {
mouse_x = [Link].x;
mouse_y = [Link].y;
}
if ([Link] == ALLEGRO_EVENT_MOUSE_BUTTON_
DOWN) {
mouse_clicked = true;
} else if ([Link] == ALLEGRO_EVENT_MOUSE_
BUTTON_UP) {
[Link]
350 Practical Game Programming
mouse_clicked = false;
}
if ([Link] == ALLEGRO_EVENT_TIMER) {
if (current_state == MENU) {
update_button(&start_button, mouse_x,
mouse_y, mouse_clicked);
update_button(&options_button, mouse_x,
mouse_y, mouse_clicked);
if (options_button.clicked)
current_state = OPTIONS;
} else if (current_state == OPTIONS) {
update_slider(&volume_slider, mouse_x,
mouse_y, mouse_clicked);
}
redraw = true;
}
if (current_state == MENU) {
draw_button(&start_button, font);
draw_button(&options_button, font);
} else if (current_state == OPTIONS) {
draw_slider(&volume_slider);
al_draw_textf(font, al_map_rgb(255, 255,
255), 300, 250, 0, "Volume: %.0f%%", volume_slider.value
* 100);
}
al_flip_display();
}
}
al_destroy_font(font);
al_destroy_display(display);
[Link]
Advanced Topics in Practical Game Programming 351
al_destroy_event_queue(event_queue);
al_destroy_timer(timer);
return 0;
}
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
int main() {
al_init();
al_install_audio(); // Initialize audio subsystem
al_init_acodec_addon(); // Enable codecs
al_reserve_samples(16); // Reserve 16 sample
instances for mixing
[Link]
352 Practical Game Programming
// Cleanup
al_destroy_sample(laser_sound);
al_uninstall_audio();
return 0;
}
ALLEGRO_SAMPLE_INSTANCE *music_instance =
al_create_sample_instance(music);
al_attach_sample_instance_to_mixer(music_instance,
al_get_default_mixer());
[Link]
Advanced Topics in Practical Game Programming 353
typedef struct {
float x, y; // Sound source position
} SoundSource;
al_set_sample_instance_pan(sound_instance, pan);
al_set_sample_instance_gain(sound_instance, volume);
}
EXAMPLE USAGE
SoundSource monster = {400, 300};
float player_x = 200, player_y = 200;
[Link]
354 Practical Game Programming
al_set_audio_stream_playmode(music_stream,
ALLEGRO_PLAYMODE_LOOP);
al_attach_audio_stream_to_mixer(music_stream,
al_get_default_mixer());
Fade-Out Music
Gradually reduce volume over time:
[Link]
Advanced Topics in Practical Game Programming 355
al_set_sample_instance_speed(sound_instance, 0.5); //
Play at half speed
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <math.h>
#include <stdio.h>
void update_spatial_audio(ALLEGRO_SAMPLE_INSTANCE
*instance,
SoundSource *source,
float listener_x, float
listener_y)
{
[Link]
356 Practical Game Programming
float dx = source-
>x—
listener_x;
float dy = source-
>y—
listener_y;
float distance = sqrtf(dx*dx + dy*dy);
int main()
{
al_init();
al_install_keyboard();
al_install_audio();
al_init_acodec_addon();
al_init_font_addon();
al_init_ttf_addon();
al_reserve_samples(16);
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));
[Link]
Advanced Topics in Practical Game Programming 357
ALLEGRO_SAMPLE_INSTANCE *music_instance =
al_create_sample_instance(music);
al_attach_sample_instance_to_mixer(music_instance,
al_get_default_mixer());
ALLEGRO_AUDIO_STREAM *music_stream =
al_load_audio_stream("[Link]", 4, 2048);
if (music_stream) {
al_set_audio_stream_playmode(music_stream,
ALLEGRO_PLAYMODE_LOOP);
al_attach_audio_stream_to_mixer(music_stream,
al_get_default_mixer());
}
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
running = false;
if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_0: // Stop/Reset everything started
by 1–9
fade_out = false; // cancel pending
fade
[Link]
358 Practical Game Programming
[Link]
Advanced Topics in Practical Game Programming 359
player_x,
player_y);
break;
case ALLEGRO_KEY_5:
if (music_instance) {
ALLEGRO_MIXER *lowpass =
al_create_mixer(44100,
ALLEGRO_
AUDIO_DEPTH_FLOAT32,
ALLEGRO_
CHANNEL_CONF_2);
al_attach_mixer_to_mixer(lowpass,
al_get_
default_mixer());
al_set_mixer_frequency(lowpass,
2000);
al_attach_sample_instance_to_
mixer(music_instance,
lowpass);
al_play_sample_instance(music_
instance);
}
break;
case ALLEGRO_KEY_6:
if (music_stream)
al_set_audio_stream_
playing(music_stream, true);
break;
case ALLEGRO_KEY_7:
fade_out = true;
break;
case ALLEGRO_KEY_8:
if (music_stream)
al_set_audio_stream_
playing(music_stream, false); // Pause
break;
case ALLEGRO_KEY_9:
al_set_sample_instance_speed(music_
instance, 0.5); // slow
break;
[Link]
360 Practical Game Programming
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
}
}
/*——Fade-out handler—————————————————————*/
if (fade_out && music_stream && volume >
0.0f) {
volume—= 0.01f;
al_set_audio_stream_gain(music_stream,
volume);
if (volume <= 0.0f) {
fade_out = false;
al_set_audio_stream_playing(music_stream,
false); // ← FIX
}
}
if ([Link] == ALLEGRO_EVENT_TIMER)
redraw = true;
[Link]
Advanced Topics in Practical Game Programming 361
"ESC to Quit"
};
al_clear_to_color(al_map_rgb(0,0,0));
al_flip_display();
}
}
/*——Cleanup————————————————————————————*/
al_destroy_sample(laser);
al_destroy_sample(explosion);
al_destroy_sample(music);
al_destroy_sample_instance(music_instance);
if (music_stream)
al_destroy_audio_stream(music_stream);
al_destroy_font(font);
al_destroy_timer(timer);
al_destroy_event_queue(queue);
al_destroy_display(display);
al_uninstall_audio();
return 0;
}
Multithreading
1. Implement a Dual-Threaded Game Loop: Create a program where
one thread handles rendering and input, while a second thread
updates game logic (e.g., moving a sprite). Use mutexes to synchronize
shared variables like sprite position.
[Link]
362 Practical Game Programming
Custom Shaders
5. Dynamic Colour Shader: Modify the fragment shader to pulse a
sprite’s colour based on a sine wave controlled by a time uniform.
6. Greyscale Transition: Implement a shader that gradually converts a
texture to greyscale when the player presses a key.
7. Screen Distortion Effect: Create a fragment shader that warps the
screen using a noise function or sine wave.
8. Health-Based Shader: Change a character’s appearance (e.g., adding a
red tint) based on health value passed from C++ to the shader.
Networking
9. Multiplayer Pong: Build a TCP-based Pong game where the server
manages ball physics and two clients control paddles.
10. Chat System with UDP: Create a UDP-based chat program where
messages are broadcasted to all clients. Compare reliability with TCP.
11. Lag Compensation: Simulate network latency in a multiplayer game
and implement interpolation to smooth player movement.
12. Lobby System: Extend the server to track player usernames and
readiness status before starting a game.
AI Programming
13. A Pathfinding Visualization: Render a grid-based map with obstacles.
Let the player click start/end points, and display the A path.
14. Finite State Machine for NPCs: Create an enemy that patrols, chases
the player if within range, and returns to patrol when out of range.
15. Flocking Behavior: Implement Boid algorithms (separation,
alignment, cohesion) for a group of creatures.
16. Behavior Tree for Boss AI: Design a boss that cycles through attack
patterns (e.g., charge, shoot, retreat) using a decision tree.
[Link]
Advanced Topics in Practical Game Programming 363
Optimization
17. Texture Atlas Generator: Write a tool to combine multiple sprites into
a single texture atlas and update rendering code to use it.
18. Collision Detection Grid: Implement spatial partitioning to limit
collision checks to nearby objects. Benchmark performance gains.
19. Memory Leak Detector: Use Valgrind or Allegro’s debug tools to
identify and fix leaks in a provided buggy code sample.
20. Dynamic Resolution Scaling: Adjust rendering resolution dynamically
based on frame time to maintain 60 fps.
GUI Systems
21. Pause Menu: Create a pause menu with buttons to resume, adjust
volume, or quit. Use custom button components.
22. Inventory System: Design a drag-and-drop inventory grid with item
tool tips.
23. Settings Screen: Add sliders for volume, resolution, and key bindings.
Save settings to a file.
24. Dialogue Boxes: Implement modal pop-ups for confirmation (e.g.,
“Are you sure you want to quit?”).
Advanced Audio
25. 3D Audio Demo: Simulate a sound source moving around the player.
Adjust pan and volume based on relative position.
26. Dynamic Music Mixer: Layer background music with intensity-based
tracks (e.g., calm vs. combat music).
27. Echo Effect: Apply a delay filter to audio samples using Allegro’s mixer
API.
28. Voice Chat System: Stream microphone input between clients over UDP.
Comprehensive Projects
29. Tower Defense Game
• Use multithreading for pathfinding and wave updates.
• Implement GUI for tower placement and upgrades.
• Add shaders for projectile effects.
• Include networked co-op mode.
30. RPG Engine
• Create a state machine for quest-driven NPCs.
• Optimize rendering with batch drawing.
• Add spatial audio for footsteps and ambient sounds.
• Design a skill tree using custom GUI components.
[Link]
This page intentionally left blank
8
Game Development
Projects
Core Components
Player Ship: The player-controlled spaceship
Attributes: Position (x, y), speed, sprite
Movement: Controlled via keyboard input (left, right, up, down)
Shooting: Fires bullets when the player presses a key
[Link]
366 Practical Game Programming
Enemies: Enemy ships that move toward the player and can be
destroyed
Attributes: Position (x, y), speed, sprite
Movement: Move downward toward the player
Spawning: New enemies spawn at the top of the screen at regular
intervals
Bullets: Projectiles fired by the player and enemies
Attributes: Position (x, y), speed, sprite
Movement: Move upward (player bullets) or downward (enemy
bullets)
Game State: Tracks the current state of the game (playing, game
over, etc.)
Attributes: Current state (playing, game over), score, lives
[Link]
Game Development Projects 367
8.1.2. Implementation
Set Up Your Development Environment
Before writing any code, it’s important to ensure that your tools and
libraries are properly installed and configured. The following checklist sum-
marizes the required software and setup steps needed to build and run the
project.
[Link]
368 Practical Game Programming
SpaceShooter/
[Link]
src/
[Link]
[Link]
spaceship.h
[Link]
enemy.h
[Link]
bullet.h
[Link]
game.h
assets/
[Link]
[Link]
[Link]
[Link]
cmake_minimum_required(VERSION 3.10)
project(SpaceShooter)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_executable(SpaceShooter src/[Link] src/spaceship.
cpp src/[Link] src/[Link] src/[Link])
find_package(PkgConfig REQUIRED)
pkg_check_modules(ALLEGRO5 REQUIRED allegro-
5
allegro_main-5 allegro_image-5 allegro_font-5
allegro_ttf-5 allegro_primitives-5)
include_directories(${ALLEGRO5_INCLUDE_DIRS})
target_link_libraries(SpaceShooter ${ALLEGRO5_LIBRARIES})
[Link]
Game Development Projects 369
system maintains real-time action. The following list summarizes the essential
operations executed during each frame of the game loop:
[Link]
370 Practical Game Programming
1. Initialization
• Initialize Allegro subsystems (display, keyboard input, image,
primitives, fonts).
• Create the main display window, event queue, and timer.
• Install input devices and prepare the game clock (e.g., 60 fps).
2. Resource Loading
• Load sprite assets for the player ship, enemies, bullets, and
background.
• Load fonts and any sound assets used for feedback.
• Validate that all required resources are available before entering
gameplay.
3. Entity Creation
• Create the player ship with its initial position and movement
parameters.
• Initialize enemy containers and enemy spawn timers.
• Prepare data structures for bullets and other dynamic entities.
4. Input Handling
• Capture keyboard events or keyboard state each frame.
• Map input to player movement and shooting actions.
• Handle global inputs such as pause, restart, or window-close
events.
5. Update Functions
• Update the player’s position based on input and apply screen
boundaries.
• Advance bullets and enemy positions each frame.
• Spawn new enemies according to timers or wave rules.
• Detect and resolve collisions between bullets, enemies, and the
player.
• Update score, lives, and game state as required.
6. Rendering Functions
• Clear the screen and draw the background.
• Render the player ship, enemies, and active bullets.
• Draw UI elements such as score, lives, or game-over messages.
• Flip the display to present the rendered frame.
7. Game Loop
• Wait for events from the event queue.
• On timer events, update the game state and render the scene.
• Maintain a consistent update rate to ensure smooth gameplay.
8. Shutdown
• Destroy timers, event queues, fonts, and the display.
[Link]
Game Development Projects 371
1. [Link]
#include "game.h"
#include <allegro5/allegro_native_dialog.h>
int main() {
Game game;
if (![Link]()) {
al_show_native_message_box(nullptr, "Init
failed", "Init returned false",
"Pausing 5 seconds so
you can read prior errors.", nullptr, 0);
al_rest(5.0);
return 1;
}
al_show_native_message_box(nullptr, "Info",
"Startup",
"Game initialized
successfully!", nullptr, 0);
[Link]();
al_show_native_message_box(nullptr, "Info",
"Shutdown",
"Game exiting. Pausing
2 seconds.", nullptr, 0);
al_rest(2.0);
[Link]();
return 0;
}
[Link]
372 Practical Game Programming
2. GAME.H
#ifndef GAME_H
#define GAME_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <vector>
#include <string>
#include "spaceship.h"
#include "enemy.h"
#include "bullet.h"
class Game {
public:
Game();
bool init();
void run();
void shutdown();
private:
void update();
void render();
bool check_collision(ALLEGRO_BITMAP* bmp1, float ax,
float ay,
ALLEGRO_BITMAP* bmp2, float bx,
float by)
// core Allegro
ALLEGRO_DISPLAY* display;
ALLEGRO_EVENT_QUEUE* event_queue;
ALLEGRO_TIMER* timer;
bool running;
ALLEGRO_BITMAP* background;
// gameplay
Spaceship player;
std::vector<Enemy> enemies;
std::vector<Bullet> bullets;
[Link]
Game Development Projects 373
// input/game timing
double last_shot_time;
const double SHOT_COOLDOWN = 0.20; // seconds
// asset paths
std::string bullet_path;
};
#endif
3. [Link]
#include "game.h"
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_native_dialog.h>
#include <algorithm>
#include <string>
#include <cstdio>
Game::Game()
: display(nullptr),
[Link]
374 Practical Game Programming
event_queue(nullptr),
timer(nullptr),
running(false),
background(nullptr),
player(400.0f, 520.0f),
last_shot_time(0.0) {}
bool Game::init() {
std::fprintf(stderr, "[INIT] Allegro starting\n");
if (!al_init()) {
al_show_native_message_box(nullptr, "Error",
"Allegro Init", "Failed to initialize Allegro!", nullptr,
0);
return false;
}
if (!al_install_keyboard()) {
al_show_native_message_box(nullptr, "Error",
"Keyboard", "Failed to install keyboard!", nullptr, 0);
return false;
}
if (!al_init_image_addon()) {
al_show_native_message_box(nullptr, "Error",
"Image Addon", "Failed to initialize image addon!",
nullptr, 0);
return false;
}
if (!al_init_primitives_addon()) {
al_show_native_message_box(nullptr, "Error",
"Primitives Addon", "Failed to initialize primitives
addon!", nullptr, 0);
return false;
}
[Link]
Game Development Projects 375
//—
—Asset loads relative to the EXE directory—
—
const std::string bgPath = exe_path_join("assets",
"[Link]");
const std::string shipPath = exe_path_join("assets",
"[Link]");
const std::string enemyPath = exe_path_join("assets",
"[Link]");
bullet_path = exe_path_join("assets",
"[Link]");
background = al_load_bitmap(bgPath.c_str());
if (!background) {
al_show_native_message_box(display, "Asset
error", "Load failed",
("Failed to load " +
bgPath).c_str(), nullptr, 0);
return false;
}
if ()) {
al_show_native_message_box(display, "Asset
error", "Load failed",
("Failed to load " +
shipPath).c_str(), nullptr, 0);
return false;
}
[Link]
376 Practical Game Programming
return false;
}
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
return true;
}
void Game::run() {
std::fprintf(stderr, "[RUN] start\n");
running = true;
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT ev;
if (al_wait_for_event_timed(event_queue, &ev,
0.25)) {
switch ([Link]) {
case ALLEGRO_EVENT_TIMER:
std::fprintf(stderr, "[EV] TIMER\n");
update();
render();
break;
[Link]
Game Development Projects 377
case ALLEGRO_EVENT_DISPLAY_CLOSE:
std::fprintf(stderr, "[EV]
DISPLAY_CLOSE\n");
running = false;
break;
case ALLEGRO_EVENT_KEY_DOWN:
std::fprintf(stderr, "[EV] KEY_DOWN:
%d\n", [Link]);
if ([Link] == ALLEGRO_
KEY_ESCAPE) {
running = false;
}
break;
default:
std::fprintf(stderr, "[EV] other:
%u\n", [Link]);
break;
}
} else {
// No event within timeout—
render anyway to
keep window responsive
render();
}
}
std::fprintf(stderr, "[RUN] end\n");
}
//————methods————
void Game::update() {
[Link]();
ALLEGRO_KEYBOARD_STATE ks;
al_get_keyboard_state(&ks);
double now = al_get_time();
if (al_key_down(&ks, ALLEGRO_KEY_SPACE) &&
(now—
last_shot_time) >= SHOT_COOLDOWN) {
[Link](bullets, bullet_path.c_str());
[Link]
378 Practical Game Programming
last_shot_time = now;
}
if (player.get_bitmap()) {
for (auto& e : enemies) {
if (!e.is_active() || !e.get_bitmap())
continue;
if (check_collision(player.get_bitmap(),
player.get_x(), player.get_y(),
e.get_bitmap(),
e.get_x(), e.get_y())) {
std::fprintf(stderr, "[GAME] Player
collided with enemy—
ending\n");
running = false;
break;
[Link]
Game Development Projects 379
}
}
}
}
void Game::render() {
if (background) al_draw_bitmap(background, 0, 0, 0);
[Link]();
for (auto& e : enemies) [Link]();
for (auto& b : bullets) [Link]();
al_flip_display();
}
const float aw =
static_cast<float>(al_get_bitmap_width(a));
const float ah =
static_cast<float>(al_get_bitmap_height(a));
const float bw =
static_cast<float>(al_get_bitmap_width(b));
const float bh =
static_cast<float>(al_get_bitmap_height(b));
return (ax < b_right) && (a_right > bx) && (ay < b_
bottom) && (a_bottom > by);
}
void Game::shutdown() {
if (timer) { al_stop_timer(timer); al_destroy_
timer(timer); timer = nullptr; }
[Link]
380 Practical Game Programming
if (event_queue) { al_destroy_event_queue(event_
queue); event_queue = nullptr; }
if (background) { al_destroy_bitmap(background);
background = nullptr; }
[Link]();
[Link]();
4. SPACESHIP.H
#ifndef SPACESHIP_H
#define SPACESHIP_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include "bullet.h"
#include <vector>
class Spaceship {
public:
Spaceship(float x, float y);
~Spaceship();
// Non-
copyable (owning raw pointer)
Spaceship(const Spaceship&) = delete;
Spaceship& operator=(const Spaceship&) = delete;
// Movable
Spaceship(Spaceship&& other) noexcept;
Spaceship& operator=(Spaceship&& other) noexcept;
[Link]
Game Development Projects 381
void update();
void render();
void shoot(std::vector<Bullet>& bullets, const char*
bullet_image_path);
private:
float x, y;
ALLEGRO_BITMAP* image;
};
#endif
5. [Link]
#include "spaceship.h"
#include <allegro5/allegro.h>
// Move ctor
Spaceship::Spaceship(Spaceship&& other) noexcept
: x(other.x), y(other.y), image([Link])
{
[Link] = nullptr;
}
// Move assign
Spaceship& Spaceship::operator=(Spaceship&& other)
noexcept {
if (this != &other) {
if (image) al_destroy_bitmap(image);
x = other.x; y = other.y; image = [Link];
[Link] = nullptr;
}
[Link]
382 Practical Game Programming
return *this;
}
void Spaceship::update() {
ALLEGRO_KEYBOARD_STATE state;
al_get_keyboard_state(&state);
if (al_key_down(&state, ALLEGRO_KEY_LEFT)) x—
= 5;
if (al_key_down(&state, ALLEGRO_KEY_RIGHT)) x += 5;
if (al_key_down(&state, ALLEGRO_KEY_UP)) y—
= 5;
if (al_key_down(&state, ALLEGRO_KEY_DOWN)) y += 5;
if (x < 0) x = 0;
if (y < 0) y = 0;
if (image) {
int w = al_get_bitmap_width(image);
int h = al_get_bitmap_height(image);
if (x > 800—
w) x = 800—
w;
if (y > 600—
h) y = 600—
h;
}
}
void Spaceship::render() {
if (image) al_draw_bitmap(image, x, y, 0);
}
[Link]
Game Development Projects 383
6. ENEMY.H
#ifndef ENEMY_H
#define ENEMY_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
class Enemy {
public:
Enemy(float x, float y, const char* image_path);
void update();
void render();
void set_active(bool active) { this-
>active =
active; }
bool is_active() const { return active; }
float get_x() const { return x; }
float get_y() const { return y; }
ALLEGRO_BITMAP* get_bitmap() const { return
image; }
private:
float x, y;
bool active;
ALLEGRO_BITMAP* image;
};
#endif
7. [Link]
#include "enemy.h"
#include <cstdlib>
[Link]
384 Practical Game Programming
active = false;
}
}
Enemy::~Enemy() {
if (image) { al_destroy_bitmap(image); image =
nullptr; }
}
// Move ctor
Enemy::Enemy(Enemy&& other) noexcept
: x(other.x), y(other.y), active([Link]),
image([Link])
{
[Link] = nullptr;
[Link] = false;
}
// Move assign
Enemy& Enemy::operator=(Enemy&& other) noexcept {
if (this != &other) {
if (image) al_destroy_bitmap(image);
x = other.x; y = other.y; active = [Link];
image = [Link];
[Link] = nullptr;
[Link] = false;
}
return *this;
}
void Enemy::update() {
if (!active || !image) return;
// simple downward drift
y += 2.0f;
if (y > 600) {
int h = al_get_bitmap_height(image);
y =—h;
int w = al_get_bitmap_width(image);
x = static_cast<float>(rand() % (800—
w));
}
}
[Link]
Game Development Projects 385
void Enemy::render() {
if (active && image) {
al_draw_bitmap(image, x, y, 0);
}
}
8. BULLET.H
#ifndef BULLET_H
#define BULLET_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
class Bullet {
public:
Bullet(float x, float y, float dx, float dy);
Bullet(float x, float y, float dx, float dy, const
char* ima
~Bullet();
// Non-
copyable (owning raw pointer)
Bullet(const Bullet&) = delete;
Bullet& operator=(const Bullet&) = delete;
// Movable
Bullet(Bullet&& other) noexcept;
Bullet& operator=(Bullet&& other) noexcept;
void update();
void render();
bool is_active() const { return active; }
void set_active(bool a) { active = a; }
float get_x() const { return x; }
float get_y() const { return y; }
ALLEGRO_BITMAP* get_bitmap() const { return image; }
private:
void load_image_from(const char* image_path);
float x, y;
float dx, dy;
[Link]
386 Practical Game Programming
bool active;
ALLEGRO_BITMAP* image;
};
#endif
9. [Link]
#include "bullet.h"
Bullet::~Bullet() {
if (image) { al_destroy_bitmap(image); image =
nullptr; }
}
// Move ctor
Bullet::Bullet(Bullet&& other) noexcept
: x(other.x), y(other.y), dx([Link]), dy([Link]),
active([Link]), image([Link])
{
[Link] = nullptr;
[Link]
Game Development Projects 387
[Link] = false;
}
// Move assign
Bullet& Bullet::operator=(Bullet&& other) noexcept {
if (this != &other) {
if (image) al_destroy_bitmap(image);
x = other.x; y = other.y; dx = [Link]; dy =
[Link];
active = [Link]; image = [Link];
[Link] = nullptr; [Link] = false;
}
return *this;
}
void Bullet::update() {
if (!active || !image) { active = false; return; }
y += dy; x += dx;
int w = al_get_bitmap_width(image);
int h = al_get_bitmap_height(image);
if (y <—
h || y > 600 || x <—
w || x > 800) active =
false;
}
void Bullet::render() {
if (active && image) al_draw_bitmap(image, x, y, 0);
}
• Open the Command Palette (⇧⌘P or Ctrl + Shift + P) and run CMake:
Configure.
• Select the appropriate kit (e.g., GCC) and specify the generator as Ninja.
• Run CMake to build.
[Link]
388 Practical Game Programming
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
With the core game implemented, there are many opportunities to expand
its features and polish the experience. The following tasks suggest mean-
ingful enhancements you can add to deepen gameplay and improve
presentation.
1. Scoring and Health System: Display hit counts or health bars clearly
on the screen and refine how damage is calculated.
2. Improved AI: Enhance enemy tank behavior with smarter movement,
targeting, and obstacle avoidance.
3. Weapons and Shooting Mechanics: Introduce multiple weapon types
or adjust firing rate, speed, and damage for variety.
4. Battlefield and Levels: Create different maps or obstacle layouts and
increase difficulty through more complex environments.
5. Sound Effects: Add sound effects for firing, impacts, and victory
events to improve immersion.
Exercises
1. Action Game Characteristics
Identify the defining characteristics of action games demonstrated
by the space shooter. Why are responsiveness and fast feedback more
critical here than narrative depth or complex AI?
2. Entity Lifecycle Management
Explain the lifecycle of a bullet from creation to removal. Why is it
important to deactivate or erase bullets once they leave the screen or
collide with an enemy?
3. Collision Detection Basics
Describe how axis-aligned bounding box (AABB) collision checks work.
Why are they well suited to a 2D space shooter with rectangular sprites?
[Link]
Game Development Projects 389
Homework Questions
1. Input Handling Strategies
Compare polling the keyboard state with event-based input handling.
What are the advantages of using continuous keyboard state checks
for movement in an action game?
2. Difficulty and Player Skill
Discuss how enemy spawn frequency, speed, and quantity influence
difficulty. How can designers balance challenge so the game remains
fun rather than frustrating?
3. Performance Considerations
Why should inactive enemies and bullets be removed from their
containers regularly? What problems might arise if these objects
accumulate over time?
4. Visual Feedback and Player Perception
Explain how immediate visual feedback (such as explosions or
flashing sprites) improves player perception of responsiveness and
fairness during gameplay.
Programming Projects
1. Scoring System (Core Project)
Add a visible scoring system that increases the player’s score
whenever an enemy is destroyed. Display the score prominently on
the screen during gameplay.
2. Lives and Health System
Extend the game so the player has multiple lives or a health bar.
Deduct lives or health when colliding with enemies and trigger a
game-over condition when it reaches zero.
3. Power-Ups and Collectibles
Implement power-ups that occasionally drop from destroyed enemies,
such as rapid fire, temporary shields, or spread shots. Ensure power-
ups activate and expire correctly.
4. Multiple Enemy Types
Introduce additional enemy types with different movement speeds,
behaviors, or hit points. Use simple variation rules to increase
gameplay depth.
[Link]
390 Practical Game Programming
Capstone Project
Design and implement a fully polished space shooter game featuring multiple
levels, score tracking, lives, power-ups, sound effects, and clear game-over
and restart mechanics. Include a short design reflection describing how input
handling, collision logic, and difficulty progression work together to create a
compelling action-game experience.
Core Components
Player Tank: The primary tank controlled by the player
Attributes: Position (x, y), rotation angle, movement speed, sprite,
health (or hit count)
[Link]
Game Development Projects 391
Movement: Controlled via keyboard input; the tank rotates left or right
and moves forward or backward based on its facing direction
Shooting: Fires projectiles in the direction the tank is facing, subject to
firing rate and projectile speed limits
Enemy Tank: An opposing tank controlled by scripted logic or simple
AI behavior
Attributes: Position (x, y), rotation angle, movement speed, sprite,
health (or hit count)
Movement: Moves based on AI rules such as patrolling, chasing the
player, or repositioning for tactical advantage
Shooting: Fires projectiles toward the player or along its facing
direction, using the same projectile system as the player tank
Bullets (Projectiles): Projectiles fired by both player and enemy
tanks
Attributes: Position (x, y), velocity (dx, dy), speed
Movement: Travel in straight lines in the firing direction until they hit a
tank, collide with an obstacle, or leave the playfield
Collision: Deactivate on impact with tanks or environmental
obstacles
Battlefield and Obstacles: The bounded play area where tank combat
takes place
Attributes: Screen bounds, obstacle positions, obstacle sizes
Function: Restricts tank movement to the playfield and provides cover
or tactical chokepoints that influence combat and positioning
Collision System: Manages interactions between tanks, bullets, and
obstacles
Detection Method: Axis-aligned bounding box (AABB) collision checks
Function: Prevents tanks from passing through obstacles, registers
bullet hits, and enforces valid movement
Game State and Scoring: Tracks the overall progress and outcome of
a round
Attributes: Current round state (playing, round won, paused), hit
counters or health values, win condition thresholds
Win Condition: A round ends when a tank reaches a defined hit limit or
health depletion, triggering a win or loss state
[Link]
392 Practical Game Programming
[Link]
Game Development Projects 393
8.2.2. Implementation
Before implementing this and other games in this chapter, ensure you have
Visual Studio Code, GCC, CMake, Ninja, and Allegro 5 installed and configured
as described in chapter 1 and mentioned in 8.1.2.
[Link]
394 Practical Game Programming
1. Initialization
• Initialize Allegro subsystems (keyboard, image, primitives, font,
TTF).
• Create the display, event queue, and timer.
• Load tank sprites and prepare the battlefield.
2. Resource Loading
• Load tank textures, bullet graphics (if any), and fonts.
• Set bitmap flags for smooth scaling and rendering.
• Generate obstacles procedurally to populate the map.
3. Entity Creation
• Instantiate Tank objects for both players.
[Link]
Game Development Projects 395
[Link]
#include "game.h"
int main() {
Game game;
if ([Link]()
[Link]();
}
[Link]()
return 0;
}
[Link]
396 Practical Game Programming
GAME.H
#ifndef GAME_H
#define GAME_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <vector>
#include <string>
#include <allegro5/allegro_font.h>
#include "tank.h"
#include "bullet.h"
#include "common.h"
class Game {
public:
Game();
bool init();
void run();
void shutdown();
private:
void generateObstacles();
bool tankOverlapsAnyObstacle(const Tank& t) const;
bool bulletHitsAnyObstacle(float bx, float by)
const;
void renderObstacles() const;
bool rectsOverlap(float ax,float ay,float aw,float
ah, float bx,float by,float bw,flo
private:
void update();
void render();
void fireBulletFrom(const Tank& t);
bool check_collision(const Tank& tank, const Bullet&
bullet);
[Link]
Game Development Projects 397
ALLEGRO_DISPLAY* display;
ALLEGRO_EVENT_QUEUE* event_queue;
ALLEGRO_TIMER* timer;
bool running;
std::vector<Bullet> bullets;
std::vector<Obstacle> obstacles;
//—
—Round win & pause UI—
—
static constexpr int WIN_SCORE = 5; // first tank to
TAKE 5 hits loses
ALLEGRO_FONT* uiFont = nullptr;
std::string winText;
int winTextFrames = 0; // frames to show
banner (300 == 5s @60FPS)
bool roundPaused = false;
};
#endif // GAME_H
[Link]
398 Practical Game Programming
[Link]
#include<cmath>
#include "game.h"
#include "tank.h"
#include "bullet.h"
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
Game::Game()
: display(nullptr), event_queue(nullptr),
timer(nullptr), running(true) {}
bool Game::init() {
if (!al_init()) return false;
if (!al_install_keyboard()) return false;
if (!al_init_image_addon()) return false;
if (!al_init_primitives_addon()) return false;
if (!al_init_font_addon()) return false;
al_init_ttf_addon();
al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP |
ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR);
display = al_create_display(SCREEN_W, SCREEN_H);
if (!display) return false;
al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA,
ALLEGRO_INVERSE_ALPHA);
event_queue = al_create_event_queue();
timer = al_create_timer(1.0 / 60.0);
if (!event_queue || !timer) return false;
[Link]
Game Development Projects 399
al_register_event_source(event_queue,
al_get_keyboard_event_source());
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
void Game::shutdown() {
if (uiFont) { al_destroy_font(uiFont); uiFont =
nullptr; }
if (timer) { al_destroy_timer(timer); timer =
nullptr; }
if (event_queue) { al_destroy_event_queue(event_
queue); event_queue = nullptr; }
if (display) { al_destroy_display(display); display =
nullptr; }
}
void Game::resetRound() {
[Link]();
[Link](100, 100);
[Link](0);
[Link](600, 400);
[Link](0);
generateObstacles();
}
void Game::run() {
while (running) {
[Link]
400 Practical Game Programming
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
update();
render();
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
// Tank1 (right side)—
WASD + Right Ctrl
case ALLEGRO_KEY_W: t1_forward =
true; break;
case ALLEGRO_KEY_S: t1_backward =
true; break;
case ALLEGRO_KEY_A: [Link](-
rotStep); break;
case ALLEGRO_KEY_D: tank1.
rotateBy(+rotStep); break;
case ALLEGRO_KEY_LCTRL:
fireBulletFrom(tank1); break;
[Link]
Game Development Projects 401
// Tank1
case ALLEGRO_KEY_W: t1_forward = false;
break;
case ALLEGRO_KEY_S: t1_backward = false;
break;
// Tank2
case ALLEGRO_KEY_UP: t2_forward =
false; break;
case ALLEGRO_KEY_DOWN: t2_backward =
false; break;
}
}
}
}
void Game::update() {
if (!roundPaused) {
// Movement (blocked by obstacles)
if (t1_forward) { tank1.
moveForward(+moveSpeed); if
(tankOverlapsAnyObstacle(tank1)) { tank1.
if (t1_backward) { [Link](-
moveSpeed); if (tankOverlapsAnyObstacle(tank1)) { tank1.
if (t2_forward) { tank2.
moveForward(+moveSpeed); if
(tankOverlapsAnyObstacle(tank2)) { tank2.
if (t2_backward) { [Link](-
moveSpeed); if (tankOverlapsAnyObstacle(tank2)) { tank2.
clampTankToScreen(tank1);
clampTankToScreen(tank2);
// Bullets
for (auto& b : bullets) [Link]();
[Link](std::remove_if([Link](),
[Link](),
[this](const Bullet& b){
if ([Link]() <—
10 || [Link]() > SCREEN_W
+ 10 || [Link]() <—
10 || [Link]() > SCREEN_H
[Link]
402 Practical Game Programming
return true;
if (bulletHitsAnyObstacle([Link](),
[Link]()))
return true;
return false;
}),
[Link]());
[Link]
Game Development Projects 403
void Game::render() {
al_clear_to_color(al_map_rgb(20, 30, 40));
renderObstacles();
[Link]();
[Link]
404 Practical Game Programming
[Link]();
for (auto& b : bullets) [Link]();
ALLEGRO_FONT* f = al_create_builtin_font();
if (f) {
al_draw_textf(f, al_map_rgb(255,255,255), 10, 10,
0, "Hits on Tank1: %d", score1);
al_draw_textf(f, al_map_rgb(255,255,255), 10, 30,
0, "Hits on Tank2: %d", score2);
al_destroy_font(f);
}
al_flip_display();
}
[Link]
Game Development Projects 405
static_cast<float>([Link]()) * [Link]() +
static_cast<float>([Link]()) * [Link]()
);
const float muzzleOffset = halfDiag + 6.0f; // small
safety margin
void Game::clampTankToScreen(Tank& t) {
float nx = [Link]();
float ny = [Link]();
if (nx < 0) nx = 0;
if (ny < 0) ny = 0;
if (nx > SCREEN_W—
[Link]()) nx = SCREEN_W—
t.
getWidth();
if (ny > SCREEN_H—
[Link]()) ny = SCREEN_H—
t.
getHeight();
[Link](nx, ny);
}
bool Game::rectsOverlap(float ax,float ay,float aw,float
ah, float bx,float by,float bw,float bh) const
[Link]
406 Practical Game Programming
return (ax < bx + bw) && (ax + aw > bx) && (ay < by +
bh) && (ay + ah > by);
}
void Game::generateObstacles() {
[Link]();
std::srand((unsigned)std::time(nullptr));
const int count = 10;
const float w = [Link]() > 0 ? tank1.
getWidth() : 64.0f;
[Link]
Game Development Projects 407
TANK.H
#ifndef TANK_H
#define TANK_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
[Link]
408 Practical Game Programming
class Tank {
public:
Tank();
Tank(float x, float y, const char* image_path, float
forwardOffsetRadians = 0.0f);
~Tank();
private:
void normalizeAngle();
#endif // TANK_H
[Link]
Game Development Projects 409
[Link]
#include "tank.h"
#include "common.h"
#include <cstdio>
#include <allegro5/allegro_primitives.h>
Tank::~Tank() {
if (image) {
al_destroy_bitmap(image);
image = nullptr;
}
}
[Link]
410 Practical Game Programming
if (this != &other) {
if (image) al_destroy_bitmap(image);
x = other.x; y = other.y;
image = [Link]; [Link] = nullptr;
width = [Link]; height = [Link];
angle = [Link];
forwardOffset = [Link];
}
return *this;
}
void Tank::normalizeAngle() {
if (angle >= PI) {
angle = std::fmod(angle + PI, 2.0f * PI)—
PI;
} else if (angle <—
PI) {
angle = std::fmod(angle—
PI, 2.0f * PI) + PI;
}
}
void Tank::render() {
if (!image) {
al_draw_filled_rectangle(x, y, x + 40, y + 40,
al_map_rgb(255, 0, 0));
return;
}
const float cx = width * 0.5f;
const float cy = height * 0.5f;
al_draw_rotated_bitmap(image, cx, cy, x + cx, y + cy,
angle, 0);
}
[Link]
Game Development Projects 411
BULLET.H
#ifndef BULLET_H
#define BULLET_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
class Bullet {
public:
Bullet(float x, float y, float dx, float dy);
void update();
void render();
float getX() const { return x; }
float getY() const { return y; }
private:
float x, y, dx, dy;
};
#endif // BULLET_H
[Link]
#include "bullet.h"
void Bullet::update() {
x += dx;
y += dy;
}
void Bullet::render() {
[Link]
412 Practical Game Programming
COMMON.H
#ifndef COMMON_H
#define COMMON_H
#include <cmath>
#endif // COMMON_H
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
Exercises
1. Tank Movement and Orientation
Explain how representing tank orientation using radians enables
smooth rotation and directional movement. Why is it important to
normalize angles over time?
2. Event-Driven Game Loop
Describe how the Allegro timer drives the game loop at 60 frames per
second. Why should game logic updates be executed only in response
to timer events?
3. Collision Handling Strategy
Review the collision logic used for tanks, bullets, and obstacles. Why are
axis-aligned bounding boxes (AABB) an appropriate choice for this game?
[Link]
Game Development Projects 413
Homework Questions
1. Control Scheme Design
Discuss the advantages and challenges of having two players share
a keyboard. How do control mappings affect player comfort and
fairness?
2. Projectile Management
Why is it important to spawn bullets outside the tank’s bounding box?
Describe what could go wrong if bullets begin inside their source tank.
3. Obstacle-Driven Gameplay
Explain how obstacles influence player tactics and movement
decisions. How do obstacles increase strategic depth in an otherwise
simple combat arena?
4. Simple AI Limitations
The enemy tank AI uses basic decision logic. What types of player
behaviors might this AI fail to respond to effectively, and why?
Programming Projects
1. Enhanced Enemy AI (Core Project)
Improve enemy tank behavior by implementing simple steering logic,
such as seeking the player, maintaining distance, or avoiding obstacles
dynamically.
2. Multiple Weapon Types
Extend the shooting system to support additional weapon types,
such as spread shots, slow but powerful shells, or ricocheting bullets.
Balance their speed and damage appropriately.
3. Map Loading from Data Files
Replace procedural obstacle generation with map loading from an
external file format (e.g., JSON or CSV). Allow different battlefield
layouts for variety.
4. Health Bars and Visual Feedback
Add visible health bars or damage indicators to tanks. Provide visual
feedback when a tank is hit, such as a brief flash or screen shake.
5. Sound Effects Integration (Advanced)
Add sound effects for firing bullets, tank explosions, and round
victories using Allegro’s audio subsystem.
[Link]
414 Practical Game Programming
Capstone Project
Design and implement a polished game featuring multiple maps, improved
AI behaviors, enhanced graphics, and audio feedback. Include configurable
match rules (such as time-based rounds or score limits) and provide a menu
system for mode selection. Submit a short design overview explaining how
movement, combat, AI, and level design combine to create engaging gameplay.
Core Components
Player Character: The primary character controlled by the player
Attributes: Position (x, y), movement rules, inventory capacity,
interaction range
Movement: Controlled via keyboard or mouse input; movement may
be grid-based, screen-to-screen, or free-form depending on world
design
Interaction: Can examine objects, collect and use items, talk to NPCs,
activate environmental triggers, and initiate puzzle actions
Nonplayer Characters (NPCs): Characters inhabiting the world that
the player can interact with
Attributes: Position (x, y), dialogue state, quest flags, interaction
availability
Function: Provide information, story context, quests, items, or puzzle
hints
[Link]
Game Development Projects 415
[Link]
416 Practical Game Programming
[Link]
Game Development Projects 417
8.3.2. Implementation
Key Operations in the Game
Adventure games rely on a continuous cycle of narrative progression, player inter-
action, and world updates. Unlike action-oriented genres, the core operations
here focus on exploration, dialogue, puzzle resolution, and state management.
These operations run throughout the game loop and ensure that the world
responds consistently to player actions. The following list summarizes the essen-
tial operations that drive an adventure game during runtime.
[Link]
418 Practical Game Programming
1. Initialization
• Initialize Allegro subsystems (display, keyboard, image, font,
primitives).
• Create the main window, event queue, and timer.
• Load fonts, images, and any initial room or character data.
2. Resource Loading
• Load room definitions, object data, and NPC information.
• Prepare textures, background art, and UI assets.
• Optional: Load dialogue scripts or quest data from external files.
3. Entity Creation
• Create the player character with initial position and attributes.
• Generate rooms, walls, doors, and interactive objects.
• Instantiate NPCs and assign their dialogue or behaviors.
[Link]
Game Development Projects 419
4. Input Handling
• Capture keyboard or mouse events.
• Map input to movement, interaction, menu navigation, or dialogue
choices.
• Handle Esc key press or window-close events to exit the game.
5. Update Functions
• Move the player character and apply room boundaries.
• Check for interactions with objects, NPCs, or puzzle elements.
• Update quest states, puzzle progress, and triggered events.
• Manage transitions between rooms or scenes.
6. Rendering Functions
• Draw rooms, walls, doors, and environmental details.
• Render the player character and NPCs.
• Display UI elements such as dialogue boxes, inventory menus, and
prompts.
• Flip the display to present the updated frame.
7. Game Loop
• Wait for events from the event queue.
• On timer events, update the game state and render the scene.
• Maintain a consistent frame rate (e.g., 60 fps).
8. Shutdown
• Destroy fonts, timers, event queues, and the display.
• Release any loaded textures or resources.
• Cleanly exit the program.
[Link]
420 Practical Game Programming
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
redraw = true;
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
[Link]
Game Development Projects 421
running = false;
} else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
switch ([Link]) {
case ALLEGRO_KEY_ESCAPE:
running = false;
break;
case ALLEGRO_KEY_UP:
player_y—= player_speed;
break;
case ALLEGRO_KEY_DOWN:
player_y += player_speed;
break;
case ALLEGRO_KEY_LEFT:
player_x—= player_speed;
break;
case ALLEGRO_KEY_RIGHT:
player_x += player_speed;
break;
}
}
al_clear_to_color(al_map_rgb(0, 0, 0));
renderRooms(rooms, al_map_rgb(120,120,120),
al_map_rgb(30,30,30));
al_draw_filled_rectangle(player_x, player_y,
player_x + PLAYER_SIZE, playe
// Center and draw a white ☺ inside the
square using the existing font
const char* smile = u8"\u263A"; // ☺
(Unicode U+263A)
int tw = al_get_text_width(font, smile);
int th = al_get_font_line_height(font);
float tx = player_x + (PLAYER_SIZE—
tw) * 0.5f;
float ty = player_y + (PLAYER_SIZE—
th) * 0.5f;
al_draw_text(font, al_map_rgb(255,255,255),
tx, ty, 0, smile);
[Link]
422 Practical Game Programming
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
}
PLAYER_MOVEMENT.H
#ifndef PLAYER_MOVEMENT_H
#define PLAYER_MOVEMENT_H
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#endif
[Link]
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include "room.h"
#include <cstdlib>
#include <ctime>
#include <algorithm>
// Axis-
aligned rectangle overlap check
static bool rectsOverlap(float ax,float ay,float aw,float
ah,
float bx,float by,float bw,float
bh) {
return (ax < bx + bw) && (ax + aw > bx) && (ay < by +
bh) && (ay + ah > by);
}
std::vector<Room> generateRooms(
[Link]
Game Development Projects 423
int count,
int screenW, int screenH,
int minSize, int maxSize,
int wallThickness, int doorWidth
) {
std::srand((unsigned)std::time(nullptr));
std::vector<Room> rooms;
[Link](count);
[Link]
424 Practical Game Programming
[Link]
Game Development Projects 425
[Link]
426 Practical Game Programming
drawWallWithGap(x, y + s—
th, x + s, y +
s—
th, gx1, gy1, gx2, gy2, wallColor, edgeColor);
} break;
case 3: { // left (vertical)
float gy1 = y + [Link]—
[Link] *
0.5f;
float gy2 = y + [Link] + [Link] *
0.5f;
float gx1 = x, gx2 = x + th;
drawWallWithGap(x, y, x, y + s, gx1, gy1,
gx2, gy2, wallColor, edgeColor);
} break;
}
[Link]
Game Development Projects 427
ROOM.H
#ifndef ROOM_H
#define ROOM_H
#include <vector>
#include <allegro5/allegro.h>
// Generate non-
overlapping rooms (best effort) inside
screen bounds
std::vector<Room> generateRooms(
int count,
int screenW, int screenH,
int minSize = 100, int maxSize = 180,
int wallThickness = 6,
int doorWidth = 40
);
#endif // ROOM_H
[Link]
428 Practical Game Programming
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
1. Level Design: Design the layout of each level, including key locations,
obstacles, and objectives. Add collisions to make room walls solid
2. Puzzles: Create puzzles that challenge the player and fit seamlessly
into the game’s world.
3. UI Elements: Add code for holding the Shift key down as a function to
make the player move continuously (fast movement).
4. Menus and HUD: Create intuitive menus and HUD elements that
provide necessary information to the player.
5. Usability Testing: Test the UI to ensure it is user-friendly and accessible.
6. Bug Fixing: Identify and fix bugs through rigorous testing.
7. Balancing: Ensure the game is balanced and provides a fair challenge.
8. Polish: Add final touches to the game, such as visual effects, sound
enhancements, and minor gameplay tweaks.
9. Final Testing: Perform a final round of testing to ensure the game is
polished and free of major issues.
Exercises
1. Adventure Game System Mapping
Identify the major systems described in this chapter (player
movement, rooms, NPCs, interaction, puzzles, inventory, quests, UI).
Briefly explain how at least three of these systems interact to support
exploration-driven gameplay.
2. Exploration vs. Action
Compare adventure games with action-oriented games. Why do
adventure games emphasize slower pacing, environmental detail, and
player investigation rather than rapid reflexes?
3. Room-Based World Design
Explain how dividing the world into rooms or locations simplifies
navigation and puzzle design. What advantages does this provide over
a single large continuous map for an adventure game?
4. Puzzle Integration
Choose one type of puzzle described in the chapter (item-based,
environmental, dialogue-based, or sequence puzzle). Explain how it
can be integrated naturally into the game world and narrative.
[Link]
Game Development Projects 429
Homework Questions
1. Narrative Structure and Player Agency
Discuss how branching dialogue and player choice can influence
storytelling in adventure games. How can designers balance player
freedom with a coherent narrative?
2. Inventory-Based Puzzles
Why are inventory puzzles a common feature in adventure games?
What design principles ensure these puzzles feel logical rather than
frustrating?
3. Game-State Tracking
Adventure games rely heavily on tracking progress (visited rooms,
solved puzzles, items collected). What problems might occur if
game-state variables are poorly designed or inconsistently
updated?
4. Feedback and Fairness
Explain why clear feedback is essential in adventure games when
a player attempts an incorrect action. How does feedback help
prevent players from becoming stuck?
Programming Projects
1. Room Collision and Boundaries (Core Project)
Extend the room system by adding collision detection so the player
cannot walk through walls except at door locations. Ensure movement
feels smooth and predictable.
2. Object Interaction System
Implement interactable objects within rooms, such as doors, switches,
notes, or keys. Allow the player to examine or use these objects with a
dedicated interaction key.
3. Inventory and Item Usage
Add an inventory system that allows players to collect items, view
them in a menu, and use them to solve puzzles or unlock new
areas.
4. Puzzle Implementation
Create at least one complete puzzle that requires the player to explore
rooms, gather information or items, and perform actions in a specific
order to progress.
5. NPC Dialogue and Story Hooks (Advanced)
Add NPCs that provide dialogue, hints, or story context. Implement
branching dialogue options that change based on quest progress or
prior interactions.
[Link]
430 Practical Game Programming
Capstone Project
Design and implement a small but complete adventure game scenario con-
taining multiple rooms, interactive objects, at least one NPC, and a short
puzzle-driven quest chain. The project should demonstrate coherent integra-
tion of exploration, interaction, puzzle-solving, and narrative feedback. Submit
a brief design summary explaining how the story, puzzles, and environment
work together to guide the player.
Core Components
Player Ship: The spaceship controlled by the player
Attributes: Position (x, y), movement speed, sprite, remaining lives
[Link]
Game Development Projects 431
Movement: Controlled via keyboard input; the ship moves left and right
along the bottom of the screen and is constrained within screen
boundaries
Shooting: Fires bullets upward when the player presses the designated
fire key
Enemies: Hostile entities that threaten the player
Attributes: Position (x, y), movement speed, size or sprite
Movement: Spawn near the top of the screen and move downward
toward the player at a constant or gradually increasing speed
Behavior: Must be destroyed by player bullets or avoided to prevent
collisions
Bullets: Projectiles fired by the player’s spaceship
Attributes: Position (x, y), speed, size
Movement: Travel upward from the player’s ship toward enemies
Collision: Deactivate when they strike an enemy or leave the screen
Scoring System: Tracks the player’s progress and performance
Attributes: Current score
Function: Increases the score when an enemy is successfully destroyed
Lives System: Represents the player’s remaining chances to stay in
the game
Attributes: Remaining lives (e.g., three at the start)
Rules: A collision between the player ship and an enemy results in the
loss of one life; the game ends when all lives are lost
[Link]
432 Practical Game Programming
8.4.2. Implementation
Just as for any game development project with Allegro 5 in C++, we will assume
you have all the software tools and the Allegro 5 library installed on the com-
puter platform you have chosen. If not, please go back to chapter 1 to complete
the installation.
[Link]
Game Development Projects 433
[Link]
434 Practical Game Programming
[Link]
Game Development Projects 435
struct Bullet {
float x, y;
bool active;
};
struct Enemy {
float x, y;
bool active;
};
int main() {
al_init();
al_init_image_addon();
al_init_primitives_addon();
al_init_font_addon();
al_init_ttf_addon();
al_install_keyboard();
std::srand((unsigned)std::time(nullptr));
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_timer_event_source(timer));
al_register_event_source(event_queue,
al_get_keyboard_event_source());
[Link]
436 Practical Game Programming
int score = 0;
std::vector<Bullet> bullets;
std::vector<Enemy> enemies;
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
if (!gameOver) {
// Update bullets
for (auto &bullet : bullets) {
if ([Link]) {
bullet.y—= 8;
if (bullet.y < 0) {
[Link] = false;
}
}
}
[Link]
Game Development Projects 437
// Update enemies
for (auto &enemy : enemies) {
if ([Link]) {
enemy.y += 2;
if (enemy.y > SCREEN_H) {
[Link] = false;
}
}
}
// Bullet–
enemy collisions
for (auto &bullet : bullets) {
if (![Link]) continue;
for (auto &enemy : enemies) {
if (![Link]) continue;
if (bullet.x < enemy.x + ENEMY_
SIZE &&
bullet.x + BULLET_SIZE >
enemy.x &&
bullet.y < enemy.y + ENEMY_
SIZE &&
bullet.y + BULLET_SIZE >
enemy.y) {
[Link] = false;
[Link] = false;
score += 10;
}
}
}
[Link]
438 Practical Game Programming
enemies.push_back({static_
cast<float>(std::rand() % (SCREEN_W—
ENEMY_SIZE)), 0,
true});
}
// NEW: Player–
enemy collision (use
triangle's AABB)
bool playerHit = false;
for (auto &enemy : enemies) {
if (![Link]) continue;
bool overlap =
player_x < enemy.x + ENEMY_SIZE &&
player_x + PLAYER_SIZE > enemy.x
&&
player_y < enemy.y + ENEMY_SIZE &&
player_y + PLAYER_SIZE > enemy.y;
if (overlap) {
[Link] = false; //
consume the enemy
playerHit = true;
}
}
if (playerHit) {
—lives;
if (lives <= 0) {
gameOver = true;
gameOverFrames = (int)(5 * FPS);
}
}
} else {
// NEW: simple 5-
second pause then exit
if (—
gameOverFrames <= 0) {
running = false;
}
}
redraw = true;
} else if ([Link] == ALLEGRO_EVENT_DISPLAY_
CLOSE) {
running = false;
[Link]
Game Development Projects 439
al_clear_to_color(al_map_rgb(0, 0, 0));
// Draw bullets
for (const auto &bullet : bullets) {
[Link]
440 Practical Game Programming
if ([Link]) {
al_draw_filled_rectangle(
bullet.x, bullet.y,
bullet.x + BULLET_SIZE, bullet.y
+ BULLET_SIZE,
al_map_rgb(255, 255, 255)
);
}
}
// enemy block
al_draw_filled_rectangle(
enemy.x, enemy.y,
enemy.x + ENEMY_SIZE, enemy.y +
ENEMY_SIZE,
al_map_rgb(255, 0, 0)
);
// flashing face
const char* whiteFace = u8"\u263A"; // ☺
const char* blackFace = u8"\u263B"; // ☻
const char* face = flashPhase ? blackFace
: whiteFace;
ALLEGRO_COLOR faceColor = flashPhase ?
al_map_rgb(0, 0, 0) : al_map_rgb(255, 255, 255);
[Link]
Game Development Projects 441
// Score (top-
left)
al_draw_textf(font, al_map_rgb(255, 255,
255), 10, 10, 0, "Score: %d", score);
al_flip_display();
}
}
al_destroy_font(font);
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
al_destroy_display(display);
return 0;
}
[Link]
442 Practical Game Programming
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
[Link]
Game Development Projects 443
Exercises
1. Arcade Design Principles
Explain why arcade games prioritize simple controls and immediate
visual feedback. How do these principles contribute to short,
replayable play sessions?
2. Game Loop Structure
Describe the role of the game loop in an arcade shooter. Why is it
important to separate update logic (movement, collisions) from
rendering logic?
3. Collision Detection Strategy
Review the bullet–enemy and player–enemy collision checks used
in the game. Why are axis-aligned bounding box (AABB) checks
sufficient for this type of arcade game?
4. Lives and Game-Over Logic
Explain how the life system controls game difficulty and session
length. What happens to gameplay tension as the number of
remaining lives decreases?
Homework Questions
1. Difficulty Progression
Arcade games often become more difficult over time. Discuss at
least three ways difficulty can be scaled in a space shooter without
changing the basic controls.
2. Performance Considerations
Why is it important to remove inactive bullets and enemies from
memory each frame? What problems might arise if inactive objects
are never cleaned up?
3. Visual Readability
Arcade games typically use high-contrast colours and simple shapes.
Why is visual clarity especially important when many enemies or
projectiles are on screen?
[Link]
444 Practical Game Programming
4. Replay Value
What features encourage players to replay arcade games repeatedly?
Consider scoring systems, randomness, and escalating challenge in
your answer.
Programming Projects
1. Enemy Waves and Patterns (Core Project)
Replace random enemy spawning with structured waves. Each wave
should increase in difficulty by introducing faster enemies, denser
formations, or new movement patterns.
2. Difficulty Scaling System
Implement gradual difficulty progression by increasing enemy speed,
spawn frequency, or bullet speed as the player’s score increases.
3. Power-Ups and Bonuses
Add power-ups that temporarily enhance the player, such as faster
firing, shields, wider bullets, or bonus points. Ensure power-ups
expire after a fixed duration.
4. Additional Enemy Types
Introduce new enemy behaviors, such as zigzag movement and
homing shots, or large, slow enemies that require multiple hits to
destroy.
5. Score Multipliers and Combos (Advanced)
Implement a combo or multiplier system that rewards players for
destroying enemies consecutively without missing or taking damage.
6. Pause and Menu System (Advanced)
Add a start menu, pause menu, and game-over menu. Allow the player
to restart or exit cleanly without closing the program.
[Link]
Game Development Projects 445
family, friends, or new acquaintances, the social benefits of board games are
undeniable.
In this project, we’ll create a basic tic-tac-toe game, where two players take
turns placing their marks (X or O) on a 3 × 3 grid.
Core Components
Game Board (Grid): The play area where the game takes place
Attributes: Grid size (3 × 3), cell dimensions, cell states (empty, X, O)
Function: Defines valid positions for placing marks and serves as the
basis for win and draw detection logic
Player Marks: The symbols placed by players during the game
Types: X and O
Attributes: Mark type, grid position (row, column)
Rules: Marks can only be placed in empty grid cells and cannot be
changed once placed
Turn Management System: Controls which player may act at any
given time
Attributes: Current player (Player X or Player O)
Function: Alternates turns after each valid move and prevents multiple
actions in the same turn
Feedback: Optionally displays a visual or textual indicator showing
whose turn it is
Win and Draw Detection System: Determines the outcome of the game
Checks: Three matching marks in any row, column, or diagonal
Draw Condition: All grid cells are filled and no winning alignment exists
Function: Ends the game when a win or draw condition is met and
prevents further input
[Link]
446 Practical Game Programming
[Link]
Game Development Projects 447
8.5.2. Implementation
Just as for any game development project with Allegro 5 in C++, we will assume you
have all the software tools and the Allegro 5 library installed on the computer plat-
form you have chosen. If not, please go back to chapter 1 to complete the installation.
[Link]
448 Practical Game Programming
1. Initialization
• Initialize Allegro and its add-ons for primitives, fonts, and mouse
input.
• Create a display window, font, and event queue.
2. Game Loop
• Handle events for mouse clicks to place marks on the grid.
• Update the game state, including checking for a winner or draw.
• Draw the grid, marks, and game status (winner or draw).
3. Drawing Functions
• draw_grid(): Draws the 3 × 3 grid.
• draw_marks(): Draws the X and O marks on the grid.
[Link]
Game Development Projects 449
4. Game Logic
• check_winner(): Checks if there is a winner.
• is_draw(): Checks if the game is a draw.
5. Shutdown
• Clean release of Allegro resources.
For gameplay, the first player selects a square, then the second player
proceeds to do the same. Play proceeds until one player manages to complete
three in a row for their respective character (X or O) or the game ends in a draw.
Player grid[GRID_SIZE][GRID_SIZE];
Player currentPlayer = PLAYER_X;
[Link]
450 Practical Game Programming
[Link]
Game Development Projects 451
bool is_draw() {
for (int row = 0; row < GRID_SIZE; ++row) {
for (int col = 0; col < GRID_SIZE; ++col) {
if (grid[row][col] == NONE) return false;
}
}
return true;
}
int main() {
if (!al_init()) {
std::cerr << "Failed to init Allegro\\n";
return—1;
}
al_init_primitives_addon();
al_init_font_addon();
al_init_ttf_addon();
al_install_mouse();
[Link]
452 Practical Game Programming
ALLEGRO_EVENT_QUEUE *event_queue =
al_create_event_queue();
al_register_event_source(event_queue,
al_get_display_event_source(display));
al_register_event_source(event_queue,
al_get_mouse_event_source());
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_MOUSE_BUTTON_
DOWN) {
int col = [Link].x / CELL_SIZE;
int row = [Link].y / CELL_SIZE;
[Link]
Game Development Projects 453
al_clear_to_color(al_map_rgb(0, 0, 0));
draw_grid();
draw_marks();
if (winner != NONE) {
const char* msg = (winner == PLAYER_X) ?
"Player X Wins!" : "Player O Wins!";
al_draw_text(font, al_map_rgb(255, 255, 255),
SCREEN_W / 2.0f, SCREEN_H / 2.0f, ALLEGRO_ALIGN_CENTER,
msg);
} else if (is_draw()) {
al_draw_text(font, al_map_rgb(255, 255, 255),
SCREEN_W / 2.0f, SCREEN_H / 2.0f, ALLEGRO_ALIGN_CENTER,
"It's a Draw!");
}
al_flip_display();
}
if (font) al_destroy_font(font);
if (event_queue) al_destroy_event_queue(event_queue);
if (display) al_destroy_display(display);
return 0;
}
[Link]
454 Practical Game Programming
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
[Link]
Game Development Projects 455
Exercises
1. Board-State Representation
Explain how the 3 × 3 grid array represents the game board state.
Why is it important to include an explicit NONE state in addition to
PLAYER_X and PLAYER_O?
2. Mouse-Based Input Mapping
Describe how mouse click coordinates are converted into grid indices.
What assumptions does this conversion make about cell size and
screen layout?
3. Turn Management Logic
Explain how the program ensures that Player X and Player O alternate
turns correctly. What could go wrong if turn switching were not
centralized in one place?
4. Win Detection Strategy
Review the check_winner() function. Why is it sufficient to check
rows, columns, and diagonals explicitly for a 3 × 3 Tic-Tac-Toe grid?
Homework Questions
1. State-Driven Game Logic
Discuss how Tic-Tac-Toe demonstrates the idea of a finite game state
(playing → won → draw). How does freezing input after the game ends
simplify game logic?
2. User Feedback and Clarity
Why is immediate visual feedback (drawing marks instantly, showing
win/draw messages) important for board games? What kinds of
confusion could arise without it?
3. Scalability Considerations
How would the logic for win detection need to change if the board size
were increased from 3 × 3 to 4 × 4 or 5 × 5?
4. Human vs. AI Play
What challenges arise when replacing one human player with an AI
opponent? Why might Tic-Tac-Toe be a good introductory problem for
implementing game AI?
[Link]
456 Practical Game Programming
Programming Projects
1. Win Highlighting (Core Project)
Extend the game to visually highlight the three winning cells or draw a
line through the winning combination once a player has won.
2. Replay and Reset Functionality
Add a button or key binding (such as R) that resets the board and starts
a new round without restarting the program.
3. Move Undo and Redo
Implement undo and redo functionality to allow players to step
backward and forward through their move history. This feature is
especially useful for teaching and experimentation.
4. AI Opponent
Replace one of the human players with an AI. Begin with a random-
move AI, then implement more advanced strategies such as defensive
play or the minimax algorithm.
5. Custom Board Sizes (Advanced)
Extend the game to support different board sizes (for example, 4 × 4 or
5 × 5). Update win and draw detection logic accordingly.
6. Timed Mode (Advanced)
Add an optional timer that limits how long each player has to make a
move. If time expires, the turn is forfeited or the game ends.
Capstone Project
Design a polished Tic-Tac-Toe application that includes visual win highlighting,
sound effects, selectable themes, multiple game modes (human vs. human
and human vs. AI), and persistent statistics tracking wins, losses, draws, and
streaks. Include a short design reflection explaining how each added feature
improves clarity, replayability, or user experience.
[Link]
Game Development Projects 457
Core Components
CPU (Interpreter): The central processing unit of the emulator.
Attributes: Program counter (PC), index register (I), general-purpose
registers (V0–VF), stack, stack pointer
Function: Fetches, decodes, and executes CHIP-8 instructions (opcodes)
according to the original specification
Behavior: Each opcode manipulates registers, memory, timers, graphics
output, or program control flow
Memory System: Represents the CHIP-8 addressable memory space
Attributes: 4 KB memory array, reserved interpreter region, program
memory region starting at address 0x200
Function: Stores program instructions, font sprites, and runtime data
used by the emulator
Use: ROM files are loaded into memory and executed directly without
modification
Display System: Handles graphical output for CHIP-8 programs
Attributes: 64 × 32 monochrome framebuffer
Rendering Model: Pixels are drawn using XOR logic, allowing sprite
erasure and collision detection
[Link]
458 Practical Game Programming
[Link]
Game Development Projects 459
5. ROM Loading
External CHIP-8 ROM files should be loaded into memory at the
correct start address (0x200), and execution should begin from that
location without modification.
8.6.2. Implementation
Key Operations in the Emulator
1. Initialization
• Initialize Allegro, keyboard input, display, timer, and event queue.
• Allocate memory and registers.
• Load built-in font sprites into memory.
2. ROM Loading
• Read a CHIP-8 ROM from file.
• Copy it into memory starting at address 0x200.
3. Main Emulation Loop
• Fetch and execute one or more opcodes per tick.
• Update timers at a fixed rate.
• Handle input and redraw the display when required.
4. Opcode Execution
• Implement arithmetic, logic, branching, graphics, and input
instructions.
• Maintain correctness with respect to the CHIP-8 specification.
5. Rendering
• Convert the 64 × 32 framebuffer into scaled pixels on the screen.
• Clear and redraw efficiently.
[Link]
460 Practical Game Programming
// ====================
// CHIP-
8 CONSTANTS
// ====================
static const int MEM_SIZE = 4096;
static const int VIDEO_W = 64;
static const int VIDEO_H = 32;
static const int SCALE = 10;
static const int START_ADDR = 0x200;
// ====================
// CHIP-
8 STATE
// ====================
uint8_t memory[MEM_SIZE];
uint8_t V[16]; // Registers V0–
VF
uint16_t I; // Index register
uint16_t pc; // Program counter
uint16_t stack[16];
uint8_t sp;
uint8_t delay_timer;
uint8_t sound_timer;
// ====================
// STUDENT TODO FLAGS
// ====================
bool debugMode = false;
int cycles_per_frame = 8; // STUDENT TODO: make
configurable
[Link]
Game Development Projects 461
// ====================
// INITIALIZATION
// ====================
void reset() {
memset(memory, 0, sizeof(memory));
memset(V, 0, sizeof(V));
memset(stack, 0, sizeof(stack));
memset(framebuffer, 0, sizeof(framebuffer));
memset(keypad, 0, sizeof(keypad));
pc = START_ADDR;
I = 0;
sp = 0;
delay_timer = 0;
sound_timer = 0;
// ====================
// ROM LOADING
// ====================
bool loadROM(const char* filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) return false;
[Link]((char*)&memory[START_ADDR], MEM_SIZE
- START_ADDR);
return true;
}
// ====================
// OPCODE EXECUTION
// ====================
void executeCycle() {
uint16_t opcode = memory[pc] << 8 | memory[pc + 1];
pc += 2;
[Link]
462 Practical Game Programming
// Implement at least:
// -CLS (00E0)
// -JP addr (1NNN)
// -LD Vx, byte (6XNN)
// -ADD Vx, byte (7XNN)
// -DRW Vx, Vy, nibble (DXYN)
if (debugMode) {
std::cout << "PC=" << std::hex << pc
<< " OP=" << opcode << std::dec <<
"\n";
}
}
// ====================
// DEBUG DRAWING
// ====================
void drawDebugOverlay() {
// TODO (Task 4):
// Display register values, PC, I, timers.
// Optional: display memory or stack contents.
}
// ====================
// MAIN PROGRAM
// ====================
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: chip8 <rom>\n";
return 1;
}
// Allegro setup
al_init();
al_init_primitives_addon();
al_install_keyboard();
ALLEGRO_DISPLAY* display =
al_create_display(VIDEO_W * SCALE, VIDEO_H *
SCALE);
[Link]
Game Development Projects 463
al_register_event_source(queue,
al_get_display_event_source(display));
al_register_event_source(queue,
al_get_timer_event_source(timer));
al_register_event_source(queue,
al_get_keyboard_event_source());
reset();
if (!loadROM(argv[1])) {
std::cerr << "Failed to load ROM\n";
return 1;
}
al_start_timer(timer);
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if ([Link] == ALLEGRO_EVENT_TIMER) {
// ====================
// CPU EXECUTION
// ====================
for (int i = 0; i < cycles_per_frame; ++i) {
executeCycle();
}
// ====================
// TIMERS
// ====================
if (delay_timer > 0) delay_timer-
-
;
if (sound_timer > 0) sound_timer-
-
;
[Link]
464 Practical Game Programming
// ====================
// RENDERING
// ====================
al_clear_to_color(al_map_rgb(0, 0, 0));
if (debugMode) {
drawDebugOverlay();
}
al_flip_display();
}
else if ([Link] == ALLEGRO_EVENT_KEY_DOWN) {
// ====================
// INPUT
// ====================
if ([Link] ==
ALLEGRO_KEY_ESCAPE)
running = false;
[Link]
Game Development Projects 465
}
else if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
}
al_destroy_event_queue(queue);
al_destroy_timer(timer);
al_destroy_display(display);
return 0;
}
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
1. Sound Support: Add audible feedback when the sound timer is active.
2. Instruction Accuracy: Verify emulator behavior against known
CHIP-8 test ROMs.
3. Configurable Speed: Allow the user to adjust CPU execution speed.
4. Debug Mode: Add register and memory inspection tools for learning
and testing.
Exercises
1. Emulation vs. Game Recreation
Explain the difference between writing an emulator and re-
implementing a game. Why does running original CHIP-8 ROMs
require accurate replication of the original system’s behavior?
2. Instruction Fetch-Decode-Execute Cycle
Describe each stage of the fetch-decode-execute cycle used in the
CHIP-8 emulator. Why is it important to increment or modify the
program counter carefully during opcode execution?
3. Timers and Synchronization
Explain the difference between the CPU execution rate and the 60 Hz
delay and sound timers in CHIP-8. What problems can occur if these
timers are updated incorrectly?
[Link]
466 Practical Game Programming
Homework Questions
1. Opcode Accuracy and Undefined Behavior
Why is opcode accuracy critical for emulator correctness? What
types of bugs might appear if even a single opcode is implemented
incorrectly?
2. Input-Mapping Challenges
Discuss the challenges involved in mapping a modern keyboard to
the CHIP-8 hexadecimal keypad. How can poor input mapping affect
game playability?
3. Rendering Efficiency
Why is it beneficial to redraw the display only when the display flag is
set, rather than every CPU cycle?
4. Debugging Emulated Systems
Explain how a debug mode that displays registers, memory, and the
program counter can help diagnose emulator errors more effectively
than logging alone.
Programming Projects
1. Sound Timer Support (Core Project)
Implement sound output that plays whenever the sound timer is
greater than zero. Use Allegro’s audio system to generate a short
beep or tone, and ensure it stops precisely when the timer reaches
zero.
2. Opcode Set Completion
Extend the emulator by implementing all remaining CHIP-8 opcodes.
Verify correctness by running multiple test ROMs and comparing
behavior with known-good emulator results.
3. Configurable CPU Speed
Add keyboard controls that allow the user to increase or decrease the
number of CPU cycles executed per frame at runtime. Display the
current speed on screen.
4. Debug Overlay Implementation
Implement a toggleable debug overlay that displays register values
(V0–VF), the index register, program counter, stack pointer, and
timers. Optionally highlight memory regions accessed by the current
opcode.
[Link]
Game Development Projects 467
Capstone Project
Develop a polished CHIP-8 emulator application that supports accurate opcode
execution, sound, configurable speed, debugging tools, and multiple ROM
loading. Include documentation describing how the emulator implements the
fetch-decode-execute cycle, timer synchronization, and input mapping. Demon-
strate correctness by running at least three classic CHIP-8 games successfully.
Core Components
Puzzle Grid: The structured layout that holds all tiles
Attributes: Grid dimensions (e.g., 4 × 4), cell size, grid coordinates
Function: Defines valid tile locations and constrains movement rules
[Link]
468 Practical Game Programming
[Link]
Game Development Projects 469
8.7.2. Implementation
Key Operations in the Game
1. Initialization
• Initialize the Allegro library and its components (image add-on,
keyboard input).
• Create the game window with specified dimensions.
2. Tile Creation
• Each tile has properties like its current position (x, y), its correct
position (correctX, correctY), and its image.
• Generate tiles and assign them their correct positions. Create a
bitmap for each tile and fill it with a random colour.
3. Shuffling
• Shuffle the tiles randomly to create the initial puzzle state. Optional:
Ensure the puzzle is solvable.
4. Rendering
• Draw each tile at its current position on the screen. This is done in
the game loop to continuously update the display.
5. Input Handling
• Capture user input to move the tiles. For example, use arrow keys to
move the empty space and slide adjacent tiles into it.
6. Game Logic
• Implement logic to move tiles into the empty space when the player
presses a key.
[Link]
470 Practical Game Programming
• Check if the tiles are in the correct order to determine if the player
has solved the puzzle.
7. Game Loop
• The game runs in a loop where it continuously checks for user
input, updates the game state, and renders the tiles.
//——Config——
const int GRID_SIZE = 4;
const int TILE_SIZE = 150; // 4 × 4 *
150 = 600px square
const int SCREEN_W = GRID_SIZE * TILE_SIZE;
const int SCREEN_H = GRID_SIZE * TILE_SIZE;
[Link]
Game Development Projects 471
// Initialization
bool init() {
if (!al_init()) { std::cerr << "al_init failed\n";
return false; }
if (!al_init_image_addon()) { std::cerr << "image
addon init failed\n"; return false; }
[Link]
472 Practical Game Programming
// Tile Creation
void create_tiles() {
[Link]();
emptyRow = GRID_SIZE—
1;
emptyCol = GRID_SIZE—
1;
solved = false;
[Link]
Game Development Projects 473
[Link] = r; [Link] = c;
[Link] = label++;
[Link] = al_create_bitmap(TILE_SIZE,
TILE_SIZE);
if (![Link]) { std::cerr << "Failed to
create tile bitmap\n"; continue; }
ALLEGRO_BITMAP* back =
al_get_target_bitmap();
al_set_target_bitmap([Link]);
// pleasant pseudo-
random color
(deterministic per label for consistency):
std::srand([Link] * 2654435761u); // mix a
bit
ALLEGRO_COLOR fill = al_map_rgb(70
+ std::rand()%160, 70 + std::rand()%160, 70 +
std::rand()%160);
std::srand(static_
cast<unsigned>(std::time(nullptr))); // restore
randomness for shuffle later
al_clear_to_color(fill);
// border
al_draw_filled_rectangle(0, 0, TILE_SIZE, 6,
al_map_rgb(0,0,0));
al_draw_filled_rectangle(0, TILE_SIZE-6,
TILE_SIZE, TILE_SIZE, al_map_rgb(0,0,0));
al_draw_filled_rectangle(0, 0, 6, TILE_SIZE,
al_map_rgb(0,0,0));
al_draw_filled_rectangle(TILE_SIZE-6, 0,
TILE_SIZE, TILE_SIZE, al_map_rgb(0,0,0));
// number
draw_number_centered([Link], g_font_numbers,
[Link], al_map_rgb(255,255,255));
al_set_target_bitmap(back);
tiles.push_back(t);
}
}
}
[Link]
474 Practical Game Programming
// Shuffling
void shuffle_tiles(int moves = 400) {
for (int i = 0; i < moves; ++i) {
(void)do_random_move();
}
}
[Link]
Game Development Projects 475
bool is_solved() {
if (!(emptyRow == GRID_SIZE-
1 && emptyCol ==
GRID_SIZE-
1)) return false;
for (const auto& t : tiles) {
if ([Link] != [Link] || [Link] !=
[Link]) return false;
}
return true;
}
// Drawing
void draw_tiles() {
al_clear_to_color(al_map_rgb(30, 30, 30));
for (const auto& t : tiles) {
int x = [Link] * TILE_SIZE;
int y = [Link] * TILE_SIZE;
al_draw_bitmap([Link], x, y, 0);
}
// empty cell outline
int ex = emptyCol * TILE_SIZE;
int ey = emptyRow * TILE_SIZE;
al_draw_rectangle(ex+4, ey+4, ex + TILE_SIZE—
4, ey +
TILE_SIZE—
4, al_map_rgb(255, 255, 255), 2);
if (solved) {
const char* msg = "Solved!";
int w = al_get_text_width(g_font_ui, msg);
int h = al_get_font_line_height(g_font_ui);
int x = SCREEN_W/2—
w/2;
int y = SCREEN_H/2—
h/2;
al_draw_filled_rectangle(x-
16, y-10, x+w+16,
y+h+10, al_map_rgba(0,0,0,200));
al_draw_text(g_font_ui, al_map_rgb(255, 255,
255), SCREEN_W/2, SCREEN_H/2, ALLEGRO_ALIGN_CENTER,
msg);
}
}
[Link]
476 Practical Game Programming
solved = is_solved();
}
// Main loop
int main() {
if (!init()) return—
1;
[Link]
Game Development Projects 477
al_register_event_source(queue,
al_get_display_event_source(display));
al_register_event_source(queue,
al_get_keyboard_event_source());
al_register_event_source(queue,
al_get_timer_event_source(timer));
create_tiles();
shuffle_tiles();
al_start_timer(timer);
[Link]
478 Practical Game Programming
handle_keypress([Link]);
}
break;
case ALLEGRO_EVENT_TIMER:
draw_tiles();
al_flip_display();
break;
}
}
// Finishing up
for (auto& t : tiles) {
if ([Link]) al_destroy_bitmap([Link]);
}
if (g_font_numbers) al_destroy_font(g_font_numbers);
if (g_font_ui) al_destroy_font(g_font_ui);
al_destroy_timer(timer);
al_destroy_event_queue(queue);
al_destroy_display(display);
return 0;
}
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
[Link]
Game Development Projects 479
Exercises
1. Grid Representation and Tile Location
Explain how the two-dimensional grid representation simplifies both
the movement logic and rendering of puzzle tiles. Why is it useful to
separate a tile’s current position from its correct position?
2. Movement Constraints
Describe why only tiles adjacent to the empty space are allowed
to move. How does enforcing this rule guarantee valid puzzle
interactions?
3. Solvability Considerations
Explain why random permutations of tiles may result in unsolvable
puzzles. How does shuffling the puzzle by performing many legal
moves avoid this problem?
4. Win Condition Detection
Review the is_solved() function. Why is it important to check both the
tile positions and the location of the empty space?
Homework Questions
1. User Experience in Puzzle Games
Discuss how clear visual feedback (such as tile borders, numbered
labels, and empty-space outlines) affects the player’s ability to
understand and solve the puzzle.
2. Keyboard vs. Mouse Control
Compare keyboard-based tile movement with mouse-based
interaction (clicking tiles to slide them). What are the advantages and
disadvantages of each input method?
3. Difficulty and Cognitive Load
How does increasing the grid size affect the puzzle’s difficulty?
At what point might a larger grid become frustrating rather than
challenging?
4. Puzzle Games and Replayability
Why are simple puzzle games often still highly replayable? Discuss
the role of randomness, difficulty scaling, and performance tracking
(time or moves).
[Link]
480 Practical Game Programming
Programming Projects
1. Move Counter and Scoring System (Core Project)
Add a move counter that increments each time a tile is slid. Display
the number of moves on the screen and show the final move count
when the puzzle is solved.
2. Sound Effects Integration
Add a sound effect that plays whenever a tile moves. Optionally include
a different sound for invalid moves or when the puzzle is completed.
3. Difficulty Levels
Extend the game to support different grid sizes, such as 3 × 3, 4 × 4, or
5 × 5. Allow the player to select the difficulty level before starting or
restarting the puzzle.
4. Timer-Based Challenge Mode (Advanced)
Add a timer that tracks how long the player takes to solve the puzzle.
Display the elapsed time and use it as an alternative scoring metric
alongside the move counter.
5. Image-Based Puzzle (Advanced)
Replace the coloured tiles with fragments of an image. Load a single
image, divide it into tile-sized sections, and render each fragment on
the corresponding tile.
6. Animated Tile Sliding (Optional)
Add smooth animations so tiles slide into the empty space rather than
snapping instantly. Interpolate tile positions over a short duration to
improve visual polish.
[Link]
Game Development Projects 481
enemy types, weapons, locations, quests, treasures, and so forth. As such, here,
we’ll cover only the outline of a possible basic design and some implementation
steps for a simple RPG—a basic framework that you can expand upon.
Core Components
Player Character: The main controllable protagonist
Attributes: Position (x, y), HP, attack, defense, level, experience points (XP)
Movement: Controlled via keyboard input (e.g., WASD or arrow keys)
Enemies: Hostile entities that challenge the player
Attributes: Position, HP, combat stats, sprite
Behavior: Move and attack using simple AI patterns
Nonplayer Characters (NPCs): Interactive characters within the world
Attributes: Dialogue state, quest flags, position
Function: Provide information, quests, items, or services
Quests and Story System: Tracks narrative progress
Attributes: Quest states (inactive, active, completed), quest stages
Function: Unlocks content and guides player progression
Inventory System: Manages collected items
Attributes: Item slots, item IDs, quantities
Function: Supports consumables, equipment, and key items
Combat System: Handles battles with enemies
Type: Turn-based combat
Function: Resolves attacks, abilities, damage, and rewards
[Link]
482 Practical Game Programming
[Link]
Game Development Projects 483
8.8.2. Implementation
Key Operations in the Game
The following operations constitute the core runtime flow of a simple
Allegro-based RPG. They map directly to the systems listed above and can be
implemented incrementally.
[Link]
484 Practical Game Programming
• Apply item rules (consumable vs. equipment vs. key) and enforce
stack counts.
7. Encounter Handling (Overworld → Battle)
• Detect battle start (random encounter tile, scripted boss trigger, or
touching an enemy).
• Push a combat state and pause overworld updates until combat
ends.
8. Combat Turn Cycle
• Establish turn order; in the player’s turn, accept action selection
(attack, skill, item).
• Compute damage/heal with the current formula and update HP/MP.
• Check victory/defeat; on victory, award XP/loot and return to
overworld.
9. Progression Updates
• Add XP; if threshold reached, level up, raise stats, and show a
level-up banner/SFX.
• Optional: Unlock new abilities at specific levels.
10. Map Transitions and Scene Management
• On door/warp tiles, save the player’s exit point, switch the current
map, and place the player at the entry point.
• Reload map layers, entities, and background music as needed.
11. Rendering Pipeline per Frame
• Clear → draw tile map (visible region) → draw entities → draw
overlays (e.g., highlights) → draw HUD/Dialog/menus → flip display.
• Respect draw order for clarity (UI on top).
12. Audio Triggers
• Play loops for BGM; trigger one-shots for actions (confirm, attack,
item use, level up).
• Fade or switch tracks on map/scene changes.
13. Saving and Loading
• Serialize player stats, position, inventory, quest flags, and map
identifier to a save file.
• On load, restore state and assets, then resume at the saved location.
14. Performance and Housekeeping
• Avoid per-frame allocations in hot paths; reuse containers/buffers.
• Destroy Allegro resources (bitmaps, fonts, audio) on shutdown;
guard against nulls.
[Link]
Game Development Projects 485
[Link]
486 Practical Game Programming
void shutdown() {
if (queue) al_destroy_event_queue(queue);
if (timer) al_destroy_timer(timer);
if (display) al_destroy_display(display);
al_uninstall_audio();
}
};
[Link]
Game Development Projects 487
std::unordered_map<std::string, ALLEGRO_BITMAP*>
bitmaps;
std::unordered_map<std::string, ALLEGRO_FONT*>
fonts;
// SFX/BGM maps omitted for brevity.
bool load() {
bitmaps["player"] = al_load_bitmap("assets/
[Link]");
bitmaps["tiles"] = al_load_bitmap("assets/tiles.
png");
fonts["ui"] = al_load_ttf_font("assets/
[Link]", 18, 0);
// Check for nulls in real code.
return true;
}
void unload() {
for (auto& [k,v] : bitmaps) if (v)
al_destroy_bitmap(v);
for (auto& [k,v] : fonts) if (v)
al_destroy_font(v);
}
};
[Link]
488 Practical Game Programming
struct WorldEntities {
Entity player;
std::vector<Entity> enemies;
std::vector<Entity> npcs;
void create(Assets& A) {
[Link] = [Link]("player");
player.x = 100; player.y = 100;
enemies.push_back(Entity{300, 180, [Link]("enemy_
slime"), Stats{6,0,1,0,1,0}, true});
// Add NPCs similarly . . .
}
};
[Link]
Game Development Projects 489
[Link]
490 Practical Game Programming
bool boot() {
if (![Link]()) return false;
[Link]();
[Link]["enemy_slime"] = al_load_
bitmap("assets/enemy_slime.png");
[Link]("assets/maps/[Link]", assets);
[Link](assets);
return true;
}
[Link]
Game Development Projects 491
void draw() {
al_clear_to_color(al_map_rgb(0,0,0));
[Link](map, ents, camx, camy);
[Link](assets, "HP: " + std::to_
string([Link]));
al_flip_display();
}
void run() {
while ([Link]) {
ALLEGRO_EVENT ev; al_wait_for_event(app.
queue, &ev);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE)
[Link] = false;
if ([Link] == ALLEGRO_EVENT_TIMER) {
update(1.0/60.0); draw(); }
if ([Link] == ALLEGRO_EVENT_KEY_DOWN ||
[Link] == ALLEGRO_EVENT_KEY_UP) {
[Link](ev);
if ([Link]) [Link] = false;
}
}
}
};
[Link]
492 Practical Game Programming
struct DialogueDB {
std::unordered_map<std::string, DialogueNode> nodes;
const DialogueNode* get(const std::string& id) const
{
auto it = [Link](id); return (it==nodes.
end())? nullptr : &it-
>second;
}
};
struct DialogueRunner {
const DialogueDB* db=nullptr;
const DialogueNode* cur=nullptr;
bool active=false;
[Link]
Game Development Projects 493
struct Quest {
std::string id;
int stage=0; // 0..N
QuestState state=QuestState::Inactive;
};
struct QuestLog {
std::unordered_map<std::string, Quest> quests;
void start(const std::string& id) { quests[id] =
Quest{id,0,QuestState::Active}; }
void advance(const std::string& id) { auto&
q=quests[id]; if ([Link]==QuestState::Active) ++[Link];
}
void complete(const std::string& id) { quests[id].
state = QuestState::Completed; }
bool isActive(const std::string& id) const {
auto it=[Link](id); return it!=[Link]()
&& it->[Link]==QuestState::Active;
}
};
struct Inventory {
struct Slot { int itemId=-
1; int count=0; };
std::vector<Slot> slots = std::vector<Slot>(24);
bool add(int itemId, int n=1) {
for (auto& s: slots) if ([Link]==itemId) {
[Link]+=n; return true; }
for (auto& s: slots) if ([Link]==-
1) {
[Link]=itemId; [Link]=n; return true; }
[Link]
494 Practical Game Programming
return false;
}
bool use(int idx, Entity& target, const ItemDB& DB) {
if (idx<0 || idx>= (int)[Link]()) return false;
auto& s = slots[idx]; if ([Link]<0 ||
[Link]<=0) return false;
const ItemDef& def = [Link]([Link]);
if ([Link] == ItemType::Consumable) {
[Link] = std::min([Link] +
[Link], 999);
if (—[Link]==0) [Link]=-1;
return true;
}
// Equipment/Key handling omitted in minimal
sketch.
return false;
}
};
[Link]
Game Development Projects 495
[Link]
496 Practical Game Programming
Game game;
if (![Link]()) return 1;
[Link]();
[Link]();
return 0;
}
[Link]
Game Development Projects 497
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
[Link]
498 Practical Game Programming
Exercises
1. RPG System Identification
List the major gameplay systems described in this section (movement,
dialogue, quests, inventory, combat, progression, and UI). Briefly
explain how at least three of these systems interact with one another
during normal gameplay.
2. Tile-Based World Navigation
Explain how tile-based maps simplify collision detection and
movement logic. Why is it useful to separate visual tile layers from
collision or trigger layers?
3. Dialogue and Choice Flow
Examine the dialogue system structure. How do dialogue nodes and
choices enable branching conversations, and why is this approach
preferable to hard-coded dialogue strings?
4. Turn-Based Combat Reasoning
Describe the basic turn order used in the combat scaffold. How does
turn-based combat simplify decision-making and state management
compared to real-time combat?
Homework Questions
1. Balancing Complexity in RPG Design
RPGs often include many interconnected systems. What risks arise
when too many features are introduced too early in development, and
how can a staged or modular approach mitigate these risks?
2. Quest Design and Player Motivation
Discuss how quests help guide player behavior and provide narrative
structure. What types of quest objectives are best suited for beginner-
level RPG implementations?
3. Inventory Management Trade-offs
Compare simple inventory systems (fixed slot lists) with more
complex systems (weight limits, equipment slots). What trade-offs do
these designs present for both developers and players?
4. Progression and Player Engagement
Explain how experience points and leveling systems contribute
to long-term player engagement. What problems might arise if
progression is too fast or too slow?
[Link]
Game Development Projects 499
Programming Projects
1. Combat Mechanics Completion (Core Project)
Complete the turn-based combat system by implementing victory and
defeat conditions, removing defeated enemies, and returning control
to the overworld after combat ends.
2. NPC Interaction and Dialogue Integration
Fully integrate NPC interactions so that the player can initiate dialogue
through proximity or facing direction. Connect dialogue outcomes to
quest progression, item rewards, or other gameplay effects.
3. Inventory UI and Item Usage
Design and implement a user interface for the inventory system.
Allow the player to browse items, select consumables, and apply their
effects during gameplay.
4. Quest System Implementation
Extend the quest system so that quests can be started through
dialogue, advanced through world triggers or combat events, and
completed to grant rewards such as experience, items, or currency.
5. Leveling and Progression Enhancements
Link combat outcomes to the progression system so that defeating
enemies grants experience points. Display visual or audio feedback
when the player levels up and unlocks stat improvements or abilities.
6. Enemy AI Improvements (Advanced)
Improve enemy behavior by introducing patrol routes, pursuit logic,
or simple decision-making (for example, retreating at low health or
prioritizing certain player actions).
7. Audio and Feedback Integration (Advanced)
Add sound effects for movement, combat actions, item usage, and
level-ups, along with background music for different map areas or
combat encounters.
[Link]
500 Practical Game Programming
[Link]
Game Development Projects 501
Recall what we’ve covered in previous chapters in terms of AI for NPCs. Here,
we might do something where the NPC goes toward the player with the ball
(seeking) with the goal of trying to take the ball away from the player (where
a collision transfers an object). The user player is trying to evade (triggering a
flee) function to get away. Here, we need to consider the speed of the player
and the NPC in terms of running/skating. If both the user player and the NPC
are moving at the same speed, we’ve got a situation where each will never catch
up to the other if they evade. So we might implement a mechanic whereby if
you are running in a straight line, your speed gradually increases, but if you
change direction, you go back to that base speed. We’d give that same factor
to the NPC character as well. Then we’d have a situation where when you’re
running after an object, you can overtake the object, and a direction change
becomes strategic in getting away from a pursuer. As mentioned before, with
a collision detection, your player transfers the ball/puck object on contact, so
it’s a game of figuring out angles and velocities to keep your ball/puck and get
into position to make your shot into the goal.
In game scenarios like these, you start to see where one can apply elements
from other chapters:
Taking it to the next level, you could have multiple NPCs on-screen: your
team and the opponent. This will add multiple layers of logic, where your player
NPCs would defend against the opposing team’s NPCs and so on. Such is the
complexity of elements that you would need to put into even the simplest
sports game.
[Link]
502 Practical Game Programming
Core Components
Player Character: The athlete controlled by the player
Attributes: Position (x, y), movement speed, sprite
Movement: Controlled via keyboard input
Basketball: The object used to score points
Attributes: Position, velocity, possession state
Physics: Affected by gravity and collisions
Opposing NPC: Defensive character
Attributes: Position, speed, sprite
Behavior: Seeks the player, attempts steals, or blocks shots
Hoop and Scoring Zone: The scoring target
Attributes: Position, collision bounds
Function: Registers a score when the ball passes through correctly
Score and Timer System: Tracks performance
Attributes: Current score, remaining time
Function: Ends the game when time expires
[Link]
Game Development Projects 503
8.9.2. Implementation
Key Operations in the Game
1. Input Sampling and Intent Flags
• Read Allegro keyboard events each frame; set flags for move left/
right/up/down, shoot hold/release, pause, debug.
[Link]
504 Practical Game Programming
[Link]
Game Development Projects 505
[Link]
506 Practical Game Programming
al_register_event_source(q,
al_get_timer_event_source(t));
al_register_event_source(q,
al_get_keyboard_event_source());
al_start_timer(t); return disp&&q&&t;
}
void shutdown(){ if(q)al_destroy_event_queue(q);
if(t)al_destroy_timer(t); if(disp)al_destroy_
display(disp); }
};
[Link]
Game Development Projects 507
struct Ball {
Vec pos{200, 500}, vel{0,0};
float radius=12.0f; bool airborne=false;
Possession owner = Possession::Player;
};
struct Actor {
Vec pos{100,500}, vel{0,0};
float baseSpeed=200; float burstGain=0; // speed-
ramp
mechanic
bool moving=false;
ALLEGRO_BITMAP* sprite=nullptr;
};
[Link]
508 Practical Game Programming
struct World {
Court court;
Actor player, npc;
Ball ball;
int score=0;
float timeLeft=60.0f;
bool gameOver=false;
};
[Link]
Game Development Projects 509
// floor bounce
if ([Link].y + [Link] > c.H) {
[Link].y = c.H—
[Link];
[Link].y *=—
0.55f; // damped bounce
if (std::fabs([Link].y) < 60) {
[Link]=false; [Link]={0,0}; }
}
// walls
if ([Link].x—
[Link] < 0) { [Link].x = [Link];
[Link].x *=—
0.6f; }
if ([Link].x + [Link] > c.W){ [Link].x = c.W—
b.
radius; [Link].x *=—
0.6f; }
}
};
struct Collisions {
// Rim knockback and score detection
void handle(World& w, Assets& A){
// score: ball passes through scoreZone while
moving left->right
if ([Link] && [Link](w.
[Link].x, [Link].y) && [Link].x>0){
++[Link]; [Link]=false; [Link].
owner=Possession::NPC; // after score, hand to NPC?
[Link]
510 Practical Game Programming
al_play_sample([Link]["score"], 1,0,1,
ALLEGRO_PLAYMODE_ONCE, nullptr);
}
// rim collision → reflect
if (intersectsCircleRect([Link], [Link].
radius, [Link])){
[Link].x *=—
0.6f; [Link].y *=—
0.6f;
al_play_sample([Link]["block"], 0.6,0,1,
ALLEGRO_PLAYMODE_ONCE, nullptr);
}
// steal/block: if NPC intersects ball in flight
→ chance to catch
float dist = len([Link]—
[Link]);
if ([Link] && dist < 32.0f) {
if ((rand()%100)<30) { // 30% block/catch
[Link]=false; [Link].
owner=Possession::NPC; [Link]={0,0};
al_play_sample([Link]["block"], 1,0,1,
ALLEGRO_PLAYMODE_ONCE, nullptr);
}
}
// pickup when free on ground
if (![Link] && [Link].
owner==Possession::Free){
if (len([Link]—
[Link])<28) [Link].
owner=Possession::Player;
if (len([Link] —
[Link])<28) [Link].
owner=Possession::NPC;
}
// carry with owner
if (![Link]){
if ([Link]==Possession::Player) [Link].
pos = [Link] + Vec{10,-
20};
if ([Link]==Possession::NPC) [Link].
pos = [Link] + Vec{-
10,-
20};
}
}
};
[Link]
Game Development Projects 511
struct AI {
float accel=800.0f, decel=1200.0f, burstMax=180.0f,
turnPenalty=160.0f;
// pursue
Vec dir = norm(target—
[Link]);
Vec desired = dir * ([Link] + [Link].
burstGain);
// turn penalty: reduce burst if direction
changes sharply
if (dot(norm([Link]), dir) < 0.6f) [Link].
burstGain = 0; // sharp turn resets run-
up
// straight-
line ramp up
if (len(step)>0.0f && dot(norm([Link]), dir) >
0.95f)
[Link] = std::min(burstMax, [Link].
burstGain + 60.0f*dt);
[Link]
512 Practical Game Programming
[Link]=Possession::Free;
[Link]=true;
[Link] = { baseVX, baseVY };
al_play_sample([Link]["shot"], 0.9,0,1, ALLEGRO_
PLAYMODE_ONCE, nullptr);
[Link]
Game Development Projects 513
charge=0; [Link]=false;
}
};
bool boot(){
if(![Link]()) return false;
[Link]();
[Link] = [Link]["player"];
[Link] = [Link]["npc"];
return true;
}
[Link]
514 Practical Game Programming
// ramp
if ([Link] && dot(norm([Link].
vel), dir) > 0.95f)
[Link] = std::min(180.0f,
[Link] + 80.0f*dt);
else if (![Link])
[Link] = std::max(0.0f,
[Link]—200.0f*dt);
// Timer
[Link]—= dt;
if ([Link] <= 0) [Link] = true;
}
void draw(){
al_clear_to_color(al_map_rgb(20,20,30));
if ([Link]["court"])
al_draw_bitmap([Link]["court"], 0, 0, 0);
// Draw actors
[Link]
Game Development Projects 515
if ([Link]) al_draw_
bitmap([Link], [Link].x-16,
[Link].y-32, 0);
if ([Link] .sprite) al_draw_bitmap([Link]
.sprite, [Link] .pos.x-
16, [Link] .pos.y-
32, 0);
if ([Link]["ball"]) al_draw_bitmap(assets.
bmp["ball"], [Link].x-
12, [Link].y-
12, 0);
// HUD
std::string hud = "Score: " + std::to_
string([Link]) + " Time: " + std::to_string((int)
std::ceil([Link]));
al_draw_text([Link]["ui"], al_map_
rgb(255,255,255), 16, 16, 0, hud.c_str());
// Debug
if ([Link]){
// score window & rim
al_draw_rectangle([Link].l,
[Link].t,
[Link].r,
[Link].b, al_map_rgb(255,255,0), 2);
al_draw_rectangle([Link].l,
[Link].t,
[Link].r,
[Link].b, al_map_rgb(255,0,0), 2);
}
if ([Link]){
al_draw_text([Link]["ui"], al_map_
rgb(255,200,0), 640, 320, ALLEGRO_ALIGN_CENTER, "GAME
OVER");
}
[Link]
516 Practical Game Programming
al_flip_display();
}
void run(){
while(true){
ALLEGRO_EVENT ev; al_wait_for_event(app.q,
&ev);
if ([Link]==ALLEGRO_EVENT_TIMER){
update(1.0f/60.0f); draw(); }
else handleEvent(ev);
if ([Link]) break;
}
}
};
int main(){
Game g; if(![Link]()) return 1;
[Link](); [Link](); return 0;
}
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
Exercises
1. Possession State Analysis
Identify the different possession states of the ball (player-owned, NPC-
owned, free). Explain how transitions between these states occur during
gameplay and why it is important to explicitly manage possession.
[Link]
Game Development Projects 517
2. Hold-to-Shoot Mechanics
Explain how charging shot power using key-press duration affects
gameplay difficulty. What advantages does this mechanic have over a
single-press shooting model?
3. Ball Physics Understanding
Describe the role of gravity, velocity, and damping in the basketball’s
movement. How do these factors influence whether a shot overshoots,
undershoots, or scores?
4. NPC Blocking Logic
Analyze the NPC’s blocking and interception behavior. Why is it beneficial
to use probability and cooldowns rather than deterministic blocking?
Homework Questions
1. Sports Game Design Trade-Offs
Compare realism and fun in sports games. Why might an arcade-
style basketball game intentionally simplify physics or automate
aiming?
2. Speed-Ramp Mechanics
Discuss how gradually increasing speed during sustained movement
changes player strategy. How does this mechanic affect evasion,
pursuit, and positioning?
3. Scoring Reliability
Explain why separating rim collision detection from scoring window
detection produces more reliable scoring behavior than treating the
hoop as a single collision object.
4. AI Pressure and Player Experience
How does continuous NPC pressure (stealing, blocking, intercepting)
influence the pace and challenge of the game? What risks exist if NPC
behavior is too aggressive or too passive?
Programming Projects
1. Shooting Mechanics Completion (Core Project)
Fully implement the shooting logic, including charging, release,
trajectory calculation, and successful scoring detection when the ball
passes through the hoop’s scoring window.
2. Scoring System Enhancement
Extend the scoring system to track points per shot, streak bonuses, or
accuracy percentages. Display this information in the HUD and update
it dynamically.
[Link]
518 Practical Game Programming
Capstone Project
Design and implement a complete one-on-one basketball challenge with pol-
ished presentation. The game should include responsive controls, believable
physics, competitive NPC behavior, clear scoring and timing rules, and audio-
visual feedback. Submit a short design report explaining your choices for
difficulty balancing, AI logic, and user interface clarity.
[Link]
Game Development Projects 519
game’s goal is to gather resources, build your combat units, and use those to
take control of the whole map.
Core Components
Units: Controllable characters
Attributes: Position, health, attack power, state
Actions: Move, gather resources, attack, build
Resources: Materials used for production
Attributes: Type (wood, stone), amount, location
Function: Enable unit and structure creation
Structures: Buildings placed on the map
Attributes: Position, health, type
Function: Produce units or support the economy
Economy System: Manages resource flow
Function: Handles gathering, carrying, and depositing resources
Enemy AI: Controls opposing forces
Behavior: Gathers resources, builds units, attacks player assets
UI and Command System: Player interaction layer
Function: Selection, issuing commands, displaying information
[Link]
520 Practical Game Programming
[Link]
Game Development Projects 521
10. UI/HUD
The interface should clearly display resources, population limits,
selected unit information, and build options. Tool tips should explain
costs and stats. An optional minimap can support navigation, alerts,
and situational awareness.
11. Game States and Save/Load
The game should transition cleanly between play, pause, victory, and
defeat states. Pausing must freeze simulation without losing state.
Optional save/load functionality may snapshot world state, resources,
and production queues.
12. Performance and Data-Driven Design
To maintain performance, avoid per-frame allocations and reuse
frequently created objects. Units, structures, and costs should be
defined in external data files (e.g., JSON or CSV) to allow tuning and
extension without recompilation.
8.10.2. Implementation
Key Operations in the Game
1. Event Intake and Intent Mapping
• Poll Allegro events (mouse, keyboard).
• Update intent flags (selecting, dragging, issuing right-click orders,
build hotkeys, camera pan/zoom, pause).
2. Screen ↔ World Transform
• Convert cursor from screen → world using camera offset and zoom.
• Use world coords for hit-tests (units, resources, structures) and
placement ghosts.
3. Selection Lifecycle
• On LMB down: Start marquee.
• On LMB up: Compute marquee AABB in world space; mark units
inside as selected (respect team).
• Shift-click to add/remove single units.
4. Command Resolution (RMB)
• Determine context at clicked world point (terrain, resource node,
enemy, friendly building).
• Issue corresponding order to all selected units: Move, Gather,
Attack, Build/Repair, or Return.
• Stagger goals slightly (formation spread) to reduce overlap.
5. Per-Tick Unit FSM Update
• For each unit, process state (Idle/Move/Gather/Return/Attack/Build).
• Update cooldowns, acquisition, and retreat checks.
[Link]
522 Practical Game Programming
[Link]
Game Development Projects 523
[Link]
524 Practical Game Programming
al_register_event_source(q,
al_get_keyboard_event_source());
al_register_event_source(q,
al_get_mouse_event_source());
al_start_timer(timer); return disp&&q&&timer;
}
void shutdown(){ if(q)al_destroy_event_queue(q);
if(timer)al_destroy_timer(timer); if(disp)al_destroy_
display(disp); }
};
[Link]
Game Development Projects 525
sfx["gather"]=al_load_sample("assets/[Link]");
sfx["build"]=al_load_sample("assets/[Link]");
sfx["attack"]=al_load_sample("assets/[Link]");
return true; // check nulls in real code
}
~Assets(){ for(auto&p:bmp) if([Link]) al_destroy_
bitmap([Link]);
for(auto&p:font) if([Link]) al_destroy_
font([Link]);
for(auto&p:sfx) if([Link]) al_destroy_
sample([Link]); }
};
[Link]
526 Practical Game Programming
}
}
};
struct Camera { float x=0,y=0; void follow(Vec p,int
sw,int sh,int tile){ x=p.x-
sw/2; y=p.y-
sh/2; if(x<0)
x=0;if(y<0)y=0; }
struct ResourceNode {
Vec pos; int amount=300; ResourceType
type=ResourceType::Wood;
ALLEGRO_BITMAP* sprite=nullptr;
};
struct Structure {
Vec pos; Team team; int hp=500; std::string kind; //
"HQ","Barracks","Depot"
ALLEGRO_BITMAP* sprite=nullptr;
};
struct Unit {
Vec pos, vel; Team team=Team::Player; int hp=100,
atk=8; float range=22;
UnitState st=UnitState::Idle; Vec goal;
int carry=0; int carryMax=50; ResourceType
carryType=ResourceType::Wood;
Structure* homeDepot=nullptr; ResourceNode*
targetNode=nullptr; Unit* targetEnemy=nullptr;
ALLEGRO_BITMAP* sprite=nullptr; bool selected=false;
};
struct World {
Map map; Camera cam;
std::vector<Unit> units;
std::vector<ResourceNode> nodes;
[Link]
Game Development Projects 527
std::vector<Structure> structures;
int wood=100, stone=0; // player resources
int e_wood=100; // enemy resources (toy)
bool gameOver=false;
};
struct Selection {
void boxSelect(World& w, Vec a, Vec b, float
camx,float camy){
float l=std::min(a.x,b.x)+camx,
r=std::max(a.x,b.x)+camx;
float t=std::min(a.y,b.y)+camy,
d=std::max(a.y,b.y)+camy;
for(auto& u:[Link]) if([Link]==Team::Player){
float ux=[Link].x, uy=[Link].y;
[Link] = (ux>=l && ux<=r && uy>=t &&
uy<=d);
[Link]
528 Practical Game Programming
}
}
void clear(World& w){ for(auto& u:[Link])
[Link]=false; }
};
[Link]
Game Development Projects 529
[Link]-
>amount—
= take; [Link] +=
take; [Link] = [Link]-
>type;
if ([Link] >= [Link])
{ [Link]=UnitState::Return; [Link] = [Link]?
[Link]-
>pos : [Link]; }
}
break;
case UnitState::Return:
if (len([Link]—
[Link]) > 18) break;
// deposit
if ([Link]==Team::Player){
if ([Link]==ResourceType::Wood)
[Link] += [Link]; else [Link] += [Link];
}
[Link]=0; [Link]=UnitState::Gather; // resume
node
break;
default: break;
}
}
};
[Link]
530 Practical Game Programming
[Link].push_back(s);
}
placing=false;
}
};
[Link]
Game Development Projects 531
[Link]
532 Practical Game Programming
// Right-
click command resolution: move / gather /
attack / build place
void issueRightClick(Vec worldPx){
// If clicking a resource → order gather for
selected workers
ResourceNode* rn=nullptr; for(auto& n:[Link])
if(len([Link]-
worldPx)<20) { rn=&n; break; }
Unit* enemy=nullptr; for(auto& u:[Link]) if(u.
team==Team::Enemy && len([Link]-
worldPx)<20) { enemy=&u;
break; }
// Selection drag
if (![Link] && [Link]){ /* no-
op */ }
if (![Link] && [Link]==false) { /*
release happened */ }
[Link]
Game Development Projects 533
void draw(){
al_clear_to_color(al_map_rgb(25,35,25));
[Link](A, [Link].x, [Link].y);
// Draw nodes
for(auto& n:[Link]) al_draw_filled_
circle([Link].x-[Link].x, [Link].y-[Link].y, 6,
al_map_rgb(60,160,60));
// Draw structures
for(auto& s:[Link]) al_draw_filled_
rectangle([Link].x-12-[Link].x, [Link].y-12-[Link].y, [Link].
x+12-[Link].x, [Link].y+12-[Link].y, [Link]==Team::Player?
al_map_rgb(70,120,255):al_map_rgb(220,70,70));
// Draw units
for(auto& u:[Link]){
ALLEGRO_COLOR c = ([Link]==Team::Player)?
al_map_rgb(120,200,255):al_map_rgb(255,120,120);
al_draw_filled_circle([Link].x-[Link].x,
[Link].y-
[Link].y, 6, c);
if([Link]) al_draw_circle([Link].x-W.
cam.x, [Link].y-
[Link].y, 9, al_map_rgb(255,255,0), 2);
}
// HUD
std::string hud = "Wood: "+std::to_string(W.
wood)+" Stone: "+std::to_string([Link]);
al_draw_text([Link]["ui"], al_map_
rgb(255,255,255), 10, 10, 0, hud.c_str());
al_flip_display();
}
[Link]
534 Practical Game Programming
if ([Link]==ALLEGRO_EVENT_MOUSE_BUTTON_DOWN
&& [Link]==1){
selecting=true; selStart={ (float)
[Link].x,(float)[Link].y };
}
if ([Link]==ALLEGRO_EVENT_MOUSE_BUTTON_UP &&
[Link]==1){
selecting=false;
[Link](W, selStart, [Link],
[Link].x, [Link].y);
}
if ([Link]==ALLEGRO_EVENT_MOUSE_BUTTON_UP &&
[Link]==2){
Vec worldPx = [Link] + Vec{[Link].x,
[Link].y};
issueRightClick(worldPx);
}
// keyboard quick build (B to start placing;
Enter to confirm)
if ([Link]==ALLEGRO_EVENT_KEY_DOWN){
if ([Link]==ALLEGRO_KEY_B){
[Link]=true; [Link]="Barracks"; }
if ([Link]==ALLEGRO_KEY_
ENTER){ [Link](W, A); }
}
if ([Link] && [Link]==ALLEGRO_EVENT_
MOUSE_AXES){
[Link] = [Link] + Vec{W.
cam.x, [Link].y};
[Link]
Game Development Projects 535
}
}
}
};
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
Exercises
1. Understanding Selection and Commands
Explain how box selection differs from single-click selection in an
RTS. Why is it important to support both, and how does modifier-key
selection (such as Shift-click) improve usability?
2. Coordinate Systems in RTS Games
Describe the difference between screen coordinates and world
coordinates. Why is accurate conversion between these coordinate
spaces essential for unit selection, movement commands, and
structure placement?
3. Finite-State Unit Behavior
Review the UnitState enumeration. For each state listed (Idle, Move,
Gather, Return, Attack, Build), briefly describe what conditions cause
a unit to enter or leave that state.
[Link]
536 Practical Game Programming
Homework Questions
1. RTS Complexity vs. Turn-Based Games
Compare the challenges of implementing an RTS with those of a turn-
based strategy game. Which systems become more complex in real
time, and why?
2. Pathfinding Trade-Offs
Discuss the limitations of the current “greedy steering” movement
approach. Under what conditions does this approach fail, and how
does grid-based A* pathfinding address those shortcomings?
3. User Interface and Cognitive Load
RTS games present a large amount of information to players. What UI
elements are most critical for preventing player confusion, and how
should information priority be determined?
4. AI Design Considerations
Why is it often preferable for enemy AI in strategy games to be
predictable but efficient rather than optimal? How does this affect
player enjoyment and perceived difficulty?
Programming Projects
1. Resource Gathering System (Core Project)
Complete and refine the resource-gathering logic so that units
correctly locate resources, gather them over time, return them to
deposits, and resume gathering if resources remain. Add visual
feedback for carrying resources.
2. Structure Construction System
Extend the building and placement system to support multiple
structure types. Implement construction progress indicators and
prevent overlapping or invalid placement more robustly.
3. Combat Mechanics Expansion
Enhance combat by adding armor or damage mitigation, ranged
versus melee attacks, and visual attack indicators. Ensure combat
resolves consistently for both player and enemy units.
4. Improved Enemy AI
Expand the enemy AI beyond basic harassment. Add decision-making
logic that allows the AI to choose between economic expansion,
defense, or aggression based on game state.
[Link]
Game Development Projects 537
Core Components
Timer: The core functionality of the application
Attributes: Total time, remaining time, state (stopped, running, paused)
Function: Counts down accurately based on elapsed time
Control Buttons: User interaction elements
Types: Start, Pause, Reset, Skip
Function: Control timer state transitions
Display: Visual output of timer state
Attributes: Position, font, colour
Function: Shows remaining time in MM:SS format
Session Tracking System: Light gamification element
Attributes: Completed sessions, total focus time
[Link]
538 Practical Game Programming
[Link]
Game Development Projects 539
8.11.2. Implementation
Key Operations in the Game
1. Event Intake (Mouse/Keyboard) and Intent Mapping
• Poll Allegro events each frame.
• Map input to actions: Start/Pause, Reset, Skip, Preset Selection,
Theme Toggle, Mute.
• Support keyboard shortcuts (e.g., space bar = toggle run/pause, R =
reset, M = mute).
2. Timebase Update (Drift-Resistant Countdown)
• Maintain sessionStartTime, pausedAccumulated, and lastTick.
• Per timer tick: elapsed = al_get_time() − sessionStartTime −
pausedAccumulated; remaining = totalTime − elapsed;
• When pausing, capture pauseStart; when resuming, add to
pausedAccumulated.
3. State Transitions
• Stopped → Running: Initialize timestamps. (Optional: Play start
cue.)
• Running → Paused: Freeze countdown by recording pauseStart.
• Paused → Running: Adjust pausedAccumulated.
• Running → Completed: When remaining ≤ 0, clamp to 0, set
Completed, trigger SFX/flash. (Optional: Advance to the next phase
[break/work] if Pomodoro mode is enabled.)
4. Button Hit-Testing and UI Feedback
• On mouse down, test cursor against button rectangles; set pressed
state.
[Link]
540 Practical Game Programming
• On mouse up, if still inside the same button, fire the action; update
hover/pressed visuals.
5. Time Formatting and Progress Visualization
• Convert seconds to MM:SS (zero-padded).
• Compute progress ratio p = (totalTime − remaining)/totalTime for a
bar or ring.
• Animate subtle easing on the progress indicator for polish.
6. Rendering Pass (per Frame)
• Clear background (theme colour).
• Draw time read-out, progress bar/ring, and buttons with current
states.
• Draw status text (Running/Paused/Break/Completed). (Optional:
Draw session counters.)
7. Audio/Notification Triggers
• On start, pause, resume, complete, and play appropriate SFX if
enabled.
• Optional: On complete, display an overlay or desktop
notification.
8. Persistence (Load/Save)
• On start-up, load preferences (durations, theme, audio), and stats
(sessions, total minutes).
• On completion/reset/settings change, save back to disk.
9. Edge-Case Handling
• If the app regains focus or the system clock jumps, recompute
remaining from absolute time.
• Ensure timer cannot underflow; clamp and transition once.
10. Optional: Pomodoro Cycle Manager
• Maintain a small state machine: Work → Break → Work → . . . →
Long Break after N cycles.
• Auto-start the next phase (configurable) or wait for a Start click.
[Link]
Game Development Projects 541
FOCUS_TIMER.CPP
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_primitives.h>
#include <iostream>
#include <cstdio>
struct Timer {
float totalTime;
float remainingTime;
bool running;
};
struct Button {
float x, y, width, height;
const char* label;
};
bool initAllegro() {
if (!al_init()) return false;
[Link]
542 Practical Game Programming
al_init_font_addon();
al_init_ttf_addon();
if (!al_install_keyboard()) return false;
if (!al_install_mouse()) return false;
if (!al_init_primitives_addon()) return false; //
needed for draw_filled_rectangle
return true;
}
// center label
int th = al_get_font_line_height(font);
float cx = button.x + [Link] / 2.0f;
float cy = button.y + [Link] / 2.0f—
th / 2.0f;
al_draw_text(font, al_map_rgb(0, 0, 0), cx, cy,
ALLEGRO_ALIGN_CENTER, [Link]);
}
[Link]
Game Development Projects 543
int main() {
if (!initAllegro()) {
std::cerr << "Failed to initialize Allegro.\n";
return—1;
}
[Link]
544 Practical Game Programming
al_destroy_display(display);
return—1;
}
al_register_event_source(queue,
al_get_display_event_source(display));
al_register_event_source(queue,
al_get_timer_event_source(ticker));
al_register_event_source(queue,
al_get_mouse_event_source());
al_start_timer(ticker);
if ([Link] == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if ([Link] == ALLEGRO_EVENT_TIMER) {
updateTimer(timer, 1.0f / 60.0f);
al_clear_to_color(al_map_rgb(255, 255, 255));
drawTimer(timer, font);
drawButton(startButton, font);
drawButton(pauseButton, font);
drawButton(resetButton, font);
al_flip_display();
} else if ([Link] == ALLEGRO_EVENT_MOUSE_BUTTON_
DOWN) {
if (isButtonClicked(startButton, [Link].x,
[Link].y)) {
[Link]
Game Development Projects 545
[Link] = true;
} else if (isButtonClicked(pauseButton,
[Link].x, [Link].y)) {
[Link] = false;
} else if (isButtonClicked(resetButton,
[Link].x, [Link].y)) {
[Link] = [Link];
[Link] = false;
}
}
}
if (font) al_destroy_font(font);
if (ticker) al_destroy_timer(ticker);
if (queue) al_destroy_event_queue(queue);
if (display) al_destroy_display(display);
return 0;
}
TA S K S F O R C O M P L E T I O N A N D I M P R O V E M E N T
1. Customizable Timer: Allow the user to set custom work and break
durations.
2. Sound Alerts: Add sound effects to notify the user when the timer ends.
3. Visual Themes: Add different visual themes to make the game more
appealing, load a background image, or modify the font and sizes used
for the buttons and time display.
4. Statistics: Track and display statistics such as total work time and
number of sessions completed.
Exercises
1. Understanding Timekeeping Accuracy
Explain why computing the remaining time using al_get_time() is
more reliable than decrementing a counter each frame. Describe a
[Link]
546 Practical Game Programming
Homework Questions
1. Design Robustness
Consider what happens if the application window loses focus or
the system temporarily freezes. How would you modify the current
implementation to ensure the timer remains accurate when the
application resumes?
2. State Machines in Utility Games
Compare the timer’s state machine to those used in traditional games
(such as menus or combat systems). What similarities and differences
do you observe?
3. Persistence and User Trust
Why is saving user preferences and statistics important in a utilities
game? What expectations might users have if this data were lost
between sessions?
Programming Projects
1. Customizable Timer Durations (Core Project)
Extend the timer so users can define custom work and break
durations. Add UI elements (buttons or keyboard shortcuts) to select
common presets (e.g., 15, 25, 45 minutes) and apply them at runtime.
2. Sound Alerts and Notifications
Integrate Allegro’s audio subsystem to play a sound when the timer
reaches zero. Add a mute toggle and allow users to control alert
volume. Optionally, implement a brief visual flash effect when a
session is completed.
3. Session Statistics and History Tracking
Track additional statistics such as total accumulated work time,
number of completed sessions, and daily streaks. Display these
statistics in a simple summary panel or overlay.
[Link]
Game Development Projects 547
[Link]
This page intentionally left blank
References and Resources
Official Resources
Allegro 5 Documentation
Allegro 5 Official Wiki ([Link] Comprehensive
documentation for functions, examples, and concepts.
Allegro 5 GitHub Repository ([Link] Source
code and issue tracking.
[Link] Forums ([Link] Active community for
troubleshooting and discussions.
Allegro 5 Keyboard API ([Link]
Allegro 5 documentation page for Keyboard API.
Books
Beginner-Friendly
Allegro 5 Game Programming by Jonathan S. Harbour: Step-by-step guide to
building games with Allegro 5.
Beginning C++ Game Programming by John Horton: While not Allegro-
specific, it teaches C++ concepts applicable to game devs.
Advanced
Game Programming Patterns by Robert Nystrom: Explores design patterns
applicable to Allegro-based projects.
[Link]
550 Practical Game Programming
YouTube Channels
Allegro 5 Tutorial Series by Sonar Systems ([Link]
?list=PLRtjMdoYXLf4od_bOKN3WjAPr7snPXzoe).
Making Games with Allegro 5 by VoidRealms ([Link]
playlist?list=PLvv0ScY6vfd9wBflF0f6ynlDQuaeKYzyc).
Complementary Libraries
Box2D ([Link] Physics engine integration.
OpenAL ([Link] Advanced audio (Allegro’s audio module
is sufficient for basics).
OpenGL ([Link] Advanced graphics (Allegro’s audio
module is sufficient for basics).
[Link]