概要
Autoloadは、ゲーム起動時に自動的に読み込まれ、シーン遷移しても破棄されない特別なノードです。UnityのDontDestroyOnLoadと シングルトンパターンを組み合わせたような機能を提供します。
最初は「シーンをまたいでデータを持ち越すにはどうすればいいんだろう?」と悩んでいた時、Autoloadの存在を知って感動しました。プレイヤーのステータス、宝箱の開封状態、インベントリなど、ゲームの「継続性」に関わるあらゆるデータを、この仕組みで管理できるようになりました。

Autoloadの特徴
- 永続性: シーン遷移で破棄されない
- グローバルアクセス: どこからでもアクセス可能
- 自動初期化: ゲーム起動時に自動でインスタンス化
- 単一インスタンス: シングルトンとして動作
設定方法
1. スクリプトの作成
# GameManager.gd
extends Node
# ゲーム全体で共有するデータ
var player_health: int = 100
var player_max_health: int = 100
var current_level: int = 1
var score: int = 0
# 収集済みアイテム
var collected_items: Array[String] = []
var opened_chests: Array[String] = []
# セーブポイント情報
var last_checkpoint: String = ""
var spawn_position: Vector2 = Vector2.ZERO
2. Autoloadへの登録
- プロジェクト設定 → AutoLoad
- パスにスクリプトを選択
- ノード名を設定(例:GameManager)
- 「追加」をクリック
基本的な使い方
グローバル変数のアクセス
# どのスクリプトからでもアクセス可能
func _ready():
# 読み取り
var health = GameManager.player_health
# 書き込み
GameManager.score += 100
# 関数の呼び出し
GameManager.save_game()
実践的な実装例
宝箱の開封状態管理
# GameManager.gd (Autoload)
extends Node
var opened_chests: Dictionary = {}
func mark_chest_opened(chest_id: String):
opened_chests[chest_id] = true
func is_chest_opened(chest_id: String) -> bool:
return opened_chests.has(chest_id) and opened_chests[chest_id]
func reset_all_chests():
opened_chests.clear()
# TreasureChest.gd
extends Area2D
@export var chest_id: String = "forest_chest_01"
@export var item: Resource
func _ready():
if GameManager.is_chest_opened(chest_id):
# 既に開封済み
$Sprite2D.frame = 1 # 開いた状態の画像
$CollisionShape2D.disabled = true
func _on_body_entered(body):
if body.is_in_group("player") and not GameManager.is_chest_opened(chest_id):
open_chest()
func open_chest():
# アイテム付与
give_item_to_player()
# 開封状態を記録
GameManager.mark_chest_opened(chest_id)
# アニメーション
$AnimationPlayer.play("open")
$CollisionShape2D.disabled = true
プレイヤーデータの永続化
# PlayerData.gd (Autoload)
extends Node
signal health_changed(new_health)
signal level_up(new_level)
var stats = {
"level": 1,
"experience": 0,
"health": 100,
"max_health": 100,
"attack": 10,
"defense": 5
}
var inventory: Array[Dictionary] = []
var equipped_items: Dictionary = {}
func add_experience(amount: int):
stats.experience += amount
check_level_up()
func check_level_up():
var required_exp = stats.level * 100
if stats.experience >= required_exp:
stats.level += 1
stats.max_health += 10
stats.attack += 2
stats.defense += 1
emit_signal("level_up", stats.level)
func take_damage(amount: int):
stats.health = max(0, stats.health - amount)
emit_signal("health_changed", stats.health)
if stats.health == 0:
handle_player_death()
func heal(amount: int):
stats.health = min(stats.max_health, stats.health + amount)
emit_signal("health_changed", stats.health)
セーブ・ロードシステム
# SaveManager.gd (Autoload)
extends Node
const SAVE_PATH = "user://savegame.save"
func save_game():
var save_dict = {
"player_stats": PlayerData.stats,
"player_inventory": PlayerData.inventory,
"opened_chests": GameManager.opened_chests,
"current_scene": get_tree().current_scene.scene_file_path,
"timestamp": Time.get_unix_time_from_system()
}
var save_file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
save_file.store_var(save_dict)
save_file.close()
func load_game():
if not FileAccess.file_exists(SAVE_PATH):
return false
var save_file = FileAccess.open(SAVE_PATH, FileAccess.READ)
var save_dict = save_file.get_var()
save_file.close()
# データの復元
PlayerData.stats = save_dict.get("player_stats", PlayerData.stats)
PlayerData.inventory = save_dict.get("player_inventory", [])
GameManager.opened_chests = save_dict.get("opened_chests", {})
# シーンの読み込み
var scene_path = save_dict.get("current_scene", "")
if scene_path != "":
get_tree().change_scene_to_file(scene_path)
return true
複数のAutoloadの連携
# GameEvents.gd (Autoload) - イベントバス
extends Node
signal enemy_defeated(enemy_type: String)
signal item_collected(item_name: String)
signal quest_completed(quest_id: String)
# QuestManager.gd (Autoload)
extends Node
var active_quests: Dictionary = {}
func _ready():
GameEvents.enemy_defeated.connect(_on_enemy_defeated)
GameEvents.item_collected.connect(_on_item_collected)
func _on_enemy_defeated(enemy_type: String):
for quest_id in active_quests:
var quest = active_quests[quest_id]
if quest.type == "defeat_enemies" and quest.target == enemy_type:
quest.current += 1
check_quest_completion(quest_id)
パフォーマンスとメモリ管理
注意点
# 悪い例:大量のデータを常に保持
var all_game_textures: Dictionary = {} # メモリを圧迫
# 良い例:必要な時にロード
func get_texture(texture_name: String) -> Texture2D:
return load("res://textures/" + texture_name + ".png")
クリーンアップ
func reset_game_state():
# ゲーム開始時やタイトルに戻る際にリセット
opened_chests.clear()
collected_items.clear()
current_level = 1
score = 0
ベストプラクティス
- 責任の分離: 機能ごとに別々のAutoloadを作成
- 初期化順序: 依存関係がある場合は順序に注意
- シグナルの活用: 直接的な依存を避ける
- デバッグ機能: 開発時のみ有効なデバッグ用Autoload
実装して学んだこと
Autoloadを使うようになってから、ゲームのデータ管理が劇的に改善しました。特に宝箱の開封状態管理は、以前は各シー ンで個別に管理していたため、「セーブデータが早くなると一度開けた宝箱が復活してしまう」というバグがありました。
Autoloadで一元管理するようになってからは、こういった問題が一気に解決しました。また、シグナルを使ったイベントシステムを構築することで、異なるAutoload間での連携がスムーズにできるようになり、コードの結合度を低く保つことができました。
最初は「全部一つのAutoloadにまとめてしまえば楽じゃないか?」と思っていましたが、機能ごとに分けることのメリットを実感しています。コードの可読性、保守性、デバッグのしやすさ、すべてが向上しました。