Introducción al Desarrollo de Videojuegos con GameMaker · Module 12: Debugging, Optimization, Exporting, and Publishing
Using the GameMaker Debugger
Lesson 56 of 60 · 60 min
Also available in: ES
Introduction to Debugging in GameMaker
Throughout previous modules, you have created characters with jumping mechanics, solid platform collisions, patrolling enemies with basic AI, graphical user interfaces, and interconnected levels. However, as a game project grows in complexity, anomalies inevitably arise: a character clipping through a wall under specific conditions, a coin counter accumulating unexpected values, or an enemy suddenly freezing mid-patrol. In software and game development, these logical flaws are commonly known as bugs, and the methodical process of identifying, isolating, and fixing them is called debugging.
Many novice developers attempt to diagnose issues by blindly modifying code or littering their scripts with show_debug_message() calls. While printing messages to the Output console is a valid technique for quick checks, it falls short when you need to inspect the game's internal state on an exact frame. GameMaker includes a professional diagnostic tool called the GameMaker Debugger. This suite lets you pause engine execution, inspect variable values directly in RAM, step through code line by line, and trace the exact call sequence that triggered an unforeseen error.
💡 Key difference: show_debug_message() prints text strings to the Output log while the game continues running at 60 frames per second, which often floods the console. The interactive Debugger, in contrast, halts game time completely and freezes the engine on a specific line, allowing you to inspect every variable of any active instance without disrupting the timing of execution.
Error Types: Syntax, Compile, and Runtime
To use the debugger effectively, it is essential to distinguish between the three primary categories of errors that can occur during a GameMaker project's lifecycle:
| Error Type | Detection Point | Common Cause | Diagnostic Tool |
|---|---|---|---|
| Syntax Error | While writing code | Missing semicolon, unclosed brackets, misspelled keywords. | Red underlines in the Code Editor and the Syntax Errors panel. |
| Compile Error | When pressing Play (F5) or Debug (F6), before the game launches | Calling a nonexistent resource, duplicate macros, naming conflicts. | Compiler Output log showing an abort code. |
| Runtime / Logic Error | While the game is actively running | Division by zero, reading an uninitialized variable, array out of bounds, ghost collisions. | Fatal Error window and the interactive Debugger. |
Syntax and compile errors prevent the game from even building a temporary executable. In contrast, runtime errors and logic bugs allow the game to launch normally, but cause incorrect behavior or crash the application during play. It is in this latter scenario that the debugger becomes an indispensable tool for any game programmer.
Running the Game in Debug Mode
To run your project with debugging tools enabled, do not use the standard Play button (or F5). Instead, click the bug-shaped icon next to the Play button on GameMaker's top toolbar, or press the F6 keyboard shortcut. This compiles the project with debugging symbols injected and opens two separate windows: the game window (running normally) and the GameMaker Debugger window.
The debugger interface is organized into several strategic panels: the top execution control bar (with pause, continue, and stepping controls), the resource and instance explorer (Source / Instances), the active source code view (Code View), the variable inspector (Variables View), and the custom expression monitor (Watch View). While the game runs uninterrupted, the debugger tracks performance metrics, RAM usage, and real FPS. However, its true power is unlocked when you freeze time using breakpoints.

Breakpoints and Step-by-Step Navigation
A breakpoint is a visual marker that tells the debug compiler: 'Run the game at full speed, but the exact moment you are about to execute this specific line of code, halt game execution entirely and hand control over to the developer.' Breakpoints do not modify code behavior or alter variables; they simply pause execution just before the flagged line is evaluated by the processor.
To set a breakpoint in GameMaker, open any script or object event in the code editor and left-click the gray margin to the left of the line number, or place your cursor on that line and press F9. A bright red circle will appear, confirming the breakpoint is set. You can set as many breakpoints as needed across different objects and events. When the game in debug mode hits that code block, the game window freezes on the current frame, and the debugger highlights the active line in yellow.
Once the game is paused at a breakpoint, four fundamental controls in the debugger toolbar allow you to steer program execution with pinpoint precision:
| Command | Shortcut | Function and Behavior |
|---|---|---|
| Continue | F5 | Resumes standard game execution at full speed until the game closes or encounters the next active breakpoint. |
| Step In | F11 | Advances to the immediate next instruction. If the current line calls a script or custom function, steps inside that function to inspect it. |
| Step Over | F10 | Executes the current line entirely without stepping into internal function calls, landing on the next line of the current event. |
| Step Out | Shift + F11 | Executes the rest of the current script or function to completion and pauses immediately on the line that originally called it. |
Real-Time Variable Inspection: Local, Instance, and Global
When execution pauses at a breakpoint, the Variables panel springs to life. This panel is organized hierarchically into tabs and categories that mirror GameMaker's memory architecture:
1. Local Variables (Local): Displays all variables declared with the 'var' keyword inside the currently executing event or function. These variables exist only for the brief duration of that specific code block and are discarded upon exiting. Inspecting them here helps confirm whether temporary calculations, such as vector distances or loop indices, contain the expected values.
2. Instance Variables (Self / Instance): Displays all variables bound to the object instance executing the current event (such as 'x', 'y', 'hsp', 'vsp', 'hp', 'is_grounded', or 'sprite_index'). You can expand the full list to view both built-in engine variables and custom variables initialized in the Create event. Furthermore, the debugger lets you double-click any numeric, boolean, or string value and edit it live to see how the game responds without restarting or recompiling.
3. Global Variables (Global): Displays the state of all global variables across the project (declared using the global. prefix). This is the ideal place to monitor overall game state, such as total coin count (global.coins), the current level ID, or audio settings.
The Custom Watch Window
In medium or large projects, an object can have dozens of instance variables, making it tedious to find a specific value in the general tab. To solve this, the debugger provides the Watch Window. In this panel, you can right-click and select 'Add Watch' to input any variable or expression you want to monitor closely, such as 'obj_player.vsp', 'point_distance(x, y, obj_player.x, obj_player.y)', or 'keyboard_check(vk_space)'.
As you use Step Over or Step In, expressions in the Watch list are re-evaluated in real time. If a variable's value changes from the previous step, the debugger highlights the row (typically in red or yellow), allowing you to pinpoint the exact moment a variable undergoes an unwanted mutation or turns into an invalid value such as NaN (Not a Number) or undefined.
Practical Example: Diagnosing an Infinite Jump Bug
Let's examine a real-world case study from our retro platformer project. Suppose we have programmed movement and jump physics in the Step event of obj_player. While playtesting, we discover a critical bug: the player can jump indefinitely in mid-air without touching the ground, as if equipped with an infinite jetpack.
Below is the code implemented in obj_player inside the Step event:
// Object: obj_player
// Event: Step
// 1. Capture control inputs
var _key_left = keyboard_check(vk_left) || keyboard_check(ord("A"));
var _key_right = keyboard_check(vk_right) || keyboard_check(ord("D"));
var _key_jump = keyboard_check_pressed(vk_space) || keyboard_check_pressed(ord("W"));
// 2. Calculate horizontal movement
var _move = _key_right - _key_left;
hsp = _move * walk_speed;
// 3. Apply constant gravity while airborne
vsp += grv;
// 4. Jump logic (Suspected point of failure)
if (_key_jump && is_grounded)
{
vsp = -jump_speed;
}
// 5. Ground contact check
if (place_meeting(x, y + 1, obj_solid))
{
is_grounded = true;
}
else
{
// Line where the developer forgot to update the condition properly
// is_grounded = false; // <-- Accidentally commented out during testing
}
// 6. Movement execution and vertical collisions
if (place_meeting(x, y + vsp, obj_solid))
{
while (!place_meeting(x, y + sign(vsp), obj_solid))
{
y += sign(vsp);
}
vsp = 0;
}
y += vsp;The Call Stack
When your code is structured using modular functions, constructors, methods, and chained events, bugs often occur not in the root event, but deep inside a function called several layers down. The Call Stack is the chronological execution log that records the exact sequence of function calls that brought the processor to the current line.
Imagine that in the Step event of an enemy (obj_enemy_patrol), you call a script named 'enemy_attack_player()', and within that function another custom function 'damage_player(target, amount)' is called. If execution pauses at a breakpoint inside 'damage_player', the Call Stack window will show these layers stacked from bottom to top:
// Conceptual representation of the Call Stack:
[3] damage_player() <- Current paused line (Top of stack)
[2] enemy_attack_player() <- Function that invoked damage_player()
[1] obj_enemy_patrol : Step <- Initial event that triggered the sequenceBy double-clicking any lower layer in the Call Stack window, the debugger switches the code view to display the exact line in that caller function that initiated the call. More importantly, the Variables view switches context to display the arguments and local variables active at that moment in that frame, allowing you to verify whether a parameter passed down the chain was already invalid at its source.
Anatomy of a Fatal Runtime Error
Even experienced developers make errors that the compiler cannot predict. When this occurs, GameMaker halts the game immediately and displays a 'Fatal Error' pop-up window. Far from being random noise, this dialog provides precise technical information that allows you to pinpoint and resolve the problem quickly once you know how to read it.
💡 Watch out for naming discrepancies: In GML, variable names are case-sensitive depending on configuration, and a small typo like writing 'Player_speed' instead of 'player_speed' will immediately trigger a 'Variable not set before reading it' exception.
Professional Debugging Best Practices
To keep debugging from becoming a frustrating and messy chore, adopt a disciplined workflow based on structured methods:
First, isolate variables. If a complex collision calculation produces unexpected results, break the formula down into clearly named intermediate local variables. This lets you place a breakpoint on that line and inspect the partial value of each sub-expression in the debugger's Local Variables tab, rather than trying to decipher a single dense expression.
Second, clear obsolete breakpoints. As a project grows, forgotten breakpoints scattered across various scripts and objects make running in debug mode tedious, as execution stops constantly for resolved issues. The Breakpoints tab in the debugger provides a comprehensive list of all active markers across the project, allowing you to remove them all with one click or toggle them off selectively.
Third, use guard clauses and existence checks. Rather than assuming an instance exists before accessing its properties, wrap the block with instance_exists(). For example, before referencing 'target.x', always check 'if (instance_exists(target))'. This prevents an instance destroyed on the previous frame from triggering an unhandled engine crash.
Practical Exercises
Test your diagnostic skills and command of the debugger with the following three hands-on exercises designed to solidify control over program flow and variable tracking.
Summary
- The GameMaker Debugger (Debug Mode, launched with F6) is the primary tool for diagnosing logic bugs and inspecting game state without cluttering output with scattered log messages.
- Breakpoints (toggled with F9) freeze engine execution at the selected line, allowing you to examine variables and environment state before the instruction executes.
- Stepping controls navigate program flow with precision: Step Over (F10) moves line by line, Step In (F11) enters called functions or scripts, and Continue (F5) resumes the game at full speed.
- The Variables panel categorizes data into Local (function-scoped), Instance / Self (object-scoped), and Global (game-wide), supporting live value editing while paused.
- The Watch window lets you monitor specific critical variables and expressions in real time, visually highlighting value changes between steps.
- The Call Stack details the hierarchy of functions and events that led to the current breakpoint, making it indispensable for tracking down bugs in modular code.
- Fatal runtime error messages provide precise diagnostic data, including object name, event, line number, and error cause (such as reading uninitialized variables or out-of-bounds indexing).
Practise what you learned
Exercise 1 · 1 point
What is the main difference between using the show_debug_message() function and using GameMaker's interactive Debugger?
Exercise 2 · 1 point
Match each Debugger category or panel with its function:
- Local Variables (Local)
- Instance Variables (Self / Instance)
- Global Variables (Global)
- Watch Window
Exercise 3 · 1 point
To run the game in Debug Mode, press the key, whereas to set or remove a breakpoint on a line of code, press the key.
Sign in to save your progress — Sign in