godot-autoload-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AutoLoad Architecture

AutoLoad架构

AutoLoads are Godot's singleton pattern, allowing scripts to be globally accessible throughout the project lifecycle. This skill guides implementing robust, maintainable singleton architectures.
AutoLoad是Godot的单例模式,允许脚本在整个项目生命周期内全局可访问。本指南将指导你实现健壮、可维护的单例架构。

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.

  • 绝对不要在
    _init()
    中访问AutoLoad
    — 在
    _init()
    阶段,AutoLoad并不一定已经存在。请改用
    _ready()
    _enter_tree()
  • 绝对不要创建循环依赖 — 如果GameManager依赖SaveManager,同时SaveManager又依赖GameManager,会导致初始化死锁。请使用信号或依赖注入。
  • 绝对不要在AutoLoad中存储场景特定状态 — AutoLoad会在场景切换后持续存在。存储临时状态(当前敌人数量、UI状态)会导致内存泄漏。请在
    _ready()
    中重置状态。
  • 绝对不要什么都用AutoLoad — 过度依赖会产生“上帝对象”和紧耦合。最多限制使用5-10个AutoLoad。局部逻辑请使用场景树。
  • 绝对不要假设AutoLoad的初始化顺序 — AutoLoad会按照项目设置中的从上到下顺序初始化。如果顺序很重要,请添加显式的
    initialize()
    调用或谨慎使用
    @onready

Available Scripts

可用脚本

MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请先阅读相应的脚本。

service_locator.gd

service_locator.gd

Service locator pattern for decoupled system access. Allows swapping implementations (e.g., MockAudioManager) without changing game code.
用于解耦系统访问的服务定位器模式。无需修改游戏代码即可替换实现(例如MockAudioManager)。

stateless_bus.gd

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.
无状态信号总线模式。提供领域特定信号(player_health_changed、level_completed)但不存储状态。总线仅作为邮局,而非仓库。

autoload_initializer.gd

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.

管理显式初始化顺序和依赖注入,以避免循环依赖。
请勿加载 service_locator.gd,除非你要实现依赖注入模式。

When to Use AutoLoads

何时使用AutoLoad

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
适用场景:
  • 游戏管理器:PlayerManager、GameManager、LevelManager
  • 全局状态:分数、背包、玩家属性
  • 场景切换:用于加载/卸载场景的SceneTransitioner
  • 音频管理:全局音乐/音效控制器
  • 存档/读档系统:持久化数据管理
避免使用AutoLoad的场景:
  • 场景特定逻辑(改用场景树)
  • 临时状态(使用信号或直接引用)
  • 对简单项目过度架构设计

Implementation Pattern

实现模式

Step 1: Create the Singleton Script

步骤1:创建单例脚本

Example: GameManager.gd
gdscript
extends Node
示例:GameManager.gd
gdscript
extends Node

Signals for global events

全局事件信号

signal game_started signal game_paused(is_paused: bool) signal player_died
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
undefined
var score: int = 0 var current_level: int = 1 var is_paused: bool = false
func _ready() -> void: # 初始化AutoLoad状态 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
undefined

Step 2: Register as AutoLoad

步骤2:注册为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
:
ini
[autoload]
GameManager="*res://autoloads/game_manager.gd"
The
*
prefix makes it active immediately on startup.
项目 → 项目设置 → AutoLoad
  1. 点击文件夹图标,选择
    game_manager.gd
  2. 设置节点名称:
    GameManager
    (采用PascalCase命名规范)
  3. 根据需要启用全局访问
  4. 点击“添加”
project.godot
中验证:
ini
[autoload]
GameManager="*res://autoloads/game_manager.gd"
*
前缀表示在启动时立即激活。

Step 3: Access from Any Script

步骤3:在任意脚本中访问

gdscript
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)
gdscript
extends Node2D

func _ready() -> void:
    # 访问单例
    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

1. 使用静态类型

gdscript
undefined
gdscript
undefined

✅ Good

✅ 推荐

var score: int = 0
var score: int = 0

❌ Bad

❌ 不推荐

var score = 0
undefined
var score = 0
undefined

2. Emit Signals for State Changes

2. 状态变化时发送信号

gdscript
undefined
gdscript
undefined

✅ Good - allows decoupled listeners

✅ 推荐 - 支持解耦的监听器

signal score_changed(new_score: int)
func add_score(points: int) -> void: score += points score_changed.emit(score)
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
undefined
func add_score(points: int) -> void: score += points ui.update_score(score) # 不要直接调用UI
undefined

3. Organize AutoLoads by Feature

3. 按功能组织AutoLoad

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

4. Scene Transitioning Pattern

4. 场景切换模式

gdscript
undefined
gdscript
undefined

scene_transitioner.gd

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)
undefined
extends Node
signal scene_changed(scene_path: String)
func change_scene(scene_path: String) -> void: # 淡出效果(可选) await get_tree().create_timer(0.3).timeout get_tree().change_scene_to_file(scene_path) scene_changed.emit(scene_path)
undefined

Common Patterns

常见模式

Game State Machine

游戏状态机

gdscript
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
gdscript
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:
            # 加载菜单
            pass
        GameState.PLAYING:
            get_tree().paused = false
        GameState.PAUSED:
            get_tree().paused = true
        GameState.GAME_OVER:
            # 显示游戏结束界面
            pass

Resource Preloading

资源预加载

gdscript
undefined
gdscript
undefined

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
undefined
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
undefined

Testing AutoLoads

测试AutoLoad

Since AutoLoads are always loaded, avoid heavy initialization in
_ready()
. Use lazy initialization or explicit init functions:
gdscript
var _initialized: bool = false

func initialize() -> void:
    if _initialized:
        return
    _initialized = true
    # Heavy setup here
由于AutoLoad始终会被加载,请避免在
_ready()
中进行重型初始化
。请使用延迟初始化或显式的初始化函数:
gdscript
var _initialized: bool = false

func initialize() -> void:
    if _initialized:
        return
    _initialized = true
    # 这里进行重型初始化操作

Reference

参考资料

Related

相关内容

  • Master Skill: godot-master
  • 核心技能:godot-master