Skip to main content

Practical Game Programming: 2. Essentials of Game Programming with Allegro

Practical Game Programming
2. Essentials of Game Programming with Allegro
  • Show the following:

    Annotations
    Resources
  • Adjust appearance:

    Font
    Font style
    Color Scheme
    Light
    Dark
    Annotation contrast
    Low
    High
    Margins
  • Search within:
    • My Notes + Comments
    • Notifications
    • Privacy
  • Project HomePractical Game Programming
  • Learn more about Manifold

Notes

table of contents
  1. Cover
  2. Acknowledgements
  3. 1. Introduction
    1. 1.1. Introduction to Video Games and Game Programming
    2. 1.2. History of Video Games
    3. 1.3. Types of Video Games
    4. 1.4. Road Map of Game Development
    5. 1.5. Set Up Game Development IDE
    6. 1.6. The Allegro 5 Game Library
      1. Exercises, Homework Questions, and Projects
  4. 2. Essentials of Game Programming with Allegro
    1. 2.1. Common Structure of a C/C++ Program with Allegro 5
    2. 2.2. Handling User Input
    3. 2.3. Collision Detection
    4. 2.4. Adding Sound Effects to Games
    5. 2.5. Timers and Game Timing
    6. 2.6. Using Files for Games
      1. Exercises, Homework Questions, and Projects
  5. 3. Using Graphics in Games
    1. 3.1. Bitmap Fundamentals
    2. 3.2. Bitmaps Creation and Use in Allegro 5
    3. 3.3. Utilizing Bitmaps in a Program
    4. 3.4. Scaling and Rotating Bitmaps
    5. 3.5. Displaying a Section of a Bitmap and the Concept of a Viewport
    6. 3.6. Advanced Graphics Techniques in Allegro 5
    7. 3.7. How Transformations Help with Resolution Independence
    8. 3.8. Antialiasing and Texture Smoothing
    9. 3.9. Shaders
    10. 3.10. Using Premultiplied Alpha in Allegro 5
      1. Exercises, Homework Questions, and Projects
  6. 4. Programming Sprites in Allegro 5
    1. 4.1. The Fundamentals
    2. 4.2. Additional Sprite Handling Routines
    3. 4.3. Allegro Blending Function and Sprites
    4. 4.4. Advanced Sprite Use in Allegro 5
      1. Exercises, Homework Questions, and Projects
  7. 5. Programming Backgrounds for Video Games
    1. 5.1. Types of Backgrounds
    2. 5.2. Creating and Using Static Backgrounds
    3. 5.3. Creating and Using Scrolling Backgrounds
    4. 5.4. Creating and Using Dynamic Backgrounds
    5. 5.5. Creating and Using 3D Backgrounds
      1. Exercises, Homework Questions, and Projects
  8. 6. Game Design and Development Fundamentals
    1. 6.1. Conceive an Amazing Game
    2. 6.2. Transforming a Billion-Dollar Idea into a Detailed Game Design
    3. 6.3. Implement the Game
      1. Exercises, Homework Questions, and Projects
  9. 7. Advanced Topics in Practical Game Programming
    1. 7.1. Multithreading
    2. 7.2. Custom Shaders
    3. 7.3. Networking for Multiplayer Games
    4. 7.4. AI Programming
    5. 7.5. Optimization Techniques
    6. 7.6. Custom GUI Systems
    7. 7.7. Advanced Audio
      1. Exercises, Homework Questions, and Projects
  10. 8. Game Development Projects
    1. 8.1. Action Game: Space Shooter
      1. Exercises, Homework Questions, and Projects
    2. 8.2. Platform Game: Tank War
      1. Exercises, Homework Questions, and Projects
    3. 8.3. Adventure Game
      1. Exercises, Homework Questions, and Projects
    4. 8.4. Arcade Game
      1. Exercises, Homework Questions, and Projects
    5. 8.5. Board Game
      1. Exercises, Homework Questions, and Projects
    6. 8.6. Emulator Game
      1. Exercises, Homework Questions, and Projects
    7. 8.7. Puzzle Game
      1. Exercises, Homework Questions, and Projects
    8. 8.8. Role-Playing Game
      1. Exercises, Homework Questions, and Projects
    9. 8.9. Sports Game
      1. Exercises, Homework Questions, and Projects
    10. 8.10. Strategy Game
      1. Exercises, Homework Questions, and Projects
    11. 8.11. Utilities Game
      1. Exercises, Homework Questions, and Projects
  11. References and Resources

Chapter 2.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. 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. 2. Initialize Allegro and its components such as display and font, as well as whatever you will use in the program.
  3. 3. Create the window to hold the content of the program by defining the dimensions of the window.
  4. 4. Load resources, such as fonts for displaying text.
  5. 5. Render the text by drawing the text “Hello, World!” on the screen and hold it for users to see.
  6. 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;

}

