- AutoLoad Architecture
- AutoLoads are Godot's singleton pattern, allowing scripts to be globally accessible throughout the project lifecycle. This skill guides implementing robust, maintainable singleton architectures.
- NEVER Do
- NEVER access AutoLoads in
- _init()
- — AutoLoads aren't guaranteed to exist yet during _init(). Use
- _ready()
- or
- _enter_tree()
- instead.
- NEVER create circular dependencies
- — If GameManager depends on SaveManager and SaveManager depends on GameManager, initialization deadlocks. Use signals or dependency injection.
- NEVER store scene-specific state in AutoLoads
- — AutoLoads persist across scene changes. Storing temporary state (current enemy count, UI state) causes leaks. Reset in
- _ready()
- .
- NEVER use AutoLoads for everything
- — Over-reliance creates "God objects" and tight coupling. Limit to 5-10 AutoLoads max. Use scene trees for local logic.
- NEVER assume AutoLoad initialization order
- — AutoLoads initialize top-to-bottom in Project Settings. If order matters, add explicit
- initialize()
- calls or use
- @onready
- carefully.
- Available Scripts
- MANDATORY
-
- Read the appropriate script before implementing the corresponding pattern.
- service_locator.gd
- Service locator pattern for decoupled system access. Allows swapping implementations (e.g., MockAudioManager) without changing game code.
- stateless_bus.gd
- Stateless signal bus pattern. Domain-specific signals (player_health_changed, level_completed) without storing state. The bus is a post office, not a warehouse.
- autoload_initializer.gd
- Manages explicit initialization order and dependency injection to avoid circular dependencies.
- Do NOT Load
- service_locator.gd unless implementing dependency injection patterns.
- When to Use AutoLoads
- Good Use Cases:
- Game Managers
-
- PlayerManager, GameManager, LevelManager
- Global State
-
- Score, inventory, player stats
- Scene Transitions
-
- SceneTransitioner for loading/unloading scenes
- Audio Management
-
- Global music/SFX controllers
- Save/Load Systems
- Persistent data management Avoid AutoLoads For: Scene-specific logic (use scene trees instead) Temporary state (use signals or direct references) Over-architecting simple projects Implementation Pattern Step 1: Create the Singleton Script Example: GameManager.gd extends Node
Signals for global events
signal game_started signal game_paused ( is_paused : bool ) signal player_died
Global state
var score : int = 0 var current_level : int = 1 var is_paused : bool = false func _ready ( ) -> void :
Initialize autoload state
print ( "GameManager initialized" ) func start_game ( ) -> void : score = 0 current_level = 1 game_started . emit ( ) func pause_game ( paused : bool ) -> void : is_paused = paused get_tree ( ) . paused = paused game_paused . emit ( paused ) func add_score ( points : int ) -> void : score += points Step 2: Register as AutoLoad Project → Project Settings → AutoLoad Click the folder icon, select game_manager.gd Set Node Name: GameManager (PascalCase convention) Enable if needed globally Click "Add" Verify in project.godot : [ autoload ] GameManager = " *res://autoloads/game_manager.gd " The * prefix makes it active immediately on startup. Step 3: Access from Any Script extends Node2D func _ready ( ) -> void :
Access the singleton
GameManager . connect ( "game_paused" , _on_game_paused ) GameManager . start_game ( ) func _on_button_pressed ( ) -> void : GameManager . add_score ( 100 ) func _on_game_paused ( is_paused : bool ) -> void : print ( "Game paused: " , is_paused ) Best Practices 1. Use Static Typing
✅ Good
var score : int = 0
❌ Bad
var score = 0 2. Emit Signals for State Changes
✅ Good - allows decoupled listeners
signal score_changed ( new_score : int ) func add_score ( points : int ) -> void : score += points score_changed . emit ( score )
❌ Bad - tight coupling
func add_score ( points : int ) -> void : score += points ui . update_score ( score )
Don't directly call UI
- Organize AutoLoads by Feature res://autoloads/ game_manager.gd audio_manager.gd scene_transitioner.gd save_manager.gd
- Scene Transitioning Pattern
scene_transitioner.gd
extends Node signal scene_changed ( scene_path : String ) func change_scene ( scene_path : String ) -> void :
Fade out effect (optional)
await get_tree ( ) . create_timer ( 0.3 ) . timeout get_tree ( ) . change_scene_to_file ( scene_path ) scene_changed . emit ( scene_path ) Common Patterns Game State Machine enum GameState { MENU , PLAYING , PAUSED , GAME_OVER } var current_state : GameState = GameState . MENU func change_state ( new_state : GameState ) -> void : current_state = new_state match current_state : GameState . MENU :
Load menu
pass GameState . PLAYING : get_tree ( ) . paused = false GameState . PAUSED : get_tree ( ) . paused = true GameState . GAME_OVER :
Show game over screen
pass Resource Preloading
Preload heavy resources once
const PLAYER_SCENE := preload ( "res://scenes/player.tscn" ) const EXPLOSION_EFFECT := preload ( "res://effects/explosion.tscn" ) func spawn_player ( position : Vector2 ) -> Node2D : var player := PLAYER_SCENE . instantiate ( ) player . global_position = position return player Testing AutoLoads Since AutoLoads are always loaded, avoid heavy initialization in _ready() . Use lazy initialization or explicit init functions: var _initialized : bool = false func initialize ( ) -> void : if _initialized : return _initialized = true
Heavy setup here
Reference Godot Docs: Singletons (AutoLoad) Best Practices: Scene Organization Related Master Skill: godot-master