概要
ゲーム開発を進めていくと、「プレイヤーのHPや装備を次のステージに引き継ぎたい」「集めたアイテムの情報を保持したい」「ゲーム全体の設定を一元管理したい」といった要求が出てきます。しかし、change_scene_to_file()でシーンを切り替えると、デフォルトでは古いシーンの情報はすべて破棄されてしまいます。
この問題を解決し、シーンをまたいでデータを永続的に保持するための最も標準的で強力な方法が、Autoload(オートロード) 機能です。これは、ソフトウェア設計で広く知られる「シングルトン」パターンを、Godotがエンジンレベルでサポートし、簡単に利用できるようにした仕組みです。
この記事では、基本的な設定方法から、より実践的なコード例、よくある間違いとベストプラクティス、そしてAutoload以外のデータ共有方法との比較まで解説します。
Autoload(シングルトン)とは
Autoloadとは、ゲームの起動時にGodotが自動的にインスタンス化し、シーンツリーのルートに常駐させるノードまたはスクリプトのことです。一度Autoloadに登録されると、ゲームが終了するまでメモリ上に存在し続けるため、 どのシーンのどのスクリプトからでも、グローバル変数のように直接アクセスできるようになります。
この「どこからでもアクセスできる単一のインスタンス」という性質を持つデザインパターンを、一般的にシングルトン (Singleton) と呼びます。
主な用途
Autoloadは、以下のような「ゲーム全体で共有・管理したい」要素に最適です。
- グローバルな状態管理: プレイヤーのスコア、所持金、ライフ、インベントリなど、ゲームの進行状況に関わる情報。
- 汎用的な機能の提供: サウンドマネージャー、シーン遷移時のフェードエフェクト、HTTPリクエスト管理など、どこからでも呼び出したい共通機能。
- システムの管理: ゲーム全体の進行を司るマネージャー、イベントシステム(シグナルバス)など、アプリケーションの根幹をなす機能。
Autoloadの基本的な設定方法
Autoloadの設定は、プロジェクト設定からわずか数ステップで完了します。
1. グローバルスクリプトの作成
まず、管理したいデータや機能を持つスクリプトを作成します。ここでは、プレイヤーのデータとゲーム設定を管理するPlayerData.gdというスクリプトを例に挙げます。
# PlayerData.gd
extends Node
# プレイヤーの基本情報
var player_name: String = "Hero"
var health: int = 100
var max_health: int = 100
# ゲームの進行状況
var score: int = 0
var high_score: int = 0
# 宝箱の開封状態を管理する辞書
# キー: "SceneName_ChestID", 値: bool (trueなら開封済み)
var opened_chests: Dictionary = {}
func add_score(amount: int) -> void:
score += amount
if score > high_score:
high_score = score
func take_damage(amount: int) -> void:
health -= amount
if health < 0:
health = 0
func is_chest_opened(chest_id: String) -> bool:
return opened_chests.has(chest_id)
func mark_chest_as_opened(chest_id: String) -> void:
opened_chests[chest_id] = true
2. プロジェクト設定から登録
- メニューバーから 「プロジェクト」→「プロジェクト設定」 を選択します。
- 上部のタブから 「Autoload」 を選択します。
- 「パス」 の入力欄の横にあるフォルダアイコンをクリックし、先ほど作成した
PlayerData.gdを選択します。 - 「ノード名」 は自動的にスクリプト名と同じ
PlayerDataになります。これがグローバルアクセスのための名前になります。 - 「追加」 ボタンを押して登録します。

