Chapter 2.Essentials of Game Programming with Allegro
2.1. Common Structure of a C/C++ Program with Allegro 5
As a tradition and common practice, we will create a “Hello World!” program to introduce programming with Allegro 5 in C/C++.
This program will initialize Allegro, create a display window, and draw the text “Hello, World!” on the screen. This can be done in the following steps:
- 1. Set up Allegro 5 by including the Allegro 5 library so that the library will be installed and used when the program is compiled and linked. Please note that in order to be able to use Allegro 5, you must have set up your integrated development environment (IDE) to make the Allegro 5 library accessible as covered in section 1.5 of the textbook.
- 2. Initialize Allegro and its components such as display and font, as well as whatever you will use in the program.
- 3. Create the window to hold the content of the program by defining the dimensions of the window.
- 4. Load resources, such as fonts for displaying text.
- 5. Render the text by drawing the text “Hello, World!” on the screen and hold it for users to see.
- 6. Finish up by destroying the font and display to free up the resources used by the program.
The following is a sample of complete code:
// setup Allegro, its font and TTF addons and operations for IO
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <iostream>
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
// function to initialize Allegro.
// All user defined functions used in your program must be defined before they are used.
void initAllegro() {
if (!al_init()) { // initialize Allegro
std::cerr << "Failed to initialize Allegro!" << std::endl;
exit(1);
}
if (!al_init_font_addon()) { // initialize font addon
std::cerr << "Failed to initialize font addon!" << std::endl;
exit(1);
}
if (!al_init_ttf_addon()) { // initialize TTF addon
std::cerr << "Failed to initialize TTF addon!" << std::endl;
exit(1);
}
if (!al_install_keyboard()) { // install keyboard
std::cerr << "Failed to install keyboard!" << std::endl;
exit(1);
}
}
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
if (!display) { // always check if the window is successfully created.
std::cerr << "Failed to create display!" << std::endl;
return—1;
}
ALLEGRO_FONT* font = al_load_ttf_font("arial.ttf", 32, 0);
if (!font) {
std::cerr << "Failed to load font!" << std::endl;
al_destroy_display(display);
return—1;
}
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_rest(5.0); // Display the text for 5 seconds
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.
2.1.1. Header Files
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <iostream>
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:
std::cerr << "Failed to initialize Allegro!" << std::endl;
We will use #include <iostream> rather than #include <cstdio>, as seen previously, as it simplifies the code and provides the functions for both file access and console error displays.
2.1.2. User-Defined Global Constants and Variables
As in all computer programs, all names must be defined before they are used. So in this code example, two constants are to be used, hence they are defined right after the header files:
const int SCREEN_WIDTH = 800; // this is used to set the width of the screen
const int SCREEN_HEIGHT = 600; // this is used to set the height of the screen
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.
2.1.3. User-Defined Functions
When programming to solve a problem or to implement a computing system, the well-known divide-and-conquer approach is commonly used. The first portion of the code sets up the bits of Allegro that are necessary to display the window, show the “Hello World!” text, and then quit when the user presses a key. 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:
- • When using al_install_system() in place of al_init() to initialize the Allegro system. This function allows more control, such as specifying a custom atexit function.
- • When working with shared libraries. In this case, it’s generally advised not to call al_init() within the library itself. Instead, the application using the library should handle the initialization.
- • When using a preinitialized system. If another part of your application or another library has already initialized Allegro, calling al_init() again is unnecessary and could cause issues.
- • When using native dialogue functions provided by Allegro 5, such as:
al_show_native_message_box
al_install_keyboard();
al_install_keyboard() enables keyboard input. Believe it or not, accepting keyboard input to your program isn’t mandatory!
ALLEGRO_DISPLAY* disp = al_create_display(320, 200);
al_create_display() tells Allegro to create an 800 × 600 pixel window. You may change the numbers, then recompile to view the change in size.
2.1.4. The Main Function
For the simple task of displaying “Hello World!,” the main function of the program can be laid out as follows.
Initialize the display:
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
if (!display) { // always check if the window is successfully created.
std::cerr << "Failed to create display!" << std::endl;
return—1;
}
Load the required font for the text output:
ALLEGRO_FONT* font = al_load_ttf_font("arial.ttf", 32, 0);
if (!font) {
std::cerr << "Failed to load font!" << std::endl;
al_destroy_display(display);
return—1;
}
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));
Draw and display the message for five seconds:
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_rest(5.0); // Display the text for 5 seconds
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;
}
2.2. Handling User Input
The “Hello World!” example doesn’t take any input. In most programs, especially video games, however, handling user input is necessary.
2.2.1. Keyboard
Among the input devices, the keyboard is the most commonly used, especially 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.
Setup for Handling Keyboard Input
Firstly, include necessary headers and initialize the keyboard subsystem. For keyboard, only allegro5/allegro.h is needed. (The same is not true of mouse and joystick, as you will see later.) Here is the example code:
#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:
- Event Queue: An event queue is used to store events generated by various input devices (keyboard, mouse, joystick, etc.).
You create an event queue using al_create_event_queue() and register event sources (like the keyboard or display) with al_register_event_source().
- Event Types: Allegro defines various event types, such as ALLEGRO_EVENT_KEY_DOWN for key presses, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN for mouse clicks, and ALLEGRO_EVENT_DISPLAY_CLOSE for window-close events.
- Event Loop: The event loop continuously checks for events in the event queue using functions like al_get_next_event() or al_wait_for_event(). When an event is detected, the program processes it accordingly.
The following is a simple example demonstrating event-based input handling in Allegro 5. Note here we are not error checking the loading of our libraries, as you saw in some of the chapter 1 code examples. Here, keeping with the minimal concept of a demo program (it is a simple example after all . . .), we’ll exit the program on an error. You’ll see this in some of the other code examples in this text, as we’re not creating production code, if you will, but only quick examples for the student. As a matter of fact, in the example after this one, we won’t even have exit code to check for initialization; we’ll leave that as an exercise for the student to determine where the appropriate code would go. In production or release code, however, note that it’s always good practice to include error checking in your code and exit gracefully with a warning message or error display of some kind. Here is the example code:
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
struct Point { float x, y; };
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;
ALLEGRO_DISPLAY* display = al_create_display(800, 600);
if (!display) return—1;
ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
if (!queue) { al_destroy_display(display); return—1; }
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());
bool running = true;
bool has_click = false;
Point last_click = {0, 0};
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if (ev.type == ALLEGRO_EVENT_KEY_DOWN) {
if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) running = false;
} else if (ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) {
if (ev.mouse.button == 1) { // left mouse button
last_click = { (float)ev.mouse.x, (float)ev.mouse.y };
has_click = true;
}
}
// Draw the frame, draw a red circle where the mouse is clicked
al_clear_to_color(al_map_rgb(0, 0, 0));
if (has_click) {
al_draw_filled_circle(last_click.x, last_click.y, 20.0f, al_map_rgb(255, 0, 0));
}
al_flip_display();
}
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.
Event-based input handling is efficient and allows your program to respond to user actions in real time.
State-Based Input (Held Keys)
In Allegro 5, state-based input refers to a method of handling input where the current state of input devices (like the keyboard or mouse) is captured and stored at a specific point in time. This allows you to check the status of keys or buttons at any moment rather than relying on event-driven input alone.
The following code checks if a key is currently held down (e.g., for movement)—here, the left and right arrow keys—and then will move a red square left or right accordingly:
ALLEGRO_KEYBOARD_STATE key_state;
al_get_keyboard_state(&key_state);
// Move player if arrow keys are held
if (al_key_down(&key_state, ALLEGRO_KEY_RIGHT)) {
player_x += 5;
}
if (al_key_down(&key_state, ALLEGRO_KEY_LEFT)) {
player_x—= 5;
}
'''
Key | Allegro Constant |
|---|---|
Arrow Keys | ALLEGRO_KEY_LEFT ALLEGRO_KEY_RIGHT ALLEGRO_KEY_UP ALLEGRO_KEY_DOWN |
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 |
Complete Coding Example
#include <allegro5/allegro.h>
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());
bool game_running = true;
float x = 400, y = 300;
while (game_running) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
// Event-based exit (ESC)
if (event.type == ALLEGRO_EVENT_KEY_DOWN) {
if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
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;
}
Tips for Handling User Input
- • Use event-based input for single actions (e.g., menus, jumping).
- • Use state-based input for continuous movement.
- • Always call al_get_keyboard_state() before checking key states.
- • Clean up resources with al_uninstall_keyboard() at exit.
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 window. 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>
const int SCREEN_W = 800;
const int SCREEN_H = 600;
const int BOX_PADDING = 20;
const int BALL_RADIUS = 15;
int main() {
// Initialize Allegro and components
al_init();
al_init_font_addon();
al_init_ttf_addon();
al_init_primitives_addon();
al_install_keyboard();
// Create display and set up
ALLEGRO_DISPLAY* display = al_create_display(SCREEN_W, SCREEN_H);
ALLEGRO_TIMER* timer = al_create_timer(1.0 / 60);
ALLEGRO_EVENT_QUEUE* event_queue = al_create_event_queue();
ALLEGRO_FONT* font = al_create_builtin_font();
// Register event sources
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());
// 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) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
switch (event.type) {
case ALLEGRO_EVENT_TIMER:
// Update ball position
ball_x += dx;
ball_y += dy;
// Collision with container box
if (ball_x—BALL_RADIUS < BOX_PADDING ||
ball_x + BALL_RADIUS > SCREEN_W—BOX_PADDING) {
dx =—dx;
}
if (ball_y—BALL_RADIUS < BOX_PADDING ||
ball_y + BALL_RADIUS > SCREEN_H—BOX_PADDING) {
dy =—dy;
}
redraw = true;
break;
case ALLEGRO_EVENT_KEY_DOWN:
if (event.keyboard.keycode == ALLEGRO_KEY_Q) {
done = true;
}
break;
case ALLEGRO_EVENT_DISPLAY_CLOSE:
done = true;
break;
}
if (redraw && al_is_event_queue_empty(event_queue)) {
redraw = false;
// Drawing
al_clear_to_color(al_map_rgb(0, 0, 0));
// Draw container box
al_draw_rectangle(BOX_PADDING, BOX_PADDING,
SCREEN_W—BOX_PADDING, SCREEN_H—BOX_PADDING,
al_map_rgb(255, 255, 255), 2);
// Draw ball
al_draw_filled_circle(ball_x, ball_y, BALL_RADIUS,
al_map_rgb(255, 0, 0));
// Display position text
al_draw_textf(font, al_map_rgb(255, 255, 255),
SCREEN_W—120, 10, ALLEGRO_ALIGN_LEFT,
"X: %.1f Y: %.1f", ball_x, ball_y);
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:
g++ -o bouncing_ball bouncing_ball.cpp -lallegro -lallegro_font -lallegro_ttf -lallegro_primitives
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 control across various game genres. Here’s a closer look at their role and features.
Types of Gaming Mice
- Standard Gaming Mice: These are versatile and suitable for a wide range of games, from first-person shooter (FPS) to real-time strategy (RTS) games.
- MMO Mice: Designed for massively multiplayer online games, these mice often have multiple programmable buttons to handle complex commands.
- FPS Mice: These are optimized for first-person shooters, featuring high-DPI (dots per inch) settings for precise aiming and quick movements.
- Ambidextrous Mice: Suitable for both left- and right-handed users, these mice offer symmetrical designs.
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.
Popular Uses in Games
- First-Person Shooter (FPS): Precision and quick response times are critical, making high-DPI and low-latency mice ideal.
- Real-Time Strategy (RTS): Accurate clicking and multiple programmable buttons help manage complex commands and unit control.
- Massively Multiplayer Online (MMO): Extra buttons and macros are useful for managing numerous abilities and commands.
Top Gaming Mice in 2024
- Logitech G Pro X Superlight: Known for its lightweight design and high performance, it’s a favorite among professional gamers.
- Razer DeathAdder V3 Pro: Offers excellent ergonomics and precision, making it ideal for long gaming sessions.
- SteelSeries Rival 600: Features dual sensors for enhanced accuracy and customizable weights for a personalized feel.
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:
- 1. Moving the mouse pointer and clicking around as you normally would.
- 2. Using the speed of the mouse to control something in the game. This allows you to use the mouse a bit like a joystick; most PC-based first-person shooters do this to allow the player to speedily aim.
Steps to Use Mouse in Allegro Games
To use the mouse in Allegro games, we will need to follow a few steps to initialize and handle mouse input. Here’s a basic guide to get you started:
- 1. Initialize the Mouse
First, you need to install the mouse handler using al_install_mouse(). This function sets up the mouse for use in your game:
if (!al_install_mouse()) {
fprintf(stderr, "Failed to initialize the mouse!\n");
return—;
}
- 2. Check Mouse State
You can retrieve the current state of the mouse using al_get_mouse_state(). This function fills an ALLEGRO_MOUSE_STATE structure with the current mouse position and button states:
ALLEGRO_MOUSE_STATE state;
al_get_mouse_state(&state);
- 3. Handle Mouse Input
You can check the position and button states from the ALLEGRO_MOUSE_STATE structure. For example, to get the mouse coordinates and check if the left button is pressed, use this:
int mouse_x = state.x;
int mouse_y = state.y;
if (al_mouse_button_down(&state, )) {
// Left mouse button is pressed
}
- 4. Display Mouse Cursor
To show the mouse cursor on the screen, use al_show_mouse_cursor(display), where display is your Allegro display:
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.
Key Features of Joysticks
- Buttons: Joysticks typically have multiple buttons that can be programmed for different functions in a game.
- Hat Switch: A small joystick on top of the main stick used for looking around in a game.
- Throttle Control: Found in HOTAS setups, it allows for precise control of speed in flight simulators.
Popular Uses in Games
- Flight Simulators: Joysticks provide the most realistic control for flying aircraft in games like Microsoft Flight Simulator.
- Space Simulators: Games like Elite Dangerous and Star Citizen benefit from the precise control offered by joysticks.
- Arcade Games: Classic arcade games often use digital joysticks for their simple and responsive controls.
Best Joysticks for Video Games in 2024
- Thrustmaster HOTAS Warthog: Known for its build quality and precision, it’s a favorite among flight sim enthusiasts.
- Logitech G X56 HOTAS RGB: Offers a good balance between features and price, suitable for both beginners and experienced users.
- Thrustmaster T.Flight HOTAS X: A budget-friendly option that still provides a solid experience.
Joysticks enhance the gaming experience by providing more intuitive and precise control, especially in games that simulate real-world activities.
Steps to Use a Joystick in Your Allegro 5 Games
To use a joystick in your Allegro 5 games requires the following steps:
- 1. Initialize the Joystick
- • Include the Allegro joystick header—#include <allegro5/allegro.h>
- • Install the joystick driver using al_install_joystick().
- • Check if the joystick is installed with al_is_joystick_installed().
- 2. Get Joystick Information
- • Use al_get_num_joysticks() to find out how many joysticks are connected.
- • Retrieve a joystick object with al_get_joystick().
- 3. Read Joystick State
- • Create an ALLEGRO_JOYSTICK_STATE object.
- • Use al_get_joystick_state() to update the state of the joystick.
- 4. Handle Joystick Events
- • Register the joystick event source with al_get_joystick_event_source().
- • Use an event queue to handle joystick events like button presses and axis movements.
Here’s a simple example to get you started:
#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 *joystick = al_get_joystick(0);
if (!joystick) {
printf("No joystick found!\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));
// Example of reading joystick state
while (true) {
al_get_joystick_state(joystick, &state);
if (state.button[0]) {
printf("Button 0 pressed!\n");
}
}
al_uninstall_joystick();
return 0;
}
2.3. Collision Detection
Collision detection is a crucial aspect of video games, ensuring that objects within the game world interact realistically. Collision detection is the computational process of determining when two or more objects in a game intersect or come into contact. This is essential for creating realistic interactions, such as a character bumping into a wall or a projectile hitting a target.
Collision detection is a cornerstone of action game development, ensuring that game objects interact logically within the game world. It’s involved in constraining a player’s actions in some cases (that wall you can’t go through or objects you find that you can interact with); other times, it triggers cause and effect (the missile hits my ship and I go boom). From a programming perspective, it is the code that determines whether two or more objects occupy the same space at a given time, triggering responses like bouncing, damage, or other interactions. Accurate collision detection contributes to gameplay realism and is critical for engaging user experiences.
2.3.1. Types of Collision Detection
Collision detection methods can range from simple bounding boxes—an area around your sprite, like its rectangular cell borders or a circle emanating from the sprite’s center—to more advanced algorithms for complex shapes, in which case edges will be the key components for collision detection.
- 1. Axis-Aligned Bounding Box (AABB)
This method involves checking if the bounding boxes of two objects overlap. It’s simple and efficient, especially for rectangular objects that are not rotated.
bool check_collision(AABB a, AABB b) {
return (a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y);
}
This method uses rectangles or squares to approximate an object’s boundaries, checking for overlap between them. This check for overlap in its most basic form is for a complete overlap—that is, the whole object—but can be modified to check for a single side only.
In figure 2.1, two cubes, labelled “Object A” and “Object B,” overlap. The area of overlap is red, and an arrow points to it labelled “Collision Detected.” This approach is computationally simple but can lead to false positives for irregular shapes. The context here is an area defined by the sprites’ cell size. Take, for example, the flying saucer in figure 2.2. The red area indicates the bitmap image size (we deal with a lot of square and rectangular images by default in computer graphics/photos). The bounding box for that bitmap then would include all the area indicated by the green outline in the image. This would cause inconsistencies in collision or contacts: a laser beam, for example, could go right over the top dome of the saucer in the red area, but the collision detection (the hit) would be as soon as it contacted the red area surrounding the saucer.
Figure 2.1: Collision detection using bounding boxes. Illustrated by Kyle Flemmer.
- 2. Circle Collision
For circular objects, collision detection can be done by checking the distance between their centers. If the distance is less than the sum of their radii, a collision has occurred.
bool check_circle_collision(Circle a, Circle b) {
float dx = a.x—b.x;
float dy = a.y—b.y;
float distance = sqrt(dx * dx + dy * dy);
return distance < (a.radius + b.radius);
}
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.
Figure 2.2: Saucer image with a green bounding box. Illustrated by Walter Ridgewell.
- 3. Separating Axis Theorem (SAT)
This method is used for detecting collisions between convex polygons. It involves projecting the vertices of the polygons onto various axes and checking for overlaps. It’s more complex but very powerful.
2.3.2. Phases of Collision Detection
- 1. Broad Phase
The broad phase quickly eliminates pairs of objects that cannot possibly collide. Techniques like spatial partitioning (e.g., grids, quadtrees) or bounding volume hierarchies are used to reduce the number of collision checks.
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.
2.3.3. Implementing Collision Detection in Allegro
Here’s a simple example of implementing AABB collision detection in an Allegro game:
// Initialize Allegro and Create Objects
al_init();
al_init_image_addon();
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
if (!display) {
fprintf(stderr, "Failed to create display!\n");
return—;
}
ALLEGRO_BITMAP *sprite = al_load_bitmap("sprite.png");
ALLEGRO_BITMAP *sprite2 = al_load_bitmap("sprite2.png");
// Define AABB Structure and Collision Function
typedef struct {
float x, y, width, height;
} AABB;
bool check_collision(AABB a, AABB b) {
return (a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y);
}
// Game Loop with Collision Detection
AABB box = {00, 00, al_get_bitmap_width(sprite), al_get_bitmap_height(sprite)};
AABB box2 = {200, 200, al_get_bitmap_width(sprite2), al_get_bitmap_height(sprite2)};
while (true) {
// Update positions (example)
box.x += .0;
box2.y—= .0;
// Check for collision
if (check_collision(box, box2)) {
printf("Collision detected!\n");
}
// Render
al_clear_to_color(al_map_rgb(0, 0, 0));
al_draw_bitmap(sprite, box.x, box.y, 0);
al_draw_bitmap(sprite2, box2.x, box2.y, 0);
al_flip_display();
al_rest(0.06); // Approximately 60 FPS
}
Since many games will need to utilize collision in some way, let’s look at another example.
The CollisionTest Program
The CollisionTest program demonstrates basic collision detection principles by simulating interactions between two game objects. The setup involves:
- Sprites: These are visual representations of objects; we will use simple squares.
- Movement and Interaction: The program detects collisions as sprites move, triggering predefined responses such as bouncing or stopping.
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:
- • Check for collisions in every frame.
- • Perform a collision response—that is, execute an appropriate reaction, such as reversing direction or displaying an effect. For our demonstration program, we will simply change the direction of the sprite objects after they collide (here a complete overlap) and print a message on the screen.
The primary element of code we need to examine here is of course the collision detection function. We begin with the Sprite struct, which stores the following information elements of the Sprite object (see table 2):
typedef struct Sprite {
float x, y, width, height;
float dx, dy; // velocity
} Sprite
Using these variables, we can create a function to examine the location of two sprites to see if their edges are touching or overlapping. Imagine in our 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.
Figure 2.5: Collision response with a print message. Image by Walter Ridgewell.
Variable | Purpose |
|---|---|
x, y | The position of the sprite on the screen (top-left corner). |
width, height | The size (dimensions) of the sprite. |
dx, dy | The velocity (change in position per frame). |
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.
Explaining the check_collision Function
We talked previously about what a bounding box collision was, and since our simple example is using basic shapes (squares), we will implement one here. Our check collision function looks like this:
Property | Value |
|---|---|
x | 100 |
y | 150 |
width | 50 |
height | 50 |
dx | 2 |
dy | 3 |
bool check_collision(Sprite *a, Sprite *b) {
return (a->x < b->x + b->width &&
a->x + a->width > b->x &&
a->y < b->y + b->height &&
a->y + a->height > b->y);
}
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 condition. 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.
Understanding the Conditions
The function checks if the bounding boxes of two sprites overlap by looking at the edges of the two objects:
- 1. a->x < b->x + b->width
Ensures Sprite A’s left edge is to the left of Sprite B’s right edge.
- 2. a->x + a->width > b->x
Ensures Sprite A’s right edge is to the right of Sprite B’s left edge.
- 3. a->y < b->y + b->height
Ensures Sprite A’s top edge is above Sprite B’s bottom edge.
- 4. a->y + a->height > b->y
Ensures Sprite A’s bottom edge is below Sprite B’s top edge.
2.3.4. Graphical Explanation Overlap Collision
Now at this point we would have, status-wise,
a->x < b->x + b->width → True
a->x + a->width > b->x → False
a->y < b->y + b->height → True
a->y + a->height > b->y → False
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
a->x < b->x + b->width → True
a->x + a->width > b->x → True
a->y < b->y + b->height → True
a->y + a->height > b->y → True
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.
Figure 2.7: Sprites are overlapping: Collision is detected. Illustrated by Kyle Flemmer.
2.3.5. Graphical Explanation Edge Collision
A developer may want an edge overlap detected instead of a complete overlap. To do so, the function can be modified like so:
bool check_collision_edge(Sprite *a, Sprite *b) {
bool left_edge = (a->x + a->width == b->x);
bool right_edge = (a->x == b->x + b->width);
bool top_edge = (a->y + a->height == b->y);
bool bottom_edge = (a->y == b->y + b->height);
return (left_edge || right_edge || top_edge || bottom_edge);
}
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:
- left_edge: Right edge of Sprite A touches left edge of Sprite B.
- right_edge: Left edge of Sprite A touches right edge of Sprite B.
- top_edge: Bottom edge of Sprite A touches top edge of Sprite B.
- bottom_edge: Top edge of Sprite A touches bottom edge of Sprite B.
To begin with, we would have the following, status-wise:
Figure 2.8: Sprites positioned apart, no collision detected. Illustrated by Kyle Flemmer.
a->x + a->width == b->x → False
a->x == b->x + b-> width → False
a->y + a->height == b->y → False
a->y == b->y + b->height → False
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
a->x < b->x + b->width → False
a->x + a->width > b->x → False
a->y < b->y + b->height → True
a->y + a->height > b->y → False
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 bounding 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>
typedef struct Sprite {
float x, y, width, height;
float dx, dy; // velocity
} Sprite;
bool check_collision(Sprite* a, Sprite* b) {
return (a->x < b->x + b->width &&
a->x + a->width > b->x &&
a->y < b->y + b->height &&
a->y + a->height > b->y);
}
int main() {
al_init();
al_init_primitives_addon();
al_init_font_addon();
al_init_ttf_addon();
ALLEGRO_DISPLAY* display = al_create_display(800, 600);
ALLEGRO_EVENT_QUEUE* event_queue = al_create_event_queue();
ALLEGRO_TIMER* timer = al_create_timer(1.0 / 60.0);
ALLEGRO_FONT* font = al_load_ttf_font("arial.ttf", 36, 0);
if (!font) {
fprintf(stderr, "Could not load 'arial.ttf'. Make sure the font file is available.\n");
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));
Sprite sprite1 = { 100, 100, 50, 50, 2, 2 };
Sprite sprite2 = { 300, 300, 50, 50,—2,—2 };
bool collided = false;
bool was_colliding = false;
bool redraw = true;
al_start_timer(timer);
while (true) {
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
if (event.type == ALLEGRO_EVENT_TIMER) {
// Update sprite positions
sprite1.x += sprite1.dx;
sprite1.y += sprite1.dy;
sprite2.x += sprite2.dx;
sprite2.y += sprite2.dy;
// Collision detection
bool currently_colliding = check_collision(&sprite1, &sprite2);
if (currently_colliding && !was_colliding) {
// Collision just started—reverse directions once
sprite1.dx =—sprite1.dx;
sprite1.dy =—sprite1.dy;
sprite2.dx =—sprite2.dx;
sprite2.dy =—sprite2.dy;
printf("We Collided\n");
}
collided = currently_colliding;
was_colliding = currently_colliding;
// Bounce off screen edges
if (sprite1.x < 0 || sprite1.x + sprite1.width > 800) sprite1.dx =—sprite1.dx;
if (sprite1.y < 0 || sprite1.y + sprite1.height > 600) sprite1.dy =—sprite1.dy;
if (sprite2.x < 0 || sprite2.x + sprite2.width > 800) sprite2.dx =—sprite2.dx;
if (sprite2.y < 0 || sprite2.y + sprite2.height > 600) sprite2.dy =—sprite2.dy;
redraw = true;
}
else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
break;
}
if (redraw && al_is_event_queue_empty(event_queue)) {
redraw = false;
al_clear_to_color(al_map_rgb(0, 0, 0));
// Change color to yellow if collision occurs
ALLEGRO_COLOR color1 = collided ? al_map_rgb(255, 255, 0) : al_map_rgb(255, 0, 0);
ALLEGRO_COLOR color2 = collided ? al_map_rgb(255, 255, 0) : al_map_rgb(0, 255, 0);
al_draw_filled_rectangle(sprite1.x, sprite1.y,
sprite1.x + sprite1.width, sprite1.y + sprite1.height, color1);
al_draw_filled_rectangle(sprite2.x, sprite2.y,
sprite2.x + sprite2.width, sprite2.y + sprite2.height, 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 environments. By using techniques like AABB, circle collision, and SAT, you can handle various collision scenarios in your games.
Collision detection can be a very complex issue. But by reducing it to component parts, we can make calculating collisions much easier. Simple overlap checks may miss certain state information but can often be “good enough” to get the job done (given small delta time between collision checks—otherwise, objects can easily pass through one another). They are simple and effective in most cases.
An improvement is intercept-based collision detection, where an actual intercept time is calculated. These intercept times can be stored in a table, and then the only time you need to update the table is when time passes or an object changes direction. This means the more collisions there are, the more calculation will be needed. But the end result is that objects behave correctly under all circumstances, and you can optimize the checking routine to only run when an object changes direction.
2.3.6. Useful Mathematical Equations for Collision Detection
Collision detection in video games involves various mathematical equations to determine if and when objects intersect. Here are some common methods and their equations:
Axis-Aligned Bounding Box (AABB)
For two axis-aligned bounding boxes, the collision detection can be determined by checking if their edges overlap. The equations are:
{Collision} = (A.x < B.x + B.width) and (A.x + A.width > B.x) and (A.y < B.y + B.height) and (A.y + A.height > B.y)
Where ( A ) and ( B ) are the two bounding boxes with properties ( x ), ( y ), ( width ), and ( height ).
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} < (A.radius + B.radius)
Where ( A ) and ( B ) are the circles with properties ( x ), ( y ), and ( radius ).
Separating Axis Theorem (SAT)
The Separating Axis Theorem is used for detecting collisions between convex polygons. It involves projecting the vertices of the polygons onto various axes and checking for overlaps. The key idea is that if there is a separating axis where the projections do not overlap, then the polygons do not collide.
- 1. Projection of a Point onto an Axis
Given a point P and an axis A, the projection P’ is
- 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.
Line Segment vs. Triangle
To check if a line segment intersects a triangle, you can use the following steps:
- 1. Compute Signed Distances
Compute the signed distances of the segment endpoints to the plane of the triangle:
d = P − V0 ∙ N
d2 = P2 − V0 ∙ N
Where P and P2 are the segment endpoints, V0 is a vertex of the triangle, and N is the normal of the triangle.
- 2. Intersection Point
If the signs of d and d2 are different, compute the intersection point P:
- 3. Point Inside Triangle
Check if the intersection point P is inside the triangle using barycentric coordinates or edge tests.
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.
2.4. Adding Sound Effects to Games
Sound effects play crucial roles in video games by enhancing the overall experience and creating an engaging environment. Sound and music are integral to the gaming experience, enhancing immersion, providing feedback, and supporting the narrative.
2.4.1. Sound Effects and Music
Sound effects include various sounds found in the real world as well as music.
Importance of Sound Effects
- 1. Emotional Impact
Music sets the emotional tone of the game, influencing the player’s mood and reactions. For example, a tense soundtrack can heighten the sense of danger, while a calm melody can create a peaceful atmosphere.
- 2. Immersion
Sound effects and ambient sounds help create a believable game world. The sound of footsteps, rustling leaves, or distant explosions can make the game environment feel more real.
- 3. Feedback
Audio cues provide feedback to the player, indicating actions or events. For instance, a sound effect might signal that a player has successfully completed a task or that an enemy is nearby.
- 4. Narrative Enhancement
Music and sound effects can enhance the storytelling aspect of games. They can underscore dramatic moments, highlight important events, and support the narrative flow.
Components of Game Audio
- 1. Music
Composed specifically for the game, music can vary depending on the game’s genre and setting. Dynamic music systems can change the music based on the player’s actions or the game’s state.
- 2. Sound Effects (SFX)
These include all the sounds made by characters, environments, and actions within the game. High-quality sound effects can significantly enhance the realism and immersion of the game.
- 3. Voice Acting
Voice acting brings characters to life, adding depth to the narrative and making interactions more engaging. Good voice acting can greatly enhance the player’s connection to the story and characters.
- 4. Ambient Sounds
Background sounds that create the atmosphere of the game world. These can include environmental sounds like wind, water, and wildlife, which help to set the scene and make the world feel alive.
2.4.2. Implementing Sound and Music in Games
- 1. Audio Middleware
Tools like FMOD and Wwise are commonly used to implement and manage game audio. They allow developers to create complex audio behaviors and integrate them seamlessly into the game.
- 2. Dynamic Audio
Dynamic audio systems adjust the music and sound effects in real time based on the player’s actions and the game state. This creates a more responsive and immersive audio experience.
- 3. Spatial Audio
Techniques like 3D audio and binaural sound can create a sense of space and directionality, making it easier for players to locate sounds in the game world.
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);
- 2. Load Audio Files
Load your audio files into ALLEGRO_SAMPLE objects. Allegro supports various formats like WAV, OGG, and FLAC:
ALLEGRO_SAMPLE *sfx = al_load_sample("sound.wav");
if (!sfx) {
fprintf(stderr, "Couldn't load sound.wav\n");
return—1;
}
- 3. Play Sound Effects
To play a sound effect, use al_play_sample. Note that sound effects are often used in a “one-shot” type of occurrence with an event—that is, they don’t repeat, and as such, we use a single play mode: ALLEGRO_PLAYMODE_ONCE.
// Play sound effect
al_play_sample(sfx, 1.0, 0.0, 1.0, ALLEGRO_PLAYMODE_ONCE, nullptr);
- 4. Load and Play Music
For background music, unlike the one-shot sound effect, we will use the looping function, as background music tends to play continuously. We specify this mode with ALLEGRO_PLAYMODE_LOOP.
ALLEGRO_AUDIO_STREAM *music = al_load_audio_stream("music.ogg", 4, 2048);
if (!music) {
fprintf(stderr, "Couldn't load music.ogg\n");
return—1;
}
al_set_audio_stream_playmode(music, ALLEGRO_PLAYMODE_LOOP);
al_attach_audio_stream_to_mixer(music, al_get_default_mixer());
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 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);
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
if (!display) {
fprintf(stderr, "Display creation failed\n");
return—1;
}
ALLEGRO_SAMPLE *sfx = al_load_sample("sound.wav");
if (!sfx) {
fprintf(stderr, "Couldn't load sound.wav\n");
return—1;
}
ALLEGRO_AUDIO_STREAM *music = al_load_audio_stream("music.ogg", 4, 2048);
if (!music) {
fprintf(stderr, "Couldn't load music.ogg\n");
return—1;
}
al_set_audio_stream_playmode(music, ALLEGRO_PLAYMODE_LOOP);
al_attach_audio_stream_to_mixer(music, al_get_default_mixer());
// Play sound effect
al_play_sample(sfx, 1.0, 0.0, 1.0, ALLEGRO_PLAYMODE_ONCE, nullptr);
// Set up 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));
bool running = true;
while (running) {
ALLEGRO_EVENT ev;
if (al_wait_for_event_timed(queue, &ev, 0.1)) {
if (ev.type == ALLEGRO_EVENT_KEY_DOWN && ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
running = false;
}
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
}
}
// Could redraw or update something here
}
// 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.
Live Streaming Gameplay
Live streaming gameplay involves broadcasting your gaming sessions to an audience in real time. This has become incredibly popular on platforms like Twitch, YouTube Gaming, and Facebook Gaming. Here’s how you can get started:
- 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.
Game Streaming Services
Game streaming services allow you to play games on various devices without needing powerful hardware. The game runs on a remote server or on your gaming PC, and the video is streamed to your device. Here are some popular services:
- 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.
Getting Started with Game Streaming Services
- Choose a Service: Select a service that supports the games you want to play and is available in your region.
- Set Up Your Account: Create an account and subscribe to the service if necessary.
- Install the App: Download and install the app on your device.
- Connect a Controller: Many services support Bluetooth controllers for a better gaming experience.
- Start Playing: Launch the app, log in, and start streaming your games.
Streaming in video games, whether live streaming your gameplay or using game streaming services, offers a flexible and engaging way to enjoy and share gaming experiences.
Media Streaming in Allegro Games
With Allegro, we have the ability to stream audio or video content within your game. Audio streaming capabilities are one of Allegro’s add-on functions for video, though one will need to utilize an external library. Here’s how you would handle both.
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 the entire file into memory. We saw the use of this in the previous example code, but the steps here, once more, are:
- 1. Initialize the Required Audio Add-Ons
al_install_audio();
al_init_acodec_addon();
al_reserve_samples(8);
- 2. Load and Play Audio Stream
ALLEGRO_AUDIO_STREAM *stream = al_load_audio_stream("music.ogg", 4, 2048);
if (!stream) {
fprintf(stderr, "Failed to load audio stream!\n");
return—1;
}
al_set_audio_stream_playmode(stream, ALLEGRO_PLAYMODE_LOOP);
al_attach_audio_stream_to_mixer(stream, al_get_default_mixer());
Controlling the Stream
When one is utilizing larger files, it might be useful to have some control over how those are played back. Allegro offers control over audio playback functions with al_set_audio_stream_playing, which is used to stop or start the current stream. A bool value of true starts or resumes a stream, while false stops or pauses it.
For example, this starts the stream:
bool playing = true; // start playing immediately
al_set_audio_stream_playing(stream, true);
And then one might control the playback like this:
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if (ev.type == ALLEGRO_EVENT_KEY_DOWN) {
if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
running = false;
} else if (ev.keyboard.keycode == ALLEGRO_KEY_SPACE) {
playing = !playing;
al_set_audio_stream_playing(stream, playing);
draw_ui(display, font, playing);
}
} else if (ev.type == 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:
ALLEGRO_BITMAP *frame_bitmap = al_create_bitmap(width, height);
while (av_read_frame(format_context, &packet) >= 0) {
// Decode packet and convert to ALLEGRO_BITMAP
al_draw_bitmap(frame_bitmap, 0, 0, 0);
al_flip_display();
}
Why bother streaming? In our previous example, when we called al_load_sample, we loaded sound.wav into memory in its entirety and kept it there. Most game sound effects aren’t too long—probably a few seconds at most—so, provided you don’t have too many, it’s often more than reasonable to load all of them into memory when your program starts, and leave them there until it quits.
With this in mind, how do we handle longer pieces—specifically music? Your average 3:30 pop track, when encoded as a 320 Kbps MP3, weighs in at 8.4 MB. On today’s computers, where even the cheapest laptop can bring 4GB RAM to the table, keeping this one track in memory wouldn’t be too bad. Right?
Well, even if your game did only need to play this one piece of music, we haven’t considered how much space it’ll occupy once it’s decoded. So what does this mean for our program?
- • Most audio formats—MP3 included—are heavily compressed to take up less storage space.
- • When playing back an MP3, it needs to be decoded—temporarily removing the compression.
- • So for snappy playback, files read into ALLEGRO_SAMPLEs are thus stored decoded and uncompressed in memory. If they weren’t, we’d need to decode them every time we wanted to play them, which would be slow.
- • If you’ve ever converted an MP3 to WAV, you’ll probably have noted the crazy increase in size; this is because WAVs are generally uncompressed, unlike MP3s.
- • We can therefore assume that the size of the decoded, uncompressed audio data, as stored in memory, is also going to be big.
So when loading your 3:30 track into an ALLEGRO_SAMPLE, Allegro will actually be allocating something more like 37 MB of RAM. Scale this up to multiple tracks, and you’re easily into the hundreds of megabytes; safe to say that things aren’t looking so fresh now.
If only there was an easy way to seamlessly load a bit of the music at a time, decode it, play it, and then move on to the next bit. That way, we’d only have to store a small part of the track in memory at any given time! Great? Great. Streaming audio lets you do this.
Example of Audio Streaming with Pause Function
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_font.h>
#include <stdio.h>
#include <stdbool.h>
static void draw_ui(ALLEGRO_DISPLAY *display, ALLEGRO_FONT *font, bool playin
// Clear screen
al_clear_to_color(al_map_rgb(20, 20, 24));
// 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);
al_draw_text(font, col, w/2, h/2—al_get_font_line_height(font)/2, ALLEG
al_flip_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);
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
if (!display) { fprintf(stderr, "Failed to create display!\n"); return—1
// 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
// Load audio stream
ALLEGRO_AUDIO_STREAM *stream = al_load_audio_stream("music.ogg", 4, 2048)
if (!stream) {
fprintf(stderr, "Failed to load audio stream!\n");
al_destroy_font(font);
al_destroy_display(display);
return—1;
}
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));
bool running = true;
bool playing = true; // start playing immediately
al_set_audio_stream_playing(stream, true);
draw_ui(display, font, playing); // initial draw
while (running) {
ALLEGRO_EVENT ev;
al_wait_for_event(queue, &ev);
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if (ev.type == ALLEGRO_EVENT_KEY_DOWN) {
if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
running = false;
} else if (ev.keyboard.keycode == ALLEGRO_KEY_SPACE) {
playing = !playing;
al_set_audio_stream_playing(stream, playing);
draw_ui(display, font, playing);
}
} else if (ev.type == ALLEGRO_EVENT_DISPLAY_EXPOSE) {
// Redraw if the window is exposed
draw_ui(display, font, playing);
}
}
// Cleanup
al_destroy_audio_stream(stream);
al_destroy_font(font);
al_destroy_event_queue(queue);
al_destroy_display(display);
al_uninstall_audio();
return 0;
}
2.5. Timers and Game Timing
Timers and timing are crucial elements in games, affecting gameplay, performance, and the overall player experience.
2.5.1. Roles of Timers and Timing in Games
Timers and timing mechanisms are used in several key aspects of game development.
Importance of Timers and Timing
- 1. Gameplay Mechanics
Timers are often used to create time-based challenges, such as countdowns for completing a task or time limits for levels. This adds urgency and excitement to the gameplay.
- 2. Animation and Movement
Timing is essential for smooth animations and character movements. Consistent timing ensures that animations run at the correct speed and look natural.
- 3. Game Loops
The game loop relies on precise timing to update game states and render frames consistently. This ensures a smooth and responsive gaming experience.
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.
2.5.2. Implementing Timers in Allegro
Here’s how you can implement timers in an Allegro game:
- 1. Initialize Allegro and Create a Timer
//——Timer & Events——
ALLEGRO_TIMER *timer = al_create_timer(1.0 / 60.0); // start at 60 FPS
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());
al_register_event_source(queue, al_get_timer_event_source(timer));
al_start_timer(timer);
- 2. Control Game Loop with Timer Events
Use the timer to control the game loop and ensure consistent updates:
while (true) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if (ev.type == ALLEGRO_EVENT_TIMER) {
// Update game state
// Render frame
al_flip_display();
} else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
break;
}
}
- 3. Use Timers for Delayed Actions
You can also use timers to trigger events after a delay:
double start_time = al_get_time();
double delay = 5.0; // 5 seconds delay
while (true) {
double current_time = al_get_time();
if (current_time—start_time >= delay) {
// Trigger event
start_time = current_time; // Reset timer
}
// Other game logic
}
2.5.3. Example Code
In this example, we demonstrate how to use a timer to control the game loop and update the game state at a consistent frame rate. The space bar will allow the user to change the frame rate limiter via the timer, with rates of 15 fps, 30 fps, 60 fps, and unlimited. The effect of the frame rate can be seen with the animation of the ball. Look at the edges of the ball as it moves and you’ll see the effect:
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
static const int SCREEN_W = 800;
static const int SCREEN_H = 600;
typedef enum { FPS_15, FPS_30, FPS_60, FPS_UNLIMITED, FPS_COUNT } FpsMode;
static double fps_value(FpsMode m) {
switch (m) {
case FPS_15: return 15.0;
case FPS_30: return 30.0;
case FPS_60: return 60.0;
case FPS_UNLIMITED: return 0.0; // sentinel for unlimited
default: return 60.0;
}
}
static const char* fps_label(FpsMode m) {
switch (m) {
case FPS_15: return "15 FPS";
case FPS_30: return "30 FPS";
case FPS_60: return "60 FPS";
case FPS_UNLIMITED: return "Unlimited";
default: return "?";
}
}
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
ALLEGRO_DISPLAY *display = al_create_display(SCREEN_W, SCREEN_H);
if (!display) { fprintf(stderr, "Failed to create display\n"); return—1; }
ALLEGRO_FONT *font = al_create_builtin_font();
if (!font) { fprintf(stderr, "Failed to create builtin font\n"); al_destroy_display(display); return—1; }
//——Timer & Events——
ALLEGRO_TIMER *timer = al_create_timer(1.0 / 60.0); // start at 60 FPS
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());
al_register_event_source(queue, al_get_timer_event_source(timer));
al_start_timer(timer);
//——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;
bool redraw = true;
bool running = true;
// Timekeeping for unlimited mode and smooth motion
double last_time = al_get_time();
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);
}
// Process events if we have one (in unlimited we may or may not)
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
running = false;
} else if (ev.type == ALLEGRO_EVENT_KEY_DOWN) {
if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
running = false;
} else if (ev.keyboard.keycode == ALLEGRO_KEY_SPACE) {
// Cycle FPS mode
mode = (FpsMode)((mode + 1) % FPS_COUNT);
double f = fps_value(mode);
if (mode == FPS_UNLIMITED) {
al_stop_timer(timer);
} else {
al_set_timer_speed(timer, 1.0 / f);
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 (ev.type == ALLEGRO_EVENT_TIMER) {
// Timer tick—> redraw/update
if (ev.timer.source == timer) {
redraw = true;
}
}
if (redraw && (mode == FPS_UNLIMITED || al_is_event_queue_empty(queue))) {
//——Compute dt——
double now = al_get_time();
double dt;
if (mode == FPS_UNLIMITED) {
dt = now—last_time; // real elapsed time
} else {
// Fixed step from timer frequency
dt = 1.0 / fps_value(mode);
}
last_time = now;
//——Update ball physics——
x += vx * (float)dt;
y += vy * (float)dt;
// Collide with edges and bounce
if (x—radius < 0.0f) { x = radius; vx = fabsf(vx); }
if (x + radius > SCREEN_W) { x = SCREEN_W—radius; vx =—fabsf(vx); }
if (y—radius < 0.0f) { y = radius; vy = fabsf(vy); }
if (y + radius > SCREEN_H) { y = SCREEN_H—radius; vy =—fabsf(vy); }
//——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;
}
2.6. Using Files for Games
Using files in video games is essential for storing and managing various types of data, such as game assets, configurations, save files, and more.
2.6.1. File Types Commonly Used in Games
Games rely on several different types of files to support gameplay, customization, persistence, and debugging.
Types of Files in Games
- 1. Asset Files
These include images, sounds, music, models, and other resources used in the game. They are often stored in formats like PNG, WAV, MP3, OBJ, and so on.
- 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.
2.6.2. Managing Files in Allegro
Here’s how you can handle files in an Allegro game:
- 1. Loading Asset Files
Use Allegro’s built-in functions to load images, sounds, and other assets:
ALLEGRO_BITMAP *image = al_load_bitmap("image.png");
if (!image) {
fprintf(stderr, "Failed to load image!\n");
return-1;
}
ALLEGRO_SAMPLE *sound = al_load_sample("sound.wav");
if (!sound) {
fprintf(stderr, "Failed to load sound!\n");
return-1;
}
- 2. Reading and Writing Configuration Files
Use standard C functions or libraries like libconfig to read and write configuration files:
FILE *file = fopen("config.ini", "r");
if (file) {
char buffer[256];
while (fgets(buffer, sizeof(buffer), file)) {
// Process each line
}
fclose(file);
} else {
fprintf(stderr, "Failed to open config file!\n");
}
- 3. Handling Save Files
Save and load game state using file I/O operations:
// Saving game state
FILE *save_file = fopen("save.dat", "wb");
if (save_file) {
fwrite(&game_state, sizeof(GameState), , save_file);
fclose(save_file);
} else {
fprintf(stderr, "Failed to save game state!\n");
}
// Loading game state
FILE *load_file = fopen("save.dat", "rb");
if (load_file) {
fread(&game_state, sizeof(GameState), , load_file);
fclose(load_file);
} else {
fprintf(stderr, "Failed to load game state!\n");
}
- 4. Logging Events
Write log messages to a file for debugging purposes:
FILE *log_file = fopen("game.log", "a");
if (log_file) {
fprintf(log_file, "Game started\n");
fclose(log_file);
} else {
fprintf(stderr, "Failed to open log file!\n");
}
2.6.3. Code Example
The code example we’ll examine in this section is intentionally designed to simulate a failure scenario. The goal is to demonstrate how a game initialization routine can log the status of various subsystems—such as display, controller, video, and sound—into a log file (game.log), even when required resources are missing.
Behavior
The program attempts to read from a configuration file (config.ini) 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 game.log.
Since the required assets (e.g., sound files, graphics, and config.ini) are deliberately omitted, the program will launch and then exit immediately. After execution, you can inspect game.log to see which components failed to initialize.
How to Test
To observe changes in behavior:
- 1. Run the executable as is and examine game.log.
- 2. Add one or more of the missing files (e.g., config.ini) to the executable’s directory.
- 3. Run the program again and compare the updated log entries.
This approach helps illustrate how initialization routines and logging mechanisms 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>
typedef struct {
int level;
int score;
} GameState;
int main() {
/*——open log first so errors get recorded even if early failures
FILE *log_file = fopen("game.log", "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")
}
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
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 */
}
ALLEGRO_BITMAP *image = al_load_bitmap("image.png");
if (!image) {
fprintf(stderr, "Failed to load image!\n");
if (log_file) fprintf(log_file, "Failed to load image: image.png\n");
/* do not return; keep logging/cleanup consistent */
}
ALLEGRO_SAMPLE *sound = al_load_sample("sound.wav");
if (!sound) {
fprintf(stderr, "Failed to load sound!\n");
if (log_file) fprintf(log_file, "Failed to load sound: sound.wav\n");
}
FILE *config_file = fopen("config.ini", "r");
if (config_file) {
if (log_file) fprintf(log_file, "Opened config.ini for reading\
char buffer[256];
while (fgets(buffer, sizeof(buffer), config_file)) {
/* Process each line */
}
fclose(config_file);
if (log_file) fprintf(log_file, "Finished reading config.ini\n"
} else {
fprintf(stderr, "Failed to open config file!\n");
if (log_file) fprintf(log_file, "Failed to open config.ini\n");
}
GameState game_state = {0, 0};
FILE *save_file = fopen("save.dat", "wb");
if (save_file) {
size_t wrote = fwrite(&game_state, sizeof(GameState), 1, save_f
fclose(save_file);
if (log_file) {
fprintf(log_file, "Attempted to save game state: wrote %zu record(s)\n", wrote);
if (wrote != 1) fprintf(log_file, "Warning: partial write to save.dat\n");
fprintf(log_file, "Warning: partial write to save.dat\n");
}
} else {
fprintf(stderr, "Failed to save game state!\n");
if (log_file) fprintf(log_file, "Failed to open save.dat for writing\n");
}
/* final log + cleanup */
if (log_file) {
fprintf(log_file, "Shutting down\n");
fclose(log_file);
}
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.
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.
Integrating PhysicsFS with Allegro
- 1. Initialize PhysicsFS
Initialize PhysicsFS and set up the search path:
if (PHYSFS_init(NULL) == 0) {
fprintf(stderr, "Failed to initialize PhysicsFS—%s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return—1;
}
if (PHYSFS_mount("data.zip", NULL, 1) == 0) {
fprintf(stderr, "Failed to mount archive—%s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
PHYSFS_deinit();
return—1;
}
- 2. Set Allegro to Use PhysicsFS
Use Allegro’s PhysicsFS add-on to set the file interface:
al_set_physfs_file_interface();
- 3. Load Files Using Allegro’s File I/O API
Now you can load files from the archive using Allegro’s standard file I/O functions:
ALLEGRO_BITMAP *image = al_load_bitmap("image.png");
if (!image) {
fprintf(stderr, "Failed to load image!\n");
al_destroy_display(display);
PHYSFS_deinit();
return—1;
}
ALLEGRO_SAMPLE *sound = al_load_sample("sound.wav");
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, data.zip, and extract an image to display (image.png) and a sound to play (sound.wav). The program will get the data files from data.zip, 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()));
return—1;
}
if (PHYSFS_mount("data.zip", NULL, 1) == 0) {
fprintf(stderr, "Failed to mount archive—%s\n",
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
PHYSFS_deinit();
return—1;
}
al_set_physfs_file_interface();
ALLEGRO_DISPLAY *display = al_create_display(800, 600);
if (!display) {
fprintf(stderr, "Failed to create display!\n");
PHYSFS_deinit();
return—1;
}
ALLEGRO_BITMAP *image = al_load_bitmap("image.png");
if (!image) {
fprintf(stderr, "Failed to load image!\n");
al_destroy_display(display);
PHYSFS_deinit();
return—1;
}
ALLEGRO_SAMPLE *sound = al_load_sample("sound.wav");
if (!sound) {
fprintf(stderr, "Failed to load sound!\n");
al_destroy_bitmap(image);
al_destroy_display(display);
PHYSFS_deinit();
return—1;
}
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>
- 2. Link with the memfile Add-On
Ensure your project links against the allegro_memfile library.
- 3. Create and Use memfiles
- Create a Memfile: Use al_open_memfile to create a memfile from a block of memory:
char data = "This is some data in memory.";
ALLEGRO_FILE *memfile = al_open_memfile(data, sizeof(data), "r");
if (!memfile) {
fprintf(stderr, "Failed to create memfile!\n");
return-1;
}
- 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:
ALLEGRO_FILE *writable_memfile = al_open_memfile(data, sizeof(data), "w");
if (!writable_memfile) {
fprintf(stderr, "Failed to create writable memfile!\n");
return-1;
}
const char *new_data = "New data in memory.";
al_fwrite(writable_memfile, new_data, strlen(new_data) + );
- 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();
char data = "This is some data in memory.";
ALLEGRO_FILE *memfile = al_open_memfile(data, sizeof(data), "r");
if (!memfile) {
fprintf(stderr, "Failed to create memfile!\n");
return-1;
}
char buffer[256];
al_fread(memfile, buffer, sizeof(data));
printf("Read from memfile—%s\n", buffer);
al_fclose(memfile);
ALLEGRO_FILE *writable_memfile = al_open_memfile(data, sizeof(data), "w");
if (!writable_memfile) {
fprintf(stderr, "Failed to create writable memfile!\n");
return-1;
}
const char *new_data = "New data in memory.";
al_fwrite(writable_memfile, new_data, strlen(new_data) + );
al_fclose(writable_memfile);
printf("Updated data—%s\n", data);
return 0;
}
This example initializes Allegro, creates a memfile from a block of memory, reads from it, writes new data to it, and then prints the updated data.
Exercises, Homework Questions, and Projects
Section 2.1: Basic Structure and Setup
- 1. “Hello World!” Modifications
Modify the “Hello World!” program to display your name in a custom font, center-aligned, with a gradient background. Use al_draw_gradient() for the background.
- 2. Dynamic Window Sizing
Create a program where the user can input window dimensions (via command-line arguments) and render text at the center of the dynamically sized window.
- 3. Error Handling
Enhance the basic Allegro setup code to handle failures gracefully (e.g., missing fonts, display creation errors) without crashing. Print descriptive error messages.
Section 2.2: User Input Handling
- 4. Keyboard-Controlled Sprite
Create a 2D scene where a square moves continuously using arrow keys (state-based input) and jumps when the space bar is pressed (event-based input).
- 5. Mouse Drawing Tool
Build a program that lets users draw lines or shapes on the screen using mouse clicks and drags. Include a button (rendered on-screen) to clear the canvas.
- 6. Joystick Calibration
Write a program that detects connected joysticks, prints their axes/buttons, and lets the user calibrate joystick sensitivity.
- 7. Menu Navigation
Design a menu system (Start, Options, Exit) navigable via keyboard or mouse. Highlight selected options and play sound effects on interaction.
Section 2.3: Collision Detection
- 8. Pong Clone
Implement a two-player Pong game with paddle-ball collision (AABB) and scoring. The ball speeds up after each hit.
- 9. Platformer Physics
Create a character that jumps and moves on platforms. Use AABB collision to prevent falling through floors and to handle wall slides.
- 10. Circle-Based Shooter
Develop a top-down shooter where bullets (circles) collide with enemy targets. Use circle collision detection and visual feedback on hits.
- 11. SAT Implementation
Implement the Separating Axis Theorem (SAT) for collision between two rotating rectangles, including visualization of collision normals—the direction along which the overlap is minimal—and penetration depth, which represents how far the rectangles intersect along that normal.
Section 2.4: Sound and Music
- 12. Rhythm Game Prototype
Create a rhythm game where players press keys in sync with music. Play sounds for correct inputs and track accuracy.
- 13. Ambient Soundscapes
Build a scene with dynamic ambient sounds (e.g., rain, wind) that adjust volume based on the player’s position. Use audio streaming for background music.
- 14. Voice-Acted Dialogue
Design a text-based RPG with voice-acted dialogue snippets triggered by player choices. Manage audio memory efficiently.
Section 2.5: Timers and Timing
- 15. Frame Rate Limiter
Implement a game loop using ALLEGRO_TIMER to lock the frame rate at 60 fps. Measure and display the actual fps achieved.
- 16. Animation Sequencer
Animate a sprite sheet (e.g., walking character), using timers to cycle frames smoothly. Allow pausing/resuming animations.
Section 2.6: File Management
- 18. Save/Load System
Develop a game where player progress (e.g., level, score) is saved to a binary file. Implement a “Continue” option to load saved data.
- 19. Configuration File Parser
Read game settings (e.g., resolution, volume) from an INI/JSON file and apply them at start-up. Allow runtime changes and saving preferences.
- 20. PhysicsFS Asset Loader
Package game assets (images, sounds) into a ZIP archive. Use PhysicsFS to load resources dynamically during gameplay.
- 21. Memfile Level Editor
Design a tool that lets users create levels in memory (using memfiles) and export them to a file format for later loading.
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.