Introducción al Desarrollo de Videojuegos con GameMaker · Module 8: HUDs, Interfaces, Menus, and Pause Systems

Victory and Game Over Screens

Lesson 40 of 60 · 50 min

Also available in: ES

Introduction: Closing the Core Game Loop

Every video game features a fundamental feedback loop: the player interacts with core mechanics, overcomes challenges or makes mistakes, and ultimately reaches an outcome. Up to this point in the course, our project features a character that can run, jump, collide with enemies, take damage, lose lives, and collect items, alongside a functional HUD and a main menu. However, when health or lives hit zero, or when reaching the end of the stage, the game offers no formal conclusion.

End-of-game screens serve two vital purposes: they provide immediate narrative and psychological closure to the player's effort, and they technically manage the application's state transitions. In this lesson, you will learn how to structure clean, professional Game Over and Victory screens using dedicated rooms, `Draw GUI` events, keyboard-driven controllers, and a rigorous reset of global variables to ensure every run begins in a predictable, bug-free state.

Schematic flowchart showing the transition from the gameplay room to the Game Over screen or the Victory screen, and returning to the level or main menu.
Figure 1: Transition architecture between the gameplay level and the game-ending screens. · AI-generated image

Room Structure and Required Assets

To implement these screens, we will maintain strict architectural separation. Rather than overlaying complex UI layers directly over the active stage, we will use dedicated rooms. This ensures that all enemies, projectiles, particles, and physics calculations from the level are automatically cleared from RAM upon leaving the gameplay room.

In the Asset Browser, create the following resources within their respective asset folders:

Resource NameResource TypeProject Purpose
rm_gameoverRoomRoom that displays the visual interface and menu logic after running out of lives.
rm_victoryRoomRoom to which the player is transported upon reaching the final stage goal.
spr_goalSprite32x32 pixel flag or icon marking the end of the level.
obj_goalObjectInteractive object that triggers the victory event upon player collision.
obj_gameover_controllerObjectSprite-less controller managing the Game Over screen and selection menu.
obj_victory_controllerObjectSprite-less controller managing the victory screen and selection menu.

💡 Remember that GameMaker rooms use a configured default viewport size. To maintain visual consistency, assign 'rm_gameover' and 'rm_victory' the exact same base dimensions defined for your game resolution in previous lessons (for example, 640x360 or 1280x720).

Defeat Condition: Redirecting to Game Over

In Module 7, we implemented the lives counter using the global variable `global.player_lives`. When the player takes fatal damage from an enemy or falls into a pit, they lose one life. If lives remain, the character respawns at the latest checkpoint. However, when lives drop to zero or below, the final defeat sequence must trigger.

To prevent an abrupt room jump that disorients the player, good game design calls for introducing a short delay using an Alarm. This brief pause allows any death animation or sound effect to complete before leaving the level.

Open `obj_player` and locate the code block handling life deductions within the **Step** event or your custom damage script:

// Snippet inside obj_player -> Step Event or damage function
if (hp <= 0 && !is_dead)
{
    is_dead = true;
    global.player_lives -= 1;
    
    // Stop horizontal and vertical movement
    hsp = 0;
    vsp = 0;
    
    // Check if remaining lives are still available
    if (global.player_lives > 0)
    {
        // If lives remain, trigger respawn alarm (1 second = 60 steps)
        alarm[0] = 60;
    }
    else
    {
        // If out of lives, trigger alarm transitioning to Game Over
        alarm[1] = 90;
    }
}

Next, add the **Alarm 1** event in `obj_player` with the instruction responsible for executing the room transition:

/// Event: Alarm 1 in obj_player
// Safely transition to the game over room
room_goto(rm_gameover);

Victory Condition: The Goal Object

For the victory condition, we create an endpoint object: `obj_goal`. This object represents a flagpole, portal, or chest that triggers stage completion upon contact with the player's bounding box.

In the **Create** event of `obj_goal`, initialize a boolean flag to avoid triggering the collision multiple times across consecutive frames:

/// Event: Create in obj_goal
triggered = false;

In the **Collision** event between `obj_goal` and `obj_player`, handle victory by disabling character input and initiating a short delay before changing rooms:

/// Event: Collision with obj_player in obj_goal
if (!triggered)
{
    triggered = true;
    
    // Freeze player input and motion
    other.can_move = false;
    other.hsp = 0;
    other.vsp = 0;
    
    // Start alarm to trigger the transition
    alarm[0] = 60;
}

Then, in the **Alarm 0** event of `obj_goal`, execute the room transition:

/// Event: Alarm 0 in obj_goal
room_goto(rm_victory);

