Introducción al Desarrollo de Videojuegos con GameMaker · Module 8: HUDs, Interfaces, Menus, and Pause Systems
Building a Functional Pause System
Lesson 39 of 60 · 60 min
Also available in: ES
Architecture and Core Concepts of a Pause System
Pausing a video game might seem straightforward at first glance: simply 'stopping time.' However, in software development—and specifically within GameMaker's Game Loop—the engine never truly stops. It continues processing frames, dispatching events, and rendering graphics to the display. Halting the engine's execution loop entirely would cause the operating system to flag the game window as unresponsive or frozen, ignoring all user inputs.
There are two primary paradigms for building a pause system in 2D game engines:
1. **Conditional State Flags:** Wrapping all movement, collision, and animation logic in every game object inside a condition such as `if (!global.game_paused) { ... }`. While intuitive for small prototypes, this approach scales poorly and invites human error. Forgetting a single check on an enemy, projectile, or alarm timer causes that entity to keep updating while the rest of the game world stands frozen.
2. **Instance Deactivation with Surface Capture:** The standard, professional method in GameMaker. This technique temporarily suspends all active gameplay entities using built-in functions like `instance_deactivate_all()` or `instance_deactivate_region()`. Deactivated instances stop running their `Step`, `Alarm`, and `Collision` events immediately. Because deactivated instances also cease running their `Draw` events, we capture a visual snapshot of the screen into a dedicated `surface` buffer and draw it statically underneath an interactive overlay menu.
Throughout this lesson, we will implement this second method by creating a dedicated controller object: `obj_pausa`. This controller manages screen capture, instance freezing, overlay menu navigation, and the clean reactivation of all game entities.

