godot-autoload-architecture

📁 thedivergentai/gd-agentic-skills 📅 3 days ago
0
总安装量
4
周安装量
安装命令
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-autoload-architecture

Agent 安装分布

opencode 4
gemini-cli 4
codex 4
github-copilot 3
kimi-cli 3

Skill 文档

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