これで設定は完了です。ゲームを実行すると、Godotは自動的にPlayerData.gdをインスタンス化し、PlayerDataという名前でアクセスできるようにしてくれます。
スクリプトからのアクセス方法
Autoloadに登録したノードは、登録した「ノード名」がそのままグローバル変数のように機能します。どのスクリプトからでも、事前のget_node()や@onreadyによる参照取得なしに、直接アクセスできます。
具体的なコード例
敵を倒してスコアを加算する
# Enemy.gd
func die():
# PlayerDataの関数を直接呼び 出す
PlayerData.add_score(100)
print("現在のスコア: ", PlayerData.score)
queue_free()
宝箱を開ける処理
# TreasureChest.gd
@export var chest_id: String = "level1_gold_chest"
func _ready():
# すでに開封済みか確認し、見た目を変える
if PlayerData.is_chest_opened(chest_id):
$Sprite2D.frame = 1 # 開いたスプライトに
is_open = true
func open():
if is_open: return
# PlayerDataに開封済みであることを記録
PlayerData.mark_chest_as_opened(chest_id)
# 中身を出す処理...
print("宝箱を開けた!")
UIにハイスコアを表示する
# GameOverScreen.gd
func _ready():
# PlayerDataの変数を直接参照する
$HighScoreLabel.text = "ハイスコア: %d" % PlayerData.high_score
このように、PlayerDataという名前でどこからでもアクセスできるため、シーンをまたいだデータの受け渡しが非常にシンプルになります。
よくある間違いとベストプラクティス
Autoloadは非常に便利ですが、使い方を誤るとプロジェクトの構造を複雑にし、メンテナンス性を著しく低下させる原因にもなります。
| よくある間違い | ベストプラクティス | 解説 |
|---|---|---|
| 何でもかんでもAutoloadに入れる | 本当にグローバルなものだけを登録する | グ ローバルな状態は最小限に保ち、特定のシーンや機能に閉じたデータは、その中で管理すべきです。 |
| Autoloadから他のノードを直接操作する | シグナルを使って疎結合に連携する | AutoloadがUIノードのtextを直接書き換えるのではなく、Autoload側でシグナルを定義し、UI側がそのシグナルを接続して自身の表示を更新すべきです。 |
| 巨大な神クラス(God Class)を作る | 責務に応じてAutoloadを分割する | GameState, SoundManager, SceneTransition, SaveSystem のように、役割ごとにスクリプトを分割しましょう。 |
| 状態の変更をどこからでも許可する | セッター/ゲッターでアクセスを制御する | プロパティ(Godot 4.x)を使い、値の変更を関数経由に限定することで、デバッグしやすくなります。 |
ベストプラクティス適用例:シグナルを使ったUI更新
先ほどのPlayerData.gdを、シグナルを使って改良してみましょう。
# PlayerData.gd (改良版)
extends Node
# スコアが更新されたときに発行されるシグナル
signal score_updated(new_score)
# バッキングフィールド(実際の値を保持する内部変数)
var _score: int = 0
# 外部からアクセスするプロパティ
var score: int:
get:
return _score
set(value):
_score = value
score_updated.emit(_score) # Godot 4.x推奨のシグナル発行方法
var high_score: int = 0
func add_score(amount: int) -> void:
self.score += amount # セッターを発動させるためにself.scoreと書く
if _score > high_score:
high_score = _score
# ... 他の関数は同じ ...
注意: セッター内で同じプロパティ名に直接代入すると、セッターが再帰的に呼び出されて無限ループになる可能性があります。そのため、
_scoreのようなバッキングフィールド(実際の値を保持する内部変数)を用意し、セッター内ではバッキングフィールドに代入するのが安全なパターンです。
UI側のスクリプトは、このシグナルを接続します。
# ScoreLabel.gd
extends Label
func _ready():
# PlayerDataのシグナルに接続
PlayerData.score_updated.connect(self._on_score_updated)
# 初期スコアを表示
_on_score_updated(PlayerData.score)
func _on_score_updated(new_score: int):
text = "Score: %d" % new_score
この設計により、PlayerDataはScoreLabelの存在を一切知る必要がなくなりました。UIの構成が変わっても、PlayerDataのコードを修正する必要はありません。これが疎結合な設計の強みです。
代替パターンとの比較
Autoloadは便利ですが、常に最良の選択肢とは限りません。他の設計パターンとの比較を理解し、状況に応じて使い分けることが重要です。
| パターン | 用途 | メリット | デメリット |
|---|---|---|---|
| Autoload (シングルトン) | グローバルな状態管理、汎用機能の提供 | アクセスが非常に簡単。Godotエンジンが公式にサポート。 | 依存関係が複雑になりやすい(密結合)。グローバル空間を汚染する。 |
| シーン切り替え時のパラメータ渡し | 次のシーンに一時的な情報を渡す | シンプルで、余計なグローバル状態を作らない。 | 複数のシーンをまたいでデータを保持するのが難しい。 |
| カスタムリソース (Resource) | 永続化したいデータセットを管理 | ファイルとして保存・読み込みが容易。インスペクターで編集可能。再利用性が高い。 | 実行時に動的な状態を管理するには、一手間必要。 |
いつAutoloadを使うべきか?
- アプリケーション全体で唯一の存在であるべきもの:サウンドマネージャー、セーブ/ロードシステムなど。
- 複数の、関連性のないシーンから頻繁にアクセスされるデータ:プレイヤーのスコアや所持金など。
いつ代替パターンを検討すべきか?
- 「次のシーンにだけ」情報を渡したい場合:シーン切り替え時のパラメータ渡しを検討しましょう。
- アイテムデータやキャラクターのステータスなど、設計図としてデータを管理したい場合:カスタムリソースが非常に強力です。
- ユニットテストを書きたい場合:Autoloadはグローバル状態 を持つため、テストが難しくなります。依存性注入(DI)の考え方を取り入れ、Autoloadへの直接参照ではなく、コンストラクタや
@export変数で依存オブジェクトを渡すように設計すると、モック(テスト用の代替オブジェクト)に差し替えやすくなります。
関連トピックと次のステップ
Autoloadをマスターした後は、より洗練されたアーキテクチャの構築に挑戦してみましょう。
- カスタムリソースの活用: アイテムデータベース、スキルツリー、キャラクターのステータスなど、データ駆動型の設計を学ぶことで、プロジェクトのスケーラビリティが向上します。
- シグナルバス: Autoloadを利用して、ゲーム全体で使えるグローバルなイベントシステム(シグナルバス)を構築する方法。
- データの保存と読み込み:
PlayerDataに集約した情報を、FileAccessクラスなどを使って実際にファイルに保存し、ゲームを再開したときに読み込む方法を学びましょう。
まとめ
Autoloadは、Godotにおけるグローバルなデータ管理の基本であり、シーンをまたいで情報を永続化させるための強力なツールです。その設定は簡単ですが、強力であるがゆえに、無計画な使用はプロジェクトの見通しを悪くする諸刃の剣でもあります。
重要なのは、何が本当にグローバルであるべきかを見極め、責務に応じて適切にクラスを分割し、シグナルなどを使って他のシステムとは疎結合に保つことです。今回紹介したベストプラクティスや代替パターンとの比較を参考に、プロジェクトに最適なアーキテクチャを設計してください。