godot-autoload-architecture

Expert patterns for Godot AutoLoad (singleton) architecture including global state management, scene transitions, signal-based communication, dependency injection, autoload initialization order, and anti-patterns to avoid. Use for game managers, save systems, audio controllers, or cross-scene resources. Trigger keywords: AutoLoad, singleton, GameManager, SceneTransitioner, SaveManager, global_state, autoload_order, signal_bus, dependency_injection.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-autoload-architecture" with this command: npx skills add thedivergentai/gd-agentic-skills/thedivergentai-gd-agentic-skills-godot-autoload-architecture

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

  1. Click the folder icon, select game_manager.gd
  2. Set Node Name: GameManager (PascalCase convention)
  3. Enable if needed globally
  4. 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

3. Organize AutoLoads by Feature

res://autoloads/
    game_manager.gd
    audio_manager.gd
    scene_transitioner.gd
    save_manager.gd

4. 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

Related

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

godot-master

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-shaders-basics

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-ui-theming

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-particles

No summary provided by upstream source.

Repository SourceNeeds Review