Creating the Pause Controller: obj_pausa
To ensure our pause system works independently without interfering with level logic or the HUD systems created in earlier lessons, we will build a dedicated controller object named `obj_pausa`. This object can be marked persistent so it carries across rooms, or placed directly in each playable level.
Create a new object in GameMaker's Asset Browser, name it `obj_pausa`, and leave its sprite unassigned. Check the **Persistent** box if you want a single controller running across all gameplay rooms. Alternatively, place it manually on an `Instances` or `Controllers` layer in `rm_nivel_1`.
Initializing Variables in the Create Event
In the `Create` event of `obj_pausa`, declare the variables controlling the pause state, the reference handle for the screen capture surface, and the elements of the interactive floating menu.
/// @description Create Event of obj_pausa
// Pause state
esta_pausado = false;
// Surface handle for screen capture
surf_pausa = -1;
// Pause menu items
opciones = [
"Resume",
"Restart Level",
"Main Menu"
];
// Current selected option index (0 is "Resume")
opcion_actual = 0;
total_opciones = array_length(opciones);
// Menu visual formatting
ancho_gui = display_get_gui_width();
alto_gui = display_get_gui_height();
separacion_lineas = 40;
// Input debounce timer to prevent unintentional rapid-fire scrolling
espera_entrada = 0;
tiempo_espera = 10;Let's break down each variable's role:
| Variable | Type | Purpose |
|---|---|---|
| esta_pausado | Boolean | Tracks whether the main game loop is frozen and the pause menu is open. |
| surf_pausa | Surface ID | Stores the memory handle of the surface holding our frozen screen snapshot. |
| opciones | Array | Stores the localized text strings rendered as selectable menu options. |
| opcion_actual | Integer | Points to the array index of the currently highlighted option. |
| espera_entrada | Integer | Debounce timer for smooth vertical navigation when holding directional keys. |
Visual Capture and Deactivation in the Step Event
The `Step` event of `obj_pausa` drives the state changes. During normal gameplay, this event checks for the Escape key (`vk_escape`) or gamepad Start button. Once paused, the same event manages navigation through the menu options.
A critical nuance in GameMaker: when calling `instance_deactivate_all(true)`, the `true` argument (the `notme` parameter) tells the engine to deactivate every active instance **except** the calling instance (`obj_pausa`). This allows `obj_pausa` to continue executing its `Step` and `Draw GUI` events normally, capturing player input and rendering the menu overlay.
/// @description Step Event of obj_pausa
// 1. Detect toggle input
var _tecla_pausa = keyboard_check_pressed(vk_escape) || gamepad_button_check_pressed(0, gp_start);
if (_tecla_pausa)
{
esta_pausado = !esta_pausado;
if (esta_pausado)
{
// Get active render canvas dimensions
var _w = surface_get_width(application_surface);
var _h = surface_get_height(application_surface);
// Allocate temporary surface and copy current frame buffer
surf_pausa = surface_create(_w, _h);
surface_copy(surf_pausa, 0, 0, application_surface);
// Deactivate all gameplay instances, preserving only this controller
instance_deactivate_all(true);
// Reset cursor to default option
opcion_actual = 0;
}
else
{
// Reactivate all entities upon unpausing
instance_activate_all();
// Free cached surface from VRAM if it exists
if (surface_exists(surf_pausa))
{
surface_free(surf_pausa);
surf_pausa = -1;
}
}
}
// 2. Menu navigation logic while paused
if (esta_pausado)
{
// Vertical navigation inputs
var _arriba = keyboard_check_pressed(vk_up) || keyboard_check_pressed(ord("W")) || gamepad_button_check_pressed(0, gp_padu);
var _abajo = keyboard_check_pressed(vk_down) || keyboard_check_pressed(ord("S")) || gamepad_button_check_pressed(0, gp_padd);
var _seleccionar = keyboard_check_pressed(vk_enter) || keyboard_check_pressed(vk_space) || gamepad_button_check_pressed(0, gp_face1);
// Wrap-around vertical cursor movement
if (_arriba)
{
opcion_actual -= 1;
if (opcion_actual < 0)
{
opcion_actual = total_opciones - 1;
}
}
if (_abajo)
{
opcion_actual += 1;
if (opcion_actual >= total_opciones)
{
opcion_actual = 0;
}
}
// Execute selected action
if (_seleccionar)
{
switch (opcion_actual)
{
case 0: // Resume
esta_pausado = false;
instance_activate_all();
if (surface_exists(surf_pausa))
{
surface_free(surf_pausa);
surf_pausa = -1;
}
break;
case 1: // Restart Level
esta_pausado = false;
instance_activate_all();
if (surface_exists(surf_pausa))
{
surface_free(surf_pausa);
surf_pausa = -1;
}
room_restart();
break;
case 2: // Main Menu
esta_pausado = false;
instance_activate_all();
if (surface_exists(surf_pausa))
{
surface_free(surf_pausa);
surf_pausa = -1;
}
room_goto(rm_menu);
break;
}
}
}💡 Golden Rule of GameMaker: Whenever you restart a room with room_restart() or navigate away with room_goto() from a paused state, you MUST call instance_activate_all() and free your surfaces first. If a room changes while instances are deactivated, GameMaker skips their Destroy and Clean Up events, causing memory leaks and broken logic in persistent systems.
Rendering the Overlay Menu in Draw GUI
The visual presentation of the pause state consists of three layered elements rendered in the `Draw GUI` event:
1. **The Frozen Frame Buffer:** The cached surface (`surf_pausa`). Without rendering this, the screen would clear to black or flicker, since game instances are no longer running their normal draw routines. 2. **The Darkening Dimmer:** A semi-transparent black rectangle (around 50% to 65% alpha) overlaid on the frozen image to soften the background and maximize text contrast. 3. **The Menu Panel and Options:** A centered frame displaying the 'PAUSE' header and selectable options, highlighting the current selection with accent colors or bracket indicators.
/// @description Draw GUI Event of obj_pausa
// Exit early if the game is not paused
if (!esta_pausado) exit;
// Fetch up-to-date GUI canvas dimensions
var _gui_w = display_get_gui_width();
var _gui_h = display_get_gui_height();
// 1. RENDER FROZEN BACKGROUND SURFACE
// Always verify that the surface handle is valid in GPU memory
if (surface_exists(surf_pausa))
{
// Stretch surface across the GUI resolution
draw_surface_stretched(surf_pausa, 0, 0, _gui_w, _gui_h);
}
else
{
// Fallback solid background if surface was reclaimed by the OS
draw_set_color(c_dkgray);
draw_rectangle(0, 0, _gui_w, _gui_h, false);
}
// 2. DARKENING OVERLAY
draw_set_color(c_black);
draw_set_alpha(0.65);
draw_rectangle(0, 0, _gui_w, _gui_h, false);
draw_set_alpha(1.0); // Reset alpha channel immediately
// 3. FLOATING MENU BOX
var _caja_w = 400;
var _caja_h = 280;
var _centro_x = _gui_w * 0.5;
var _centro_y = _gui_h * 0.5;
var _x1 = _centro_x - (_caja_w * 0.5);
var _y1 = _centro_y - (_caja_h * 0.5);
var _x2 = _centro_x + (_caja_w * 0.5);
var _y2 = _centro_y + (_caja_h * 0.5);
// Panel background
draw_set_color(make_color_rgb(25, 25, 40));
draw_rectangle(_x1, _y1, _x2, _y2, false);
// Outer border
draw_set_color(make_color_rgb(180, 180, 220));
draw_rectangle(_x1, _y1, _x2, _y2, true);
// Inner detail border
draw_rectangle(_x1 + 4, _y1 + 4, _x2 - 4, _y2 - 4, true);
// 4. LABELS AND OPTIONS
// Configure text alignment
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
// Header label
draw_set_color(c_yellow);
draw_text_transformed(_centro_x, _y1 + 45, "- PAUSED -", 1.5, 1.5, 0);
// Render selectable options
var _inicio_opciones_y = _y1 + 120;
for (var i = 0; i < total_opciones; i++)
{
var _pos_y = _inicio_opciones_y + (i * separacion_lineas);
if (i == opcion_actual)
{
// Highlighted selection
draw_set_color(c_white);
// Selection cursor indicators
var _texto_opcion = "> " + opciones[i] + " <";
draw_text(_centro_x, _pos_y, _texto_opcion);
}
else
{
// Inactive options
draw_set_color(c_gray);
draw_text(_centro_x, _pos_y, opciones[i]);
}
}
// Reset global drawing state to prevent side effects on other objects
draw_set_halign(fa_left);
draw_set_valign(fa_top);
draw_set_color(c_white);Memory Management: The Clean Up Event
Surfaces in GameMaker are allocated directly inside video memory (VRAM) on the GPU. Unlike ordinary scalar variables or strings, GameMaker's garbage collector cannot automatically reclaim surfaces when a room transitions or an object is destroyed. If you allocate a surface and destroy its owning object without explicitly freeing it, those megabytes linger in VRAM until the game process terminates, resulting in a progressive memory leak.
To prevent memory leaks, you should always handle resource deallocation in the `Clean Up` event for any object that manages surfaces, data structures (`ds_grid`, `ds_list`), or buffers.
/// @description Clean Up Event of obj_pausa
// Free surface from VRAM if it is still allocated
if (surface_exists(surf_pausa))
{
surface_free(surf_pausa);
surf_pausa = -1;
}
// Ensure no instances remain deactivated accidentally
instance_activate_all();Practical Extension: Smooth Audio Ducking
In commercial platformers, snapping the screen to a halt while game music continues blaring at full blast feels unpolished. A common refinement is to pause short-lived sound effects while gently ducking (lowering) background music volume to establish an ambient, muted atmosphere.
Surface Volatility and Recovery
On desktop platforms like Windows, macOS, and Linux, GameMaker surfaces are volatile. If a player minimizes the game window, presses `Alt + Tab` to switch applications, or locks their operating system, the GPU driver may discard the surface buffer without notice.
This is why our `Draw GUI` logic checks `if (surface_exists(surf_pausa))` before every drawing call. Invoking `draw_surface_stretched(surf_pausa, ...)` on an invalidated handle causes GameMaker to throw a fatal runtime error and crash. By checking existence and providing a fallback `draw_rectangle` fill, you ensure the game remains functional even if the surface is lost while the game is minimized.
Practical Exercises
Solidify your understanding by adding the following enhancements to your platformer project:
1. **Exit Confirmation Submenu:** Refactor the 'Main Menu' option so that, instead of switching rooms immediately, it prompts the player with 'Are you sure? Yes / No'. Selecting 'No' should restore the primary pause menu (Resume, Restart, Main Menu).
2. **Animated Menu Selector:** Replace the static arrow characters `"> " + opciones[i] + " <"` with a smooth oscillating sprite cursor using `sin(current_time * 0.005) * 8` to create a pulsing horizontal wave effect alongside the selected item.
3. **Room Guard Checks:** Prevent `obj_pausa` from activating when the player is already on the title screen (`rm_menu`). You can achieve this by adding `if (room == rm_menu) exit;` at the beginning of the `Step` event. Confirm that hitting Escape in the main menu does not trigger an accidental pause state.
Verification Checklist
Test and verify your implementation against these criteria:
• Pressing Escape mid-jump suspends the player character in midair; all enemies, particle effects, and coin animations freeze instantly. • The display does not flicker or go blank; the frozen frame buffer renders smoothly beneath the semi-transparent overlay. • Menu navigation using arrow keys or W/S feels responsive and cycles accurately without skipping items. • Selecting 'Resume' unfreezes the player with their exact airborne position and velocity intact, without physics hiccups. • Pausing and resuming repeatedly over several dozen cycles shows stable RAM/VRAM usage in your operating system's Task Manager or Activity Monitor, confirming that surfaces are freed correctly.
Summary
- Instance deactivation using instance_deactivate_all suspends game logic, alarms, collisions, and movement for entities in a single step.
- Passing true to the 'notme' parameter ensures the pause controller remains active to handle inputs and draw interface elements.
- Cloning application_surface onto an auxiliary surface preserves the visual game frame at the exact moment of pausing.
- Surfaces reside in volatile VRAM; always check surface_exists() before rendering and free them using surface_free() in Clean Up.
- Always reactivate instances via instance_activate_all() before restarting or changing rooms to prevent memory leaks and missing cleanup cycles.
Practise what you learned
Exercise 1 · 1 point
What is the purpose of the `true` argument (`notme` parameter) when calling `instance_deactivate_all(true)` from `obj_pausa`?
Exercise 2 · 1 point
Place in chronological order the actions executed by the `obj_pausa` controller when pause activation is triggered in its Step event:
- 1Deactivate all other room entities using instance_deactivate_all(true)
- 2Reset the menu selection cursor by setting opcion_actual = 0
- 3Get the canvas width and height using surface_get_width and surface_get_height
- 4Create the temporary surface and copy the game canvas using surface_copy
Exercise 3 · 1 point
When resuming the game or restarting the level from the pause menu, instances must be reactivated using the () function. Additionally, to prevent memory leaks, you must check whether the cloned surface still exists using (surf_pausa) and release it from memory using (surf_pausa).
Sign in to save your progress — Sign in