Building the Game Over Controller (obj_gameover_controller)

Once the player lands in `rm_gameover`, we need an object to render UI elements and listen for player inputs (to retry the level or head back to the main menu). Place a single instance of `obj_gameover_controller` in `rm_gameover`.

In its **Create** event, define the menu choices and an index variable for navigation:

/// Event: Create in obj_gameover_controller
menu_options = ["RETRY", "MAIN MENU"];
menu_index = 0;
menu_total = array_length(menu_options);

// Input cooldown to prevent accidental immediate presses
input_cooldown = 10;

In the **Step** event, handle keyboard navigation (Up/Down arrow keys or W/S) and selection via Enter or Space:

/// Event: Step in obj_gameover_controller
if (input_cooldown > 0)
{
    input_cooldown -= 1;
    exit;
}

var key_up = keyboard_check_pressed(vk_up) || keyboard_check_pressed(ord("W"));
var key_down = keyboard_check_pressed(vk_down) || keyboard_check_pressed(ord("S"));
var key_select = keyboard_check_pressed(vk_enter) || keyboard_check_pressed(vk_space);

if (key_up)
{
    menu_index -= 1;
    if (menu_index < 0) menu_index = menu_total - 1;
}

if (key_down)
{
    menu_index += 1;
    if (menu_index >= menu_total) menu_index = 0;
}

if (key_select)
{
    switch (menu_index)
    {
        case 0:
            // Retry: Reset global stats and load the first level
            scr_restart_game_state();
            room_goto(rm_level_1);
            break;
            
        case 1:
            // Main Menu: Reset stats and return to the title screen
            scr_restart_game_state();
            room_goto(rm_menu);
            break;
    }
}

To render the interface on screen, use the **Draw GUI** event, ensuring text draws consistently regardless of camera movement or zoom levels:

/// Event: Draw GUI in obj_gameover_controller
var gui_w = display_get_gui_width();
var gui_h = display_get_gui_height();

// Semi-transparent dark overlay across the room
draw_set_color(c_black);
draw_set_alpha(0.85);
draw_rectangle(0, 0, gui_w, gui_h, false);
draw_set_alpha(1.0);

// Draw main title
draw_set_halign(fa_center);
draw_set_valign(fa_middle);

draw_set_color(c_red);
draw_text_transformed(gui_w * 0.5, gui_h * 0.3, "GAME OVER", 2.0, 2.0, 0);

// Draw menu choices
for (var i = 0; i < menu_total; i++)
{
    var opt_y = (gui_h * 0.55) + (i * 40);
    
    if (i == menu_index)
    {
        draw_set_color(c_yellow);
        draw_text(gui_w * 0.5, opt_y, "> " + menu_options[i] + " <");
    }
    else
    {
        draw_set_color(c_white);
        draw_text(gui_w * 0.5, opt_y, menu_options[i]);
    }
}

// Restore default draw alignments
draw_set_halign(fa_left);
draw_set_valign(fa_top);

Building the Victory Controller (obj_victory_controller)

The victory screen follows a structure similar to Game Over, but with a celebratory presentation. This is where we reward the player by displaying their final accumulated score (`global.player_score`).

Create the `obj_victory_controller` object and configure its **Create** event:

/// Event: Create in obj_victory_controller
menu_options = ["PLAY AGAIN", "MAIN MENU"];
menu_index = 0;
menu_total = array_length(menu_options);
input_cooldown = 10;

The **Step** event replicates input handling, allowing the player to choose between replaying or returning to the main menu:

/// Event: Step in obj_victory_controller
if (input_cooldown > 0)
{
    input_cooldown -= 1;
    exit;
}

var key_up = keyboard_check_pressed(vk_up) || keyboard_check_pressed(ord("W"));
var key_down = keyboard_check_pressed(vk_down) || keyboard_check_pressed(ord("S"));
var key_select = keyboard_check_pressed(vk_enter) || keyboard_check_pressed(vk_space);

if (key_up)
{
    menu_index -= 1;
    if (menu_index < 0) menu_index = menu_total - 1;
}

if (key_down)
{
    menu_index += 1;
    if (menu_index >= menu_total) menu_index = 0;
}

if (key_select)
{
    switch (menu_index)
    {
        case 0:
            scr_restart_game_state();
            room_goto(rm_level_1);
            break;
            
        case 1:
            scr_restart_game_state();
            room_goto(rm_menu);
            break;
    }
}

In the **Draw GUI** event, add the display for the player's total accumulated score:

/// Event: Draw GUI in obj_victory_controller
var gui_w = display_get_gui_width();
var gui_h = display_get_gui_height();

// Decorative dark background
draw_set_color(make_color_rgb(20, 25, 40));
draw_set_alpha(0.9);
draw_rectangle(0, 0, gui_w, gui_h, false);
draw_set_alpha(1.0);

draw_set_halign(fa_center);
draw_set_valign(fa_middle);

// Victory banner
draw_set_color(c_yellow);
draw_text_transformed(gui_w * 0.5, gui_h * 0.25, "LEVEL COMPLETE!", 2.0, 2.0, 0);

// Display accumulated score
draw_set_color(c_white);
draw_text(gui_w * 0.5, gui_h * 0.4, "Final Score: " + string(global.player_score));

// Interactive options
for (var i = 0; i < menu_total; i++)
{
    var opt_y = (gui_h * 0.6) + (i * 40);
    
    if (i == menu_index)
    {
        draw_set_color(c_lime);
        draw_text(gui_w * 0.5, opt_y, "> " + menu_options[i] + " <");
    }
    else
    {
        draw_set_color(c_gray);
        draw_text(gui_w * 0.5, opt_y, menu_options[i]);
    }
}

draw_set_halign(fa_left);
draw_set_valign(fa_top);

Thoroughly Resetting Game State with Scripts

A common mistake beginners make is calling `room_goto(rm_level_1)` or `game_restart()` directly without explicitly resetting global variables. If the player reached Game Over because their lives fell to zero and returns to the level with `global.player_lives = 0`, they will die instantly on the very first frame.

To prevent redundant code between your defeat and victory controllers, create a reusable script called `scr_game_flow` containing the `scr_restart_game_state()` function:

/// Script: scr_game_flow
function scr_restart_game_state()
{
    // Reset global progression and tracking variables
    global.player_lives = 3;
    global.player_score = 0;
    global.coins_collected = 0;
    
    // Reset checkpoint coordinates if active
    global.checkpoint_x = -1;
    global.checkpoint_y = -1;
}

💡 Avoid using the built-in 'game_restart()' function to restart an individual stage. 'game_restart()' reloads the entire game from the initial room, resetting audio settings and display flags, which causes an unnecessary screen flicker.

Practical Example: Complete End-of-Level Flow

Hands-On Exercises

Reinforce what you have learned by completing these three exercises designed to solidify your mastery of game-ending transitions:

1. **Fade-Out Effect:** Instead of instantly switching rooms with `room_goto()`, create an `obj_fade` instance with an opacity variable `alpha = 0` that increases each step (`alpha += 0.05`). When `alpha >= 1`, invoke the room transition. Verify that the screen fades to black smoothly before displaying the Game Over interface.

2. **High Score Tracker:** Add a global variable `global.high_score = 0;` in your game initialization script. In `obj_victory_controller`, before rendering the score, check if `global.player_score > global.high_score`. If so, update `global.high_score` with the new value and draw a flashing 'NEW HIGH SCORE!' banner in gold.

3. **Remaining Time Penalty:** Add a stage timer that counts down by one second every 60 steps. If the timer reaches zero before the player reaches `obj_goal`, subtract a life due to a timeout. If that was their last life, route the player to `rm_gameover` displaying a 'TIME'S UP' notice.

Summary

  • Victory and Game Over conditions formally conclude the gameplay loop and deliver immediate player feedback.
  • Separating gameplay and end screens into dedicated rooms (rm_gameover and rm_victory) simplifies memory management and instance cleanup.
  • Using alarms prior to switching rooms allows death or victory animations to play fully without sudden cuts.
  • The Draw GUI event provides the ideal rendering context for centered titles, scores, and menu options without camera interference.
  • A centralized reset script (scr_restart_game_state) prevents critical bugs by guaranteeing lives, points, and checkpoints return to default values upon restarting.

Practise what you learned

Exercise 1 · 1 point

What is the main technical advantage of using dedicated rooms for the victory and Game Over screens instead of overlaying UI elements directly over the active level?

You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 2 · 1 point

Order the steps that occur in the goal object (obj_goal) from the moment it collides with the player until the transition to the victory screen takes place:

  1. 1Check that the triggered flag is false and immediately set it to true.
  2. 2Execute the Alarm 0 event by calling room_goto(rm_victory).
  3. 3Freeze the player by updating their movement variables and setting can_move to false.
  4. 4Start the transition countdown by setting alarm[0] = 60.
You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 3 · 2 points

Complete the code for the Alarm 1 event in obj_player to transition to the Game Over screen (rm_gameover) using the appropriate GameMaker function.

Your code is not executed: specific requirements are checked on the text.

You are practising without an account: nothing is saved. Sign up to keep your progress.

Sign in to save your progress — Sign in