'''

Table 1: Common Keycodes

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. 1. Moving the mouse pointer and clicking around as you normally would.
  2. 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. 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. 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. 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. 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. 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. 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. 3. Read Joystick State
    • • Create an ALLEGRO_JOYSTICK_STATE object.
    • • Use al_get_joystick_state() to update the state of the joystick.
  4. 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. 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 is an illustration of two overlapping, semitransparent cubes. The left cube is orange and labelled Object A, and the right cube is blue and labelled Object B. The overlapping area, where the collision is detected, is red.

    Figure 2.1: Collision detection using bounding boxes. Illustrated by Kyle Flemmer.

  2. 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 is an illustration of a flying saucer against a square red background with a green box around the perimeter of the image.

    Figure 2.2: Saucer image with a green bounding box. Illustrated by Walter Ridgewell.

  3. 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. 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 is an illustration of a flying saucer against a red background with a green circle around it. The saucer fits just within the circumference of the circle.

Figure 2.3: Saucer image with a round bounding box. Illustrated by Walter Ridgewell.

Figure 2.4 is an illustration of a flying saucer against a rectangular red background with a green box around the perimeter of the image. The saucer fits just within the dimensions of the rectangle.

Figure 2.4: Saucer image cropped with a rectangular bounding box. Illustrated by Walter Ridgewell.

  1. 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 is a screenshot of the SpriteCollisions.exe program window. Two squares, one red and one green, collide against a black background. The print message “We Collided” is centred in the program window.

Figure 2.5: Collision response with a print message. Image by Walter Ridgewell.

Table 2: Sprite Struct Variables

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:

Table 3: Sprite Struct Properties

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. 1. a->x < b->x + b->width

    Ensures Sprite A’s left edge is to the left of Sprite B’s right edge.

  2. 2. a->x + a->width > b->x

    Ensures Sprite A’s right edge is to the right of Sprite B’s left edge.

  3. 3. a->y < b->y + b->height

    Ensures Sprite A’s top edge is above Sprite B’s bottom edge.

  4. 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 is an illustration of two separate squares. The upper left square, labelled Sprite A, is orange and has the coordinates x A, y A. An arrow points diagonally down to the lower right square, labelled Sprite B, which is blue and has the coordinates x B, y B.

Figure 2.6: Sprites positioned apart: No collision detected. Illustrated by Kyle Flemmer.

Figure 2.7 is an illustration of two overlapping squares. The upper left square, labelled Sprite A, is orange. The lower right square, labelled Sprite B, is blue. The overlapping area is red.

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 is an illustration of two separate squares. The upper square, labelled Sprite A, is orange and has the coordinates x A, y A. An arrow points down to the lower square, labelled Sprite B, which is blue and has the coordinates x B, y B.

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 is an illustration of two adjacent squares. The upper square, labelled Sprite A, is orange. The lower square, labelled Sprite B, is blue. There is a red, dashed box surrounding the edges where the upper and lower squares touch.

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. 1. Projection of a Point onto an Axis

    Given a point P and an axis A, the projection P’ is

    P prime equals the quotient of P times A over A times A multiplied by A.

  2. 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. 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. 2. Intersection Point

    If the signs of d and d2 are different, compute the intersection point P:

    P equals P plus the quotient of d over d minus d sub two multiplied by the difference of P sub two minus P.

  3. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 1. Initialize the Required Audio Add-Ons

    al_install_audio();

    al_init_acodec_addon();

    al_reserve_samples(8);

  2. 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. 1. Set Up FFmpeg

    Install FFmpeg and include it in your project.

  2. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 2. Configuration Files

    These files store settings and preferences for the game. Common formats include INI, JSON, and XML.

  3. 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. 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. 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. 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. 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. 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. 1. Run the executable as is and examine game.log.
  2. 2. Add one or more of the missing files (e.g., config.ini) to the executable’s directory.
  3. 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. 1. Install PhysicsFS

    Download and install PhysicsFS from the official website.

  2. 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. 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. 2. Set Allegro to Use PhysicsFS

    Use Allegro’s PhysicsFS add-on to set the file interface:

       al_set_physfs_file_interface();

  3. 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. 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. 2. Link with the memfile Add-On

    Ensure your project links against the allegro_memfile library.

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

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

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

  3. 6. Joystick Calibration

    Write a program that detects connected joysticks, prints their axes/buttons, and lets the user calibrate joystick sensitivity.

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

  1. 8. Pong Clone

    Implement a two-player Pong game with paddle-ball collision (AABB) and scoring. The ball speeds up after each hit.

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

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

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

  1. 12. Rhythm Game Prototype

    Create a rhythm game where players press keys in sync with music. Play sounds for correct inputs and track accuracy.

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

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

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

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

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

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

  3. 20. PhysicsFS Asset Loader

    Package game assets (images, sounds) into a ZIP archive. Use PhysicsFS to load resources dynamically during gameplay.

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

  1. 22. 2D Arcade Game

    Build a complete game with scoring, sound effects, collision, and a high-score system (think Space Invaders, Breakout).

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

  3. 24. Procedural Terrain Generator

    Generate random terrain using Perlin noise, save/load maps to files, and allow players to explore with mouse/keyboard controls.

  4. 25. Roguelike Prototype

    Develop a dungeon-crawling game with grid-based movement, enemy AI, and permadeath. Use AABB collision for combat.

  5. 26. Music Visualizer

    Analyze audio streams (e.g., beat detection) and render real-time visual effects synchronized to the music.

  6. 27. Physics Sandbox

    Simulate gravity and collisions between objects (circles, rectangles). Let users spawn objects with mouse clicks and adjust physics parameters.

  7. 28. AI Racing Game

    Implement a car-racing game with AI opponents. Use state-based input for steering and event-based sounds for collisions.

  8. 29. Interactive Storybook

    Combine mouse input, animations, and voice acting to create an interactive story with branching narratives. Save progress between sessions.

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

Annotate

Next Chapter
3. Using Graphics in Games
PreviousNext
This work is licensed under a Creative Commons License (CC BY-NC-SA 4.0), except where otherwise noted. This license allows users to copy and redistribute the material in any medium or format and to remix, transform, and build upon the material as long as the original source is properly credited, the work is not used for commercial purposes, and the new creation is licensed under the same terms.
Powered by Manifold Scholarship. Learn more at
Opens in new tab or windowmanifoldapp.org