System Reference

Scene Manager Systemautoload-root

// root-based screen & level controller · spawn-point aware · Godot 4 · GDScript

01 Overview

The Scene Manager is the root of the game and is never destroyed. It owns a single child slot called CurrentScene, where the active screen lives — the title screen, stage select, or a gameplay level. Switching screens means freeing the old child and adding a new one.

Because the manager persists across every screen, it is the one place that controls what is on-screen, where the player spawns, and (later) transitions and background loading.

Core idea Nothing calls change_scene_to_file() directly. Everything routes through SceneManager.go_to(path, spawn_name) so screen-switching logic stays in one place.

02 Scene Structure

Set the manager scene as the project's Main Scene. The whole game runs inside its CurrentScene slot.

Main (Node)              # SceneManager.gd — root, never freed
└── CurrentScene (Node)  # active screen added here as a child
        └── # Title / StageSelect / Level_Town / ...

Each level scene is self-contained — it holds its own Player node and one or more Marker3D spawn points. Title and menu scenes simply have no player, so the spawn step is skipped automatically.

Level_Town (Node3D)
├── Player (CharacterBody3D)   # in group "player"
├── from_castle (Marker3D)     # entrance spawn point
├── from_select (Marker3D)     # another entrance
└── # geometry, props, etc.
Why player-in-level Each level owning its player means you can open a level and press Play to test it alone. Menus need no special-casing — they just have no player.

03 Switching Flow

A single call to go_to() runs this sequence:

free old screen load .tscn add to CurrentScene find spawn marker move player there

If no spawn name is passed, the last two steps are skipped and the scene simply loads as placed in the editor.

04 Public API

go_to(path, spawn_name = "")

The only call you need. path is the res:// path to a .tscn; spawn_name is the optional name of a Marker3D in the new scene.

# Load a screen with no player (title, menus)
SceneManager.go_to("res://screens/title.tscn")

# Load a level and place the player at a named entrance
SceneManager.go_to("res://levels/town.tscn", "from_castle")

reload_current(spawn_name = "")

Reloads whatever scene is currently active — handy for a "restart level" action.

SceneManager.reload_current()
ArgumentTypeMeaning
pathStringres:// path of the scene to load
spawn_nameStringMarker3D name to place the player; empty = load as-is

05 Spawn Points

After a level loads with a spawn name, the manager finds the player via the "player" group, finds the marker by name, and copies the marker's transform onto the player.

func _place_player_at_spawn(scene_root, spawn_name):
    var players = get_tree().get_nodes_in_group("player")
    if players.is_empty(): return   # menu — no player, skip
    var player = players[0]

    var marker = scene_root.find_child(spawn_name, true, false)
    if marker == null: return     # no match, leave as placed

    player.global_transform = marker.global_transform
Two safe skips No player in the scene → skip silently (menus). No matching marker → warn and leave the player where the level placed it. Neither case errors.

Door integration

A door's job is just to call the manager with the destination and the entrance name the player should arrive at — mirroring the Door & Teleport system.

# inside a door's body_entered handler
SceneManager.go_to("res://levels/castle.tscn", "from_town")

06 Setup Checklist

  • Create a Main (Node) scene with one child CurrentScene (Node).
  • Attach SceneManager.gd to Main.
  • Set Main.tscn as the project's Main Scene (Project Settings → Application → Run).
  • Set FIRST_SCENE in the inspector to your title screen path.
  • Add each level's Player node to the group player (Node → Groups tab).
  • Place Marker3D nodes in levels, named per entrance (e.g. from_castle).
  • Switch screens anywhere with SceneManager.go_to(path, spawn_name).
Calling SceneManager from anywhere Because Main is the root, reference it from any child via the scene tree, or register the manager as an autoload named SceneManager if you prefer a global handle. Keep one approach consistent.

07 To Do Later

Two upgrades are intentionally left as commented hooks in the code so the prototype stays simple now and you know exactly where to return.

Background / threaded loading

Instant load() is fine for prototyping but causes a brief freeze on large levels. The code marks the spot where ResourceLoader.load_threaded_request() and a progress loop should replace the instant load, so a loading screen can show.

Transition hooks

Two clearly-marked spots in go_to() — one before the swap (cover the screen) and one after (reveal) — are where the Shatter Transition system plugs in with await Transition.play_out() and await Transition.play_in().

Prototype-first Both upgrades are additive. Nothing in the current flow needs to change to add them — just fill in the marked hooks.