Introducción al Desarrollo de Videojuegos con GameMaker · Module 11: Complete Development of a 2D Platformer

Main Level Assembly and Transition Screens

Lesson 52 of 60 · 60 min

Also available in: ES

Game Flow and Screen Architecture

Up to this point in the course, you have built isolated mechanics: a player character with tight physical controls, platform and tilemap collisions, patrolling enemies, collectible coins, health bars, and interfaces drawn in the Draw GUI event. However, a commercial video game is not merely a collection of disconnected prototypes tested straight from the editor; it requires a structured and cohesive game flow.

Game flow describes the continuous journey the player experiences from launching the application to quitting it. For players to perceive a polished experience, the game must manage clean state changes: booting into a technical initialization room, presenting a functional main menu, transitioning into the fully assembled playable level, and culminating in resolution screens (Victory or Game Over) that allow restarting the loop without memory leaks or orphaned variables.

In GameMaker, this journey is orchestrated through an ordered sequence of Rooms and persistent control objects. If you switch rooms abruptly using a direct call to the built-in room_goto() function, the visual cut feels jarring, breaks immersion, and can cause visual stutter if the new room takes a frame or two to upload textures. For this reason, professional assembly demands an intermediate visual transition layer that masks state changes.

Schematic diagram of room navigation flow: from initialization to menu, main level, victory and defeat screens, and back to menu.
Room flow structure and cyclical navigation in the final project. · AI-generated image

Room Planning and Hierarchy in the Asset Browser

The first step is auditing GameMaker's Asset Browser. The order in which rooms appear inside the 'Rooms' section in the room manager defines the default starting room of your project. While during development we often launch directly into whatever room we are currently editing, the final compiled build will always run the very first room listed from top to bottom.

For this complete platformer, we will establish a strict architecture composed of five essential rooms:

Room NameBase ResolutionPrimary FunctionRequired Objects
rm_init640 x 360Technical initialization of global variables, fonts, and persistent systems.obj_game_controller, obj_transition
rm_menu640 x 360Title display, minimal instructions, and interactive start button.obj_menu_ui
rm_level_11920 x 720 (or larger)Fully assembled main level with tilemaps, player, enemies, camera, and goals.obj_player, obj_camera, obj_hud, enemies, goal
rm_game_over640 x 360Defeat screen shown after losing all lives. Allows retrying or returning to menu.obj_game_over_ui
rm_win640 x 360Victory screen reached upon clearing the goal alive. Displays final score.obj_win_ui

The rm_init room lasts for exactly one frame. Its sole purpose is to execute bootstrap code inside the Create event of a global initialization object and immediately transition to the main menu. This technique prevents repetitive global variable declarations inside rooms that will be visited multiple times.

The Visual Transition System: obj_transition

To execute smooth fades (fade out / fade in), we will create a persistent controller object called obj_transition. A persistent object (checked 'Persistent' in its properties) is not destroyed when changing rooms; it carries over intact between scenes while preserving its variables and active states.

The transition manager will operate as a simple three-state machine: idle, darkening the current screen (fade out), and brightening the new screen (fade in). We will use the Draw GUI event to render a solid black rectangle covering the entire viewport interface, unaffected by camera movement or world coordinates.

Create the object obj_transition in your Asset Browser, check its 'Persistent' box, and add the following logic to its Create event:

// Create Event of obj_transition
// Visual fade controls
alpha = 0;
fade_speed = 0.04;
target_room = noone;

// States: 0 = idle, 1 = fading out, 2 = fading in
transition_state = 0;

// Ensure the object does not duplicate if one already exists in memory
if (instance_number(obj_transition) > 1) {
    instance_destroy();
}

The next step is programming the progression logic in the Step event. When a transition is triggered (state 1), the alpha value increases progressively. The moment the screen turns completely black (alpha >= 1), we call room_goto() behind the dark veil and switch to state 2 to begin fading into the new room:

// Step Event of obj_transition
switch (transition_state) {
    case 1: // Fade Out phase
        alpha += fade_speed;
        if (alpha >= 1) {
            alpha = 1;
            if (target_room != noone && room_exists(target_room)) {
                room_goto(target_room);
            }
            transition_state = 2; // Transition to fade-in phase in the new room
        }
        break;
        
    case 2: // Fade In phase
        alpha -= fade_speed;
        if (alpha <= 0) {
            alpha = 0;
            target_room = noone;
            transition_state = 0; // Return to idle
        }
        break;
        
    case 0: // Idle
    default:
        alpha = 0;
        break;
}

To draw the overlay on top of all game elements, we use the Draw GUI event. By utilizing GUI rendering functions, we detach ourselves from world coordinates and camera view matrices:

// Draw GUI Event of obj_transition
if (alpha > 0) {
    var _gui_w = display_get_gui_width();
    var _gui_h = display_get_gui_height();
    
    draw_set_color(c_black);
    draw_set_alpha(alpha);
    draw_rectangle(0, 0, _gui_w, _gui_h, false);
    
    // Reset default drawing parameters so other objects remain unaffected
    draw_set_alpha(1.0);
    draw_set_color(c_white);
}

To make this system accessible from any script or event across the project, we will wrap the transition call inside a global convenience function. Create a Script named scr_transitions and add the following:

/// @function transition_to(_target)
/// @description Initiates a smooth transition to the specified room.
/// @param {Asset.GMRoom} _target Target room asset.
function transition_to(_target) {
    if (instance_exists(obj_transition)) {
        with (obj_transition) {
            if (transition_state == 0) {
                target_room = _target;
                transition_state = 1;
            }
        }
    } else {
        // Safety fallback if obj_transition has not been instantiated
        room_goto(_target);
    }
}

💡 Golden Rule of Drawing in GML: whenever you modify draw_set_alpha() or draw_set_color(), always restore them to their default values (1.0 and c_white) at the end of the block. Otherwise, any subsequent draw calls will inherit that transparency or tint, causing subtle HUD glitches and discolored character sprites.

Main Level Assembly: Layers, Actors, and Triggers

Now that the visual bridge is in place, we can assemble the interactive level inside rm_level_1. In earlier lessons, you set up tilemaps and placed standalone test instances. Now we will integrate the full environment using a strict Room Layer hierarchy that guarantees proper render order and optimal performance.

Open rm_level_1 and set up the following layer stack in the layer inspector, listed from top (foreground visual layer) to bottom (deepest background layer):

Layer NameLayer TypeContents and Purpose
Foreground_DetailsAsset / Tile LayerCanopy branches, hanging chains, or pillars passing in front of the player.
Instances_ControllersInstance LayerLevel managers (obj_camera, obj_hud, spawners).
Instances_PlayerInstance LayerExclusively contains obj_player, guaranteeing visual priority.
Instances_EntitiesInstance LayerPatrolling enemies (obj_enemy), coins (obj_coin), keys, interactive doors.
Tiles_CollisionsTile Layer / InstanceSolid tiles where the player and enemies walk and collide.
Tiles_DecorationTile LayerNon-colliding visual accents (wall cracks, grass tufts, moss).
Background_ParallaxBackground LayerDistant landscapes with camera-relative parallax scrolling.
Background_BaseBackground LayerSolid backdrop texture or base sky color.

To connect the level to the victory screen or the next stage, we need an interactive goal object. We will create obj_door (or a level goal marker) using an easily identifiable door or shining banner sprite. This object will detect player contact and trigger the resolution sequence.

In the Collision event between obj_door and obj_player, implement the goal check:

// Collision Event of obj_door with obj_player
// If the player touches the goal, lock inputs and trigger victory
with (other) {
    can_move = false;
    hsp = 0;
    vsp = 0;
}

// Play victory sound effect if imported
if (audio_exists(snd_stage_clear)) {
    audio_play_sound(snd_stage_clear, 10, false);
}

// Start transition to victory screen
transition_to(rm_win);

Defeat Handling: Routing to Game Over

The counterpart to reaching the goal is handling player death. In Module 7, we implemented lives and health. When the player's health or lives reach zero, we should not abruptly reload the room; we must route defeat to the Game Over screen (rm_game_over).

Inside obj_player or your player state controller, within the script or event where the loss of the final life is processed, add:

// Snippet inside player damage script or Step Event
if (global.player_lives <= 0) {
    // Disable controls to freeze current state
    can_move = false;
    
    // Play Game Over jingle
    audio_stop_all();
    if (audio_exists(snd_game_over)) {
        audio_play_sound(snd_game_over, 10, false);
    }
    
    // Send player to Game Over screen
    transition_to(rm_game_over);
}

Implementing End Screens and Interactive Return

The rm_menu, rm_win, and rm_game_over rooms share a common pattern: they are static or semi-static interface screens waiting for player input to redirect the flow. Let us build the controllers for both the victory and defeat screens.

Global Variable Management and Safe State Resets

A frequent pitfall for beginners occurs when a player dies and selects retry: previously collected coins fail to reappear, the health bar starts depleted, or the camera spawns out of bounds. This bug happens when developers rely solely on built-in room_restart() calls without resetting or sanitizing persistent global variables.

To prevent state corruption, we will define a centralized reset script called whenever a brand-new game run begins. Create a script named scr_game_management and declare this function:

/// @function start_new_game()
/// @description Initializes player progress data for a clean session.
function start_new_game() {
    global.player_lives = 3;
    global.player_health = 100;
    global.player_score = 0;
    global.coins_collected = 0;
    global.checkpoint_active = false;
    global.checkpoint_x = 0;
    global.checkpoint_y = 0;
    
    // Start journey to the first level via visual transition
    transition_to(rm_level_1);
}

Now, within the 'Play' button or interactive menu text in your obj_menu_ui (built in Module 8), replace any direct room_goto(rm_level_1) call with this reset function:

// Action when selecting 'Play' in obj_menu_ui
start_new_game();

End-to-End Game Loop Verification

With every component implemented, the complete runtime flow should operate seamlessly. The verified execution sequence is as follows:

StepSourceTrigger ActionDestinationPersistent State
1Executable / F5GameMaker engine bootrm_initobj_transition and global variables initialized.
2rm_initAutomatic on frame 1rm_menuMain menu displays with selectable options.
3rm_menuSelect 'Play' (Enter/Click)rm_level_1start_new_game() resets stats; fade-out masks loading.
4Arm_level_1obj_player collides with obj_doorrm_winInput disabled, clear jingle plays, fade to rm_win.
4Brm_level_1global.player_lives drops to 0rm_game_overInput disabled, game over audio plays, fade to rm_game_over.
5rm_win / rm_game_overAssigned key pressedrm_level_1 or rm_menuControlled state reset or return to main menu.

Practical Assembly Exercises

Reinforce your technical understanding by implementing and verifying the following three mandatory exercises in your project:

Exercise 1 (Programming Practice): Add custom transition color support. Modify obj_transition and transition_to() to accept an optional second argument for fade color (for instance, c_white for a radiant flash when clearing the level, and c_black when losing lives). Ensure the Draw GUI rectangle utilizes this internal fade_color variable passed into the function.

Verification Criteria 1: On player defeat, the screen fades to black (c_black); on touching the exit door, the screen transitions through a clean white flash (c_white) without stalling or breaking the room transition loop.

Exercise 2 (Level Assembly & Design): Build a complete, functional platforming stretch in rm_level_1 featuring: a player spawn point, at least one enemy patrolling a floating platform, a cluster of three collectible coins, a spike hazard pit that deducts health or lives, and obj_door placed on the far right. Ensure tile layers and instance layers respect the layer order specified in this lesson's hierarchy table.

Verification Criteria 2: The player can navigate the entire stretch without falling through solid collision tiles, enemies patrol back and forth without slipping off edges, and the camera follows the player smoothly to the goal.

Exercise 3 (Loop Integration Testing): Perform a complete closed-loop playthrough. Start the game from rm_init, navigate through the menu, enter the level, intentionally expend all lives to reach rm_game_over, and press the retry key to verify that score resets to 0 and lives reset to 3. Then, complete the stage by reaching the goal to display rm_win, and successfully return to the main menu.

Verification Criteria 3: The cycle can be repeated indefinitely without creating duplicate obj_transition instances, without dropping frames (FPS), and without locking player input across restarts.

Summary

  • Game flow seamlessly links initialization, menus, gameplay levels, and end screens, removing jarring visual cuts.
  • A persistent transition controller (obj_transition) rendered in Draw GUI delivers universal fade effects independent of camera views.
  • The first room listed in the Asset Browser must be a technical room (rm_init) designed to initialize global data and immediately forward to the menu.
  • The main level (rm_level_1) requires a strict layer hierarchy (Backgrounds, Tiles, Instances, GUI) to guarantee correct draw sorting.
  • Transitions into rm_win and rm_game_over must freeze player inputs and use centralized reset logic (start_new_game()) to prevent corrupted state across sessions.

Practise what you learned

Exercise 1 · 1 point

What is the primary purpose of checking the 'Persistent' box in the properties of the obj_transition object?

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

Exercise 2 · 1 point

Place the states and actions that occur inside obj_transition during a complete room transition cycle in chronological order:

  1. 1The alpha value reaches 0 and transition_state resets to 0 (idle).
  2. 2The screen turns completely black (alpha >= 1) and room_goto(target_room) is executed.
  3. 3The object remains at rest with transition_state equal to 0 (idle).
  4. 4The fade-out phase begins (transition_state = 1) and the alpha value progressively increases.
  5. 5The fade-in phase begins (transition_state = 2) in the new room and the alpha value decreases.
You are practising without an account: nothing is saved. Sign up to keep your progress.

Exercise 3 · 1 point

Match each game architecture element to its specific role described in the lesson:

  • rm_init
  • obj_transition
  • obj_door
  • rm_game_over
You are practising without an account: nothing is saved. Sign up to keep your progress.

Sign in to save your progress — Sign in