| name | godot-autoload-patterns |
| description | Godot 4 Autoload单例模式最佳实践,包含全局管理、事件总线、信号通信。 |
Godot Autoload 单例模式
概述
Autoload(自动加载)是 Godot 中的单例功能,允许脚本在游戏开始时自动加载并在整个游戏周期中存在。正确使用 Autoload 可以有效管理全局状态和系统服务。
1. 全局数据管理
1.1 GameData 单例
# game_data.gd (Add to Project Settings > Autoload)
extends Node
enum GameState { MENU, PLAYING, PAUSED, GAME_OVER }
var state: GameState = GameState.MENU
var score: int = 0:
set(value):
score = value
score_changed.emit(score)
var high_score: int = 0
var level: int = 1
var player_health: int = 100
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
_load_high_score()
func _input(event: InputEvent) -> void:
if event.is_action_pressed("pause") and state == GameState.PLAYING:
toggle_pause()
func start_game() -> void:
score = 0
state = GameState.PLAYING
game_started.emit()
func toggle_pause() -> void:
var is_paused := state != GameState.PAUSED
if is_paused:
state = GameState.PAUSED
get_tree().paused = true
else:
state = GameState.PLAYING
get_tree().paused = false
game_paused.emit(is_paused)
func end_game(won: bool) -> void:
state = GameState.GAME_OVER
if score > high_score:
high_score = score
_save_high_score()
game_over.emit(won)
func add_score(points: int) -> void:
score += points
func _load_high_score() -> void:
if FileAccess.file_exists("user://high_score.save"):
var file := FileAccess.open("user://high_score.save", FileAccess.READ)
high_score = file.get_32()
func _save_high_score() -> void:
var file := FileAccess.open("user://high_score.save", FileAccess.WRITE)
file.store_32(high_score)
2. 事件总线模式
2.1 EventBus 单例
# event_bus.gd (Autoload)
extends Node
# Player events
signal player_spawned(player: Node2D)
signal player_died(player: Node2D)
signal player_health_changed(health: int, max_health: int)
# Enemy events
signal enemy_spawned(enemy: Node2D)
signal enemy_died(enemy: Node2D, position: Vector2)
signal wave_started(wave_number: int)
# Item events
signal item_collected(item_type: StringName, value: int)
signal powerup_activated(powerup_type: StringName)
# Level events
signal level_started(level_number: int)
signal level_completed(level_number: int, time: float)
signal checkpoint_reached(checkpoint_id: int)
# Game state events
signal game_paused(is_paused: bool)
signal game_over(won: bool)
2.2 使用示例
# 在任意脚本中连接信号
class_name Enemy
extends CharacterBody2D
func die() -> void:
EventBus.enemy_died.emit(self, global_position)
queue_free()
# 监听信号
class_name GameManager
extends Node
func _ready() -> void:
EventBus.enemy_died.connect(_on_enemy_died)
EventBus.level_started.connect(_on_level_started)
func _on_enemy_died(enemy: Node2D, position: Vector2) -> void:
spawn_drops(position)
add_score(10)
func _on_level_started(level_number: int) -> void:
prepare_waves(level_number)
3. 服务管理器
3.1 AudioManager
# audio_manager.gd (Autoload)
extends Node
@export var bgm_bus: int = 0
@export var sfx_bus: int = 1
var _bgm_player: AudioStreamPlayer
var _sfx_player: AudioStreamPlayer
func _ready() -> void:
_bgm_player = AudioStreamPlayer.new()
_sfx_player = AudioStreamPlayer.new()
add_child(_bgm_player)
add_child(_sfx_player)
func play_bgm(stream: AudioStream, volume_db: float = 0.0) -> void:
_bgm_player.stream = stream
_bgm_player.volume_db = volume_db
_bgm_player.play()
func play_sfx(stream: AudioStream, volume_db: float = 0.0) -> void:
_sfx_player.stream = stream
_sfx_player.volume_db = volume_db
_sfx_player.play()
func stop_bgm() -> void:
_bgm_player.stop()
3.2 SceneManager
# scene_manager.gd (Autoload)
extends Node
signal scene_loading_progress(progress: float)
signal scene_loaded(scene: Node)
var _current_scene: Node
var _loader: ResourceLoader
func _ready() -> void:
_current_scene = get_tree().current_scene
func change_scene(scene_path: String) -> void:
scene_loading_progress.emit(0.0)
if ResourceLoader.has_cached(scene_path):
var scene := load(scene_path) as PackedScene
_swap_scene(scene.instantiate())
return
ResourceLoader.load_threaded_request(scene_path)
while true:
var progress := []
var status := ResourceLoader.load_threaded_get_status(scene_path, progress)
match status:
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
scene_loading_progress.emit(progress[0])
await get_tree().process_frame
ResourceLoader.THREAD_LOAD_LOADED:
var scene := ResourceLoader.load_threaded_get(scene_path) as PackedScene
_swap_scene(scene.instantiate())
return
_:
push_error("Failed to load scene: %s" % scene_path)
return
func _swap_scene(new_scene: Node) -> void:
if _current_scene:
_current_scene.queue_free()
_current_scene = new_scene
get_tree().root.add_child(_current_scene)
get_tree().current_scene = _current_scene
scene_loaded.emit(_current_scene)
4. 场景结构
Project Settings > Autoload
├── GameData # 全局游戏数据
├── EventBus # 事件总线
├── AudioManager # 音频管理
├── SceneManager # 场景管理
├── SaveManager # 存档管理
└── InputManager # 输入管理
5. 设计原则
| 原则 | 说明 |
|---|
| 最小化原则 | 只将真正需要全局访问的对象设为 Autoload |
| 单一职责 | 每个 Autoload 只负责一个功能 |
| 信号解耦 | 优先使用 EventBus 信号通信 |
| 避免滥用 | 过多 Autoload 会使代码耦合度增加 |
6. 注意事项
| 注意事项 | 说明 |
|---|
| 初始化顺序 | Autoload 按配置顺序初始化 |
| ** PROCESS_MODE_ALWAYS** | 需要在暂停时运行的脚本设置此模式 |
| 避免循环依赖 | Autoload 之间不要相互引用形成循环 |
| 线程安全 | 多线程场景注意信号线程安全 |