Skip to content

G1.5 — Autoloads & Singletons

What you'll learn

  • Register a script or scene as an autoload, and reach it globally by name from any script.
  • Know that an autoload is instantiated once at startup and persists across scene changes.
  • Apply the test for when something should be an autoload — and when it should not.
  • Order autoloads correctly when one depends on another.

How it applies

  • Global state stored in a scene dies on scene change. Put the score on the level scene and it resets every time the level reloads or the player moves to the next one — the "my progress vanished" bug. An autoload lives outside any one scene, so it survives the swap.
  • Making everything an autoload hides coupling. Globals are reachable from anywhere, which makes them tempting and makes overuse a trap: every script can secretly depend on every autoload, which is hard to test and prone to load-order bugs. The test for "should this be global" keeps the set small and honest.
  • A wrong or unregistered name fails immediately. Referencing Game.score when the autoload is registered as GameState is an unknown identifier; reading it before registration is null. The name is the contract — it must match.
  • Load order is a real dependency. If autoload B uses autoload A in its _ready, but A is listed after B, A is not ready yet and B crashes. The list order is initialization order.

Try it first

Your game has a score and a set of unlocked upgrades that must stay consistent as the player moves between levels (each level is its own scene). Before reading on: where should that state live so every scene can read and write it, and so it does not reset when the scene changes? What goes wrong if you store it on the current level's root node?

Concepts

The problem autoloads solve

Most state belongs to a scene and should die with it — an enemy's health, a menu's open/closed flag. But some things are global and long-lived: the player's score and inventory, a save/load service, a central event bus, an audio manager. These must be reachable from any scene and must outlive any single scene, because scenes come and go (you change levels, reload, open a menu).

Storing such state on a scene's root node fails the "Try it first" prompt: when that scene is freed — on a level change or reload — the state goes with it (G1.2: freeing a node frees its data). The score resets. That is the bug an autoload prevents.

What an autoload is

An autoload (also called a singleton) is a script or scene you register in Project Settings → Autoload, giving it a name. Godot then:

  • instantiates it once at startup, before the main scene, as a child of the tree's root;
  • keeps it alive for the whole run, independent of scene changes;
  • makes it reachable globally by its name from any script.
# an autoload script registered under the name "Game":
extends Node
var score := 0
var upgrades: Array[String] = []
# from anywhere, no path, no preload:
Game.score += 100
if "double_jump" in Game.upgrades:
    ...

Game resolves globally because the autoload registered that name. This is the robust counterpart to the fragile absolute paths of G1.4: instead of get_node("/root/Game"), you write Game, and it does not break when the tree changes.

When something should be an autoload (and when not)

The vendor guidance gives three conditions; reach for an autoload when the thing is all three:

  1. Globally accessible — genuinely needed from many unrelated places.
  2. Tracks its own data — it owns its state, rather than borrowing a scene's.
  3. Exists in isolation — it does not depend on any particular scene being present.

A save manager, an event bus, a persistent player profile: yes. A specific enemy, a single level's layout, a menu that only one scene shows: no — those belong to their scene. The discipline matters because globals are reachable from everywhere, so an oversized autoload set turns into hidden coupling that is hard to reason about and test. Keep the set small and each one self-contained.

Example

The same data, in the wrong home and the right one:

# WRONG: score lives on the level scene's root — lost when the level reloads
extends Node2D     # Level.tscn root
var score := 0

# RIGHT: score lives in an autoload — survives every scene change
extends Node       # Game.gd, registered as autoload "Game"
var score := 0

Both compile. The first resets the score on every scene change because the node holding it is freed; the second persists because the autoload is never part of the scene that gets swapped. Where the data lives is the fix.

Load order

Autoloads initialize top-to-bottom in the order they appear in the Autoload list. If one autoload uses another during startup (e.g. a SaveManager reads from a Game autoload in its _ready), the one it depends on must be listed above it, or it will not be ready yet. The list order is the initialization order — treat it as a dependency declaration.

Walkthrough

  1. Create a script res://game.gd with extends Node and var score := 0. In Project Settings → Autoload, add it with the name Game. It now exists globally.
  2. In any scene's script, do Game.score += 10 in _ready and print(Game.score). Run with F6; confirm it prints. You accessed a global with no path and no preload.
  3. Demonstrate persistence: make two tiny scenes, set Game.score in the first, then change to the second scene at runtime (get_tree().change_scene_to_file("res://second.tscn")) and print Game.score there. It retains the value across the change.
  4. Contrast: put a var local_score := 0 on a level scene's root, set it, change scenes, and observe there is no way to read it from the new scene — it was freed with the old one.

Optional sanity check

Add a second autoload that, in its _ready, reads Game.score. Place it above Game in the list and run — it may fail or read an uninitialized value because Game is not ready yet. Move it below Game and it works. That is load order as a dependency, demonstrated.

Self-check quiz

Q1 — Why does a score stored on the current level's root node reset when the player advances to the next level?

A. Scores are always reset between levels by the engine. B. Changing scenes frees the old scene's nodes, taking their data with them; the score lived on a node that was freed. C. var resets at scene boundaries. D. The new level overwrites the score with zero.

Reveal answer

B. A scene change frees the old scene (G1.2), and any data on its nodes goes with it, so a score held there is lost. A and D invent engine behavior. C misattributes it to var; the issue is the node's lifetime, not the declaration.

Q2 — Which is the best candidate for an autoload?

A. The boss enemy of level 3. B. A persistent save/load service used from many scenes that owns its own data and depends on no particular scene. C. The pause menu shown only in the gameplay scene. D. A single decorative sprite.

Reveal answer

B. A save service is globally accessed, owns its data, and exists independent of any scene — all three conditions for an autoload. A, C, and D each belong to a specific scene and fail the "exists in isolation / globally needed" test; autoloading them would create needless global coupling.

Q3 — Autoload SaveManager reads Game in its _ready and crashes at startup. SaveManager is listed above Game. Fix?

A. Rename Game to GameState. B. Move Game above SaveManager in the Autoload list, since autoloads initialize top-to-bottom and a dependency must come first. C. Remove SaveManager from the autoloads. D. Use get_node instead of the name.

Reveal answer

B. Autoloads initialize in list order; SaveManager depending on Game must be listed below it so Game is ready first. A renames without fixing order. C throws away the autoload rather than ordering it. D changes the access syntax but not the initialization-order problem.

Integration question

Q4 — open

A team stores the player's gold and unlocked abilities on each level scene's root, and reaches a distant UI label with get_node(\"/root/Level/HUD/GoldLabel\"). Two problems: gold resets every level transition, and the gold label reference breaks whenever a level's tree is restructured. Explain how a single design move — an autoload — addresses the first problem and improves the second, and state the condition test that justifies using an autoload here.

Reveal expected answer

Gold resetting is the scene-lifetime bug: gold lives on a level root, which is freed at every transition, so it cannot persist (G1.2). Moving gold and unlocked abilities into an autoload (e.g. Game.gold, Game.abilities) places that state outside any one scene, so it survives all scene changes — the autoload is instantiated once and never freed during the run. The fragile label reference improves because the autoload changes the access pattern: instead of reaching across a brittle absolute path for the label, the HUD can read Game.gold (a stable global name, not a tree route) or — better — respond to a gold_changed signal the autoload emits (G2.6), so the HUD never has to be found by path at all. The justification: gold/abilities meet all three autoload conditions — globally accessed (many scenes and UI need them), owns its own data, and exists independent of any particular scene — so an autoload is the right home, not an overuse of globals. The chapter's thesis: where the data lives determines whether it persists, and a global name is the robust alternative to a fragile path.