Introducción al Desarrollo de Videojuegos con GameMaker · Module 7: Enemies, Basic AI, and Gameplay Mechanics
Checkpoints
Lesson 35 of 60 · 55 min
Also available in: ES
The Role of Checkpoints in Game Design
In 2D platformer development, game pacing and the player frustration curve are closely tied to the distance between failing and resuming gameplay. Forcing a player to restart an entire ten-minute level after mistiming a single jump makes the experience punishing and exhausting. Checkpoints serve as temporary save points held in the game's active memory that preserve progress, allowing the character to respawn at a safe location near their latest achievement.
From an architectural standpoint in GameMaker, a checkpoint does not necessarily require saving files to disk (a topic usually reserved for persistent save files). In its purest and most efficient form for individual levels, it simply remembers the spatial `(x, y)` coordinates of a validated zone within the current room, while resetting certain player vitals such as residual velocity and hit points. In this lesson, you will build this system from scratch using GML Code, integrating it cleanly with the health and collision mechanics developed in earlier lessons.
Technical Strategy: Where and How to Store Coordinates?
For the respawn position to persist even when the `obj_player` instance is destroyed, reset, or repositioned suddenly, the saved coordinates must not depend solely on the player's own lifecycle. If a player dies and their instance is destroyed (`instance_destroy()`), all of their local and instance variables vanish from GameMaker's memory. There are two primary professional approaches to handle this in beginner and intermediate projects: using global variables accessible from any entity, or employing a persistent controller object (`obj_game_controller`).
In our project, we will use global variables (`global.checkpoint_x` and `global.checkpoint_y`). This approach is straightforward, requires minimal boilerplate code, remains easy to read for beginners, and allows checkpoint objects (`obj_checkpoint`), hazard zones, and the player instance to inspect and update these values at any point in the game loop.
💡 Remember: variables prefixed with 'global.' exist within GameMaker's virtual machine global context. Unlike variables declared with 'var' (temporary to their code block) or unprefixed instance variables (bound to the current instance), global variables retain their values across event executions and instance destructions.
Designing the Visual Asset: Creating spr_checkpoint
To give players intuitive feedback that they have reached a safe spot, the checkpoint must provide an immediate visual response. We will create a dedicated sprite—such as a mystical totem, a flag, or a sci-fi beacon—with at least two visual states: inactive and activated.
Open GameMaker's Asset Browser, right-click your sprites folder, and choose Create -> Sprite. Name it `spr_checkpoint`. Set its size to 32x32 or 32x64 pixels depending on your player's scale. Draw or import two sub-images (frames): frame 0 represents the inactive checkpoint or lowered flag (grey or red), while frame 1 represents the active checkpoint or raised flag (bright green or glowing blue). Make sure to set the Origin to 'Bottom Center', ensuring the base of the object aligns flush with the floor tiles in your room.

