Introducción al Desarrollo de Videojuegos con GameMaker · Module 10: Level Design, Cameras, and Game Progression

Doors and Multi-Room Transitions

Lesson 48 of 60 · 60 min

Also available in: ES

Introduction to Level Transitions in GameMaker

In 2D platformer development, spatial progression keeps the player engaged. Reaching the end of a stage, crossing a threshold, or opening a gate should feel clear and rewarding. In GameMaker, every level, screen, or menu is structured within an asset called a Room. Switching levels therefore means telling the engine to unload the current room's data and load the next room's content into memory.

While switching rooms can be done with a single line of code, doing so carelessly introduces common issues: the screen cuts abruptly, the player spawns at incorrect coordinates in the new room, or accumulated data (such as coins and lives) unintentionally resets. In this lesson, we will build a modular, elegant, and reusable level-transition mechanic across our entire project, ensuring the player moves from one area to another using an interactive door or goal post and a smooth visual fade.

The Room Change Lifecycle

When executing a room change instruction in GameMaker using GML Code, the engine performs a series of critical cleanup and reconstruction steps in its execution order:

1. The 'Other -> Room End' event triggers on all active instances that contain this event. 2. All instances not marked as 'Persistent' are destroyed and freed from RAM. 3. The graphic structure and tile layers of the room you are leaving are unloaded. 4. Visual assets, background layers, and tilemaps of the new room are loaded. 5. Instances defined in the new room's layers are created, executing their respective 'Create' events. 6. The 'Other -> Room Start' event fires for all instances present in the new room.

💡 Remember that regular instance variables (such as 'x', 'y', or local variables created in an instance's Create event) are wiped out when that instance is destroyed upon exiting the room. To persist a value across room changes, you must store it in a global variable or in a persistent controller object.

Essential GML Room Control Functions

GameMaker provides built-in functions to query and control room flow. Here are the most essential ones used throughout this module:

GML FunctionDescriptionTypical Platformer Use
room_goto(room_id)Immediately transitions to the room specified by its ID.Doors leading to specific predetermined levels.
room_goto_next()Advances to the next room listed in the 'Room Manager'.Progressing through levels in strict linear order.
room_exists(room_id)Returns 'true' if the room ID exists in the project.Safety check before triggering a transition.
room_restart()Restarts the current room from its initial state.Retrying a level after falling into a pit or losing a life.

💡 Avoid calling 'room_goto_next()' directly without verifying if a next room exists via 'room_next(room)'. Trying to navigate past the last room registered in your project will cause GameMaker to throw a fatal error, crashing the game.

Designing the Interactive Object: obj_puerta

Rather than creating a separate door object for every level ('obj_puerta_nivel1', 'obj_puerta_nivel2'), we apply modular design principles: a single 'obj_puerta' object capable of teleporting the player to any target room and any specific coordinates using per-instance variables.

Start by creating a sprite named 'spr_puerta'. This could be a 32x48-pixel wooden frame with a dark interior, or an animated portal drawn in the sprite editor with two or three frames. Set its collision mask to a precise or closed rectangle covering the area where the player will interact.

Next, create the object 'obj_puerta', assign it the 'spr_puerta' sprite, and uncheck 'Solid' (the player needs to overlap with the door rather than colliding with it like a solid wall). In its Create event, define the default values:

// Resource: obj_puerta
// Event: Create

// Target room for this door (default: no specific room)
destino_room = noone;

// Target coordinates where the player will spawn in the new room
destino_x = 0;
destino_y = 0;

// Flag to prevent multiple activations across consecutive frames
activada = false;

GameMaker allows overriding these variables on individual instances placed in the room. When placing an instance of 'obj_puerta' in a room, double-click it in the Room Editor, click 'Variables', and specify the target room name (such as 'rm_nivel_2') along with the exact arrival coordinates. You can also configure this using the instance's Creation Code directly in the room.

Detecting Activation: Collision and Interactivity

Classic platformers typically handle doors in one of two ways: entering automatically on contact (common for end-of-level goal posts or pipe exits), or pressing an interact key like the Up arrow or 'W' while standing in front of it. We will implement the second approach, giving players full control and preventing accidental room changes when moving near the doorway.

// Resource: obj_puerta
// Event: Step

// If the door has already been triggered, exit early
if (activada) exit;

// Check if the player is overlapping with the door
if (place_meeting(x, y, obj_jugador))
{
    // Check if the player presses the interact key / Up arrow
    var tecla_entrar = keyboard_check_pressed(vk_up) || keyboard_check_pressed(ord("W"));
    
    if (tecla_entrar)
    {
        // Validate that the target room exists in the project
        if (destino_room != noone && room_exists(destino_room))
        {
            activada = true;
            
            // Store target coordinates in global variables
            global.jugador_spawn_x = destino_x;
            global.jugador_spawn_y = destino_y;
            global.aplicar_spawn = true;
            
            // Create the transition effect instead of cutting abruptly
            var trans = instance_create_depth(0, 0, -9999, obj_transicion_fade);
            trans.sala_objetivo = destino_room;
        }
        else
        {
            show_debug_message("Warning: The door at (" + string(x) + ", " + string(y) + ") does not have a valid target room assigned.");
        }
    }
}
Schematic diagram illustrating the level transition flow: the player enters a door in Level 1, a fade-to-black sequence triggers, and the character respawns at the designated target coordinates in Level 2.
Figure 1: Control flow when interacting with a door and repositioning the player in the next room. · AI-generated image

Controlling the Spawn Point in the New Room

A frequent pitfall for beginners is assuming the character will retain their position or that the new room will know where to place them. Unless 'obj_jugador' is marked persistent, loading 'rm_nivel_2' spawns the player at the exact coordinates set inside that room's editor.

To resolve this cleanly without complex pointer management, we use the 'Global Spawn Variables' pattern ('global.jugador_spawn_x' and 'global.jugador_spawn_y'). We initialize these variables in a game controller object or a global boot script, and configure the player object to read those coordinates upon entering any new room.

In the 'Create' event of our game controller object (for example, 'obj_game_manager'), initialize the variables:

// Resource: obj_game_manager
// Event: Create

global.jugador_spawn_x = -1;
global.jugador_spawn_y = -1;
global.aplicar_spawn = false;

Then, in 'obj_jugador', add the 'Other -> Room Start' event. This event fires automatically whenever a new room begins executing:

// Resource: obj_jugador
// Event: Other -> Room Start

// Check if the previous door requested repositioning the player
if (global.aplicar_spawn)
{
    x = global.jugador_spawn_x;
    y = global.jugador_spawn_y;
    
    // Reset the flag so regular room restarts retain normal placement
    global.aplicar_spawn = false;
    
    // Reset accumulated velocities to avoid shooting forward on entry
    hspeed = 0;
    vspeed = 0;
    if (variable_instance_exists(id, "vel_x")) vel_x = 0;
    if (variable_instance_exists(id, "vel_y")) vel_y = 0;
}

Visual Transition Effect: Fade to Black

Switching rooms instantly creates an unpolished jarring cut. A smooth fade out and fade in transition gives your game a polished, retro console feel. To achieve this, we will build a dedicated object: 'obj_transicion_fade'.

Mark this object as 'Persistent' in its properties. Persistence is critical here: the object must start darkening the screen in Level 1, survive the internal 'room_goto' call, and then gradually reveal the screen once inside Level 2 before destroying itself.

// Resource: obj_transicion_fade
// Event: Create

// Visual setup
alpha = 0;              // Opacity level (0 = transparent, 1 = solid black)
velocidad_fade = 0.04;  // Rate of change per frame
estado = "oscureciendo"; // States: "oscureciendo" (fading out) or "aclarando" (fading in)
sala_objetivo = noone;  // Target room to transition into

In the Step event, we handle the opacity animation logic and trigger the room swap at full darkness:

// Resource: obj_transicion_fade
// Event: Step

if (estado == "oscureciendo")
{
    alpha += velocidad_fade;
    
    // When the screen is fully black
    if (alpha >= 1)
    {
        alpha = 1;
        
        // Switch rooms while the screen is completely covered
        if (sala_objetivo != noone && room_exists(sala_objetivo))
        {
            room_goto(sala_objetivo);
        }
        
        // Switch phase: now fade in the new room
        estado = "aclarando";
    }
}
else if (estado == "aclarando")
{
    alpha -= velocidad_fade;
    
    // Once fully transparent, the transition is complete
    if (alpha <= 0)
    {
        alpha = 0;
        instance_destroy(); // Destroy object to free memory
    }
}

To draw the black rectangle over the entire screen regardless of camera positioning, use the 'Draw GUI' event. GUI space is completely independent of in-game world room coordinates:

// Resource: obj_transicion_fade
// Event: Draw GUI

// Only draw if the overlay has visible opacity
if (alpha > 0)
{
    // Retrieve GUI layer dimensions
    var ancho_gui = display_get_gui_width();
    var alto_gui = display_get_gui_height();
    
    // Set color and alpha
    draw_set_color(c_black);
    draw_set_alpha(alpha);
    
    // Draw a rectangle covering the entire viewport
    draw_rectangle(0, 0, ancho_gui, alto_gui, false);
    
    // BEST PRACTICE: Always restore default drawing states
    draw_set_alpha(1.0);
    draw_set_color(c_white);
}

Locking Movement During Transitions

A key finishing touch is managing player control while the screen fades out. Without locking inputs, the player could blindly walk into pits or enemies during the fade. To prevent this, temporarily bypass control processing while an active transition exists.

In the 'Step' event of 'obj_jugador', right before handling keyboard inputs for horizontal movement and jumping, add the following safeguard:

// Resource: obj_jugador
// Event: Step (at the start of the event)

// Freeze player movement if an active screen transition exists
if (instance_exists(obj_transicion_fade))
{
    vel_x = 0; // Cancel horizontal inertia
    exit;      // Exit Step event early; ignore input handling
}

Practical Consolidation Exercises

Complete the following hands-on challenges to test your understanding and solidify the level-transition architecture in your platformer project:

Exercise 1: Locked Door Requiring Collectibles

Create a variant of the door or extend 'obj_puerta' to include a 'monedas_requeridas' variable. The door should only unlock and start the transition if the player's collected coin count meets or exceeds that threshold. Otherwise, display a brief indicator or switch sub-images to show that the door is locked. Validation criteria: Place a door configured with 'monedas_requeridas = 5' in a test room. Verify that pressing the interact key with 3 coins does not trigger the transition, and that collecting the 5th coin allows normal passage.

Exercise 2: Bidirectional Doors Between Two Rooms

Set up a two-way traversal system. In 'rm_nivel_1', place a door leading into a cave interior ('rm_cueva'). Inside 'rm_cueva', place another door allowing the player to return directly in front of the outside entrance in 'rm_nivel_1'. Validation criteria: The player must be able to move between the overworld and the cave seamlessly as many times as desired, spawning with zero velocity right in front of the corresponding door without clipping into nearby walls or ground geometry.

Exercise 3: Custom Visual Transition and Ambient Audio

Modify 'obj_transicion_fade' to accept a 'color_fade' parameter in its Create event (defaulting to 'c_black'). Add a sound effect for opening a wooden door or a magical portal chime using 'audio_play_sound()' the instant the player activates the door. Validation criteria: Configure a door that uses 'c_white' to simulate a flash of blinding light when exiting to an outdoor area. Verify that the audio triggers clearly and that the Draw GUI rectangle uses the configured color.

Summary

  • Rooms in GameMaker represent full levels or scenes; switching between them destroys non-persistent instances by default.
  • The 'room_goto(room_id)' function jumps to a specific room, while 'room_exists()' checks that the target ID is valid to prevent game crashes.
  • A modular 'obj_puerta' configured via instance variables ('destino_room', 'destino_x', 'destino_y') eliminates redundant door objects.
  • Reliable player repositioning is achieved through global spawn variables evaluated during the 'Other -> Room Start' event.
  • A persistent 'obj_transicion_fade' drawn in 'Draw GUI' provides a polished fade-to-black visual transition and safely locks player input during level swaps.

Practise what you learned

Exercise 1 · 1 point

Order the steps GameMaker executes chronologically during a room transition lifecycle:

  1. 1The graphical layout and tile layers of the outgoing room are unloaded.
  2. 2The 'Other -> Room End' event triggers across all active instances.
  3. 3Instances in the new room are created, executing their Create events.
  4. 4The 'Other -> Room Start' event triggers across all present instances.
  5. 5Non-persistent instances are destroyed and freed from RAM.
  6. 6Visual assets and layers of the incoming room are loaded.
You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 2 · 1 point

Why is it essential to mark the 'obj_transicion_fade' object as persistent ('Persistent') when implementing a fade transition?

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

Exercise 3 · 1 point

When setting up 'obj_puerta' in its Create event, we define variables to make its behavior modular. The variable responsible for storing the destination room is , while the coordinates where the player will spawn default to 0 using the variables and .

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

Sign in to save your progress — Sign in