Programming the obj_checkpoint Object
Now let's create the interactive object in the Asset Browser: Create -> Object, naming it `obj_checkpoint` and assigning it `spr_checkpoint`. This object must detect player contact, activate its visual state, avoid redundant re-activations, and optionally deactivate previous checkpoints if you want sequential save behavior.
We begin by configuring its Create event to initialize its internal state. Here, we freeze animation on the inactive frame using the built-in variable `image_speed`.
// Object: obj_checkpoint
// Event: Create
// Checkpoint starts inactive and without looping animation
is_active = false;
image_speed = 0;
image_index = 0;Next, we handle player interaction. This can be done via a dedicated Collision event with `obj_player` or by checking for overlaps in the checkpoint's Step event with `place_meeting()`. A dedicated collision event is clean, performant, and beginner-friendly. Add a Collision -> Objects -> `obj_player` event to `obj_checkpoint` and insert the following code:
// Object: obj_checkpoint
// Event: Collision with obj_player
// Only trigger if this checkpoint hasn't been activated yet
if (!is_active)
{
// Visually deactivate all other checkpoints in the room for consistency
with (obj_checkpoint)
{
is_active = false;
image_index = 0;
}
// Activate this specific checkpoint
is_active = true;
image_index = 1;
// Store this checkpoint's position as the latest global safe position
// We use the checkpoint's (x, y) coordinates, aligned with its base
global.checkpoint_x = x;
global.checkpoint_y = y;
// Console debug message
show_debug_message("Checkpoint activated at X: " + string(global.checkpoint_x) + ", Y: " + string(global.checkpoint_y));
}Let's examine the `with (obj_checkpoint)` statement. The `with` statement in GML iterates through every existing instance of that object type in the room. Running this forces every flag back to its inactive frame (`image_index = 0`), before the collided instance sets its own `is_active = true` and switches to `image_index = 1`. This guarantees that only one checkpoint is active at any time, eliminating ambiguity in the game world.
Global Initialization and Default Safe Position
A common pitfall when implementing checkpoints is assuming the player will always touch one before dying. If the player falls down a pit or collides with an enemy in the opening stretch of a level before reaching an `obj_checkpoint`, the engine will attempt to read `global.checkpoint_x` and `global.checkpoint_y`. If these variables have not been declared yet, GameMaker will halt execution with an 'unrecognized variable' fatal error.
To prevent this crash, we must initialize the global variables with the player's initial room position. The ideal place to do this is in the Create event of an initialization or controller object (`obj_game_controller`), or temporarily inside the Create event of `obj_player` when the room loads:
// Object: obj_player
// Event: Create (add at the end of your existing movement variables)
// If global checkpoint variables do not exist yet in this session, define them
if (!variable_global_exists("checkpoint_x"))
{
global.checkpoint_x = x;
global.checkpoint_y = y;
}The `variable_global_exists()` function checks whether a global variable of that name has been registered in memory. When the level boots up for the first time, GameMaker finds that it does not exist and saves the player's starting coordinates (`x` and `y` where the instance was placed in the room) as the default fallback. Once the player touches a checkpoint flag, those globals will already exist with updated values, so this block will not overwrite saved progress.
Respawn Mechanics: Handling Damage and Falls
The essential final piece of the checkpoint loop is the respawn mechanic. What happens when the character is defeated by an enemy or falls past the bottom boundary of the room? Rather than restarting the entire room using `room_restart()`—which would wipe collected items and reset all defeated enemies—we can reposition the player instance to the safe coordinates and restore their health.
Let's implement a respawn method inside `obj_player`. Add this code block to the Create event of `obj_player`:
// Object: obj_player
// Event: Create
// Method to return the player to their last safe checkpoint
respawn = function()
{
// Move coordinates to the last saved checkpoint
x = global.checkpoint_x;
y = global.checkpoint_y;
// Clear any residual momentum or speed to avoid respawning sliding or falling
hspd = 0;
vspd = 0;
// Reset player health if the module's health system is implemented
if (variable_instance_exists(id, "hp"))
{
hp = max_hp;
}
// Informational debug message
show_debug_message("Player respawned at safe location.");
};Now we trigger this function in response to two classic failure conditions: falling into a bottomless pit and running out of hit points in combat. In the Step event of `obj_player`, add boundary and health checks:
// Object: obj_player
// Event: Step (add inside your state-checking logic or at the end)
// 1. Pit fall detection (beyond the bottom edge of the room)
if (y > room_height + 64)
{
respawn();
}
// 2. Health depletion detection
if (variable_instance_exists(id, "hp") && hp <= 0)
{
// If the player still has lives remaining (Module 7 lives system)
if (variable_instance_exists(id, "lives_count") && lives_count > 1)
{
lives_count -= 1;
respawn();
}
else
{
// If no lives remain, restart the game or room
room_restart();
}
}Level Design Considerations for Safe Checkpoints
Writing the code is only half the battle; where you place checkpoints in the Room Editor determines whether the game feels fair and rewarding or cheap and frustrating. Poor placement can cause softlocks or instantaneous death loops.
| Design Criterion | Bad Practice | Recommended Practice |
|---|---|---|
| Enemy Proximity | Placing the checkpoint directly on an active enemy's patrol path. | Positioning the checkpoint on an elevated ledge or platform safely out of reach of patrol paths and projectiles. |
| Ledge Margin | Placing the flag right at the edge of a pit or on collapsible platforms. | Ensuring at least 64 pixels (2 standard grid tiles) of flat, solid ground on either side of the flag. |
| Velocity and Orientation | Respawning with stored vertical momentum falling through a one-way platform. | Zeroing out `vspd = 0` and `hspd = 0` on respawn and confirming the collision mask is clear of solid geometry. |
| Frequency | Only one checkpoint at the start and another at the end of a five-minute level. | Placing checkpoints right after demanding, high-skill platforming sequences. |
Pay close attention to vertical origin points: if the origin of `spr_checkpoint` is set to 'Bottom Center', placing it in the room means its coordinates will sit directly flush with the floor line. When moving the player via `x = global.checkpoint_x; y = global.checkpoint_y;`, ensure that `spr_player` also uses 'Bottom Center' as its origin. If the player's origin were set to 'Top Left', aligning to the flag's coordinate would spawn the character embedded inside the ground tiles, causing the collision system to trap them permanently.
Practical Exercises
Test your understanding of spatial persistence and control flow by implementing the following improvements in your platformer project.
Summary
- Checkpoints eliminate unnecessary player frustration by enabling safe intermediate respawns without restarting the entire level.
- Using global variables (`global.checkpoint_x` and `global.checkpoint_y`) is a simple, robust strategy for storing coordinates that outlive the player instance lifecycle.
- Initializing default coordinates at room start (using `variable_global_exists()`) is essential to prevent undefined variable crashes if the player dies before reaching a checkpoint.
- When moving the player in a `respawn()` method, always reset horizontal and vertical speeds to zero (`hspd = 0; vspd = 0`) to prevent sliding or falling through terrain upon arrival.
- Level design requires placing checkpoints on solid, flat surfaces well away from enemy patrol routes, incoming projectiles, and unstable platforms.
Practise what you learned
Exercise 1 · 1 point
Why is it recommended in the lesson to store respawn coordinates in global variables (such as `global.checkpoint_x` and `global.checkpoint_y`) instead of instance variables inside `obj_player`?
Exercise 2 · 1 point
To prevent a fatal undefined variable error at the start of the level, you can check whether the variable already exists using the ("checkpoint_x") function. Additionally, in the design of `spr_checkpoint`, frame represents the inactive or unlit checkpoint, while frame corresponds to the active or lit state.
Exercise 3 · 1 point
Match each GML code element or setting to its purpose in the checkpoint system:
- image_speed = 0;
- with (obj_checkpoint)
- variable_global_exists()
- Origin set to 'Bottom Center'
Sign in to save your progress — Sign in