「シーンを切り替えた瞬間だけカクッと固まる」「大きなステージに入るときにロード画面を出したい」——ゲームが育ってくると、必ずリソースの 読み込み方 が課題になります。実はGodotでは、リソースを いつ・どうやって読むか を選ぶだけで、体感の快適さが大きく変わります。
考え方はシンプルで、「すぐ使う小さいものは先に 読んでおく」「大きいものは必要になってから読む」「巨大なものはバックグラウンドで読みながらロード画面を出す」の3段階です。この記事では、preload() と load() の使い分けから、ResourceLoader による 非同期ロード 、進捗バー付き ロード画面 の実装、そして WeakRef を使った メモリ管理 までを解説します。
この記事でわかること
preload()とload()の違いと 使い分けResourceLoaderによる 非同期バックグラウンド読み込み の3ステップ- 進捗バー付きロード画面 の作り方(
change_scene_to_packed)- 参照カウントと
WeakRefによるメモリ管理
preload()とload()の使い分け
Godotのリソース読み込みには2つの基本関数があります。 いつ読むか と パスを変数にできるか が違うので、用途で使い分けます。

preload() ——先に読んでおく
弾やSEなど、毎フレームのように使う小さなリソースには preload() が最適です。スクリプトが パースされる時点 でリソースも読み込まれるので、実行時に呼んだ瞬間の遅延がゼロになります。
const BULLET_SCENE := preload("res://scenes/bullet.tscn") # パスは定数リテラルのみ
const HIT_SOUND := preload("res://audio/hit.wav")
func shoot() -> void:
add_child(BULLET_SCENE.instantiate()) # 遅延なく即座に使える
- パスはリテラル文字列のみ (変数は不可)
- スクリプトのパース時にまとめて読み 込まれる
- 小さい・頻繁に使うアセットに向く
load() ——必要になってから読む
プレイヤーが選んだ武器や、難易度で変わる敵など、 条件で変わる リソースには load() を使います。パスを 変数で組み立てられる のが最大の強みです。
func load_weapon(weapon_name: String) -> Node:
var path := "res://weapons/%s.tscn" % weapon_name # パスを動的に組み立て
return load(path).instantiate()
- パスを動的に組み立てられる
- 初回だけディスクから読み込み(以降はキャッシュから即座に返る)
- 大きい・条件付きのリソースに向く
tips:
preload()を大量に使うと、シーンの 初期ロードが遅く なります。特に大きなテクスチャや3Dモデルのpreload()は影響が大きいので、サイズの大きいものはload()や次のバックグラウンド読み込みに切り替えましょう。 数より合計サイズ がポイントです。
ResourceLoaderによる非同期ロード
大きなシーンを load() すると、読み込みが終わるまで メインスレッドがブロック され、ゲームが固まります。RPGのダンジョン遷移、オープンワールドのチャンク読み込みなど、重いリソースを扱う場面では ResourceLoader の 非同期API が効きます。バックグラウンドで読みながら、ゲームやアニメーションを動かし続けられます。

非同期ロードは 「依頼 → 進捗監視 → 取得」 の3ステップです。
# 1. 別スレッドで読み込みを依頼
ResourceLoader.load_threaded_request("res://levels/stage_2.tscn")
func _process(_delta: float) -> void:
var progress := [] # 進捗を受け取る空配列(後述)
var status := ResourceLoader.load_threaded_get_status(
"res://levels/stage_2.tscn", progress)
match status:
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
print("Loading: %d%%" % int(progress[0] * 100)) # progress[0]に0.0〜1.0
ResourceLoader.THREAD_LOAD_LOADED:
# 3. 完了 → リソースを取得
var scene := ResourceLoader.load_threaded_get("res://levels/stage_2.tscn")
_on_load_complete(scene)
ResourceLoader.THREAD_LOAD_FAILED:
printerr("読み込みに失敗しました")
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
printerr("パスが無効、または未リクエストです")
3つのAPIとステータスの役割はこうです。
| メソッド/ステータス | 役割 |
|---|---|
load_threaded_request(path) | 非同期ロードを開始 |
load_threaded_get_status(path, progress) | 進捗(0.0〜1.0)を取得 |
load_threaded_get(path) | 完了後にリソースを取得 |
THREAD_LOAD_IN_PROGRESS / LOADED / FAILED / INVALID_RESOURCE | 4種の状態 |
tips:
progressを 空配列で渡す のは「参照渡しで値を返す」Godotの規約です。GDScriptの関数は複数の値を直接返せないため、空配列を渡してprogress[0]に進捗を格納してもらいます。また、第3引数use_sub_threads = trueにすると、テクスチャやメッシュなどの サブリソースも並列 で読み込まれ、さらに速くなります。
実践:進捗バー付きロード画面を組む
非同期ロードを覚えたら、それをプレイヤーに見せる ロード画面 を作りましょう。RPGのダンジョン遷移、ステージ制アクションの面切り替え、オープンワールドのエリア移動——「重いシーンへ移る間、進捗バーで待たせる」場面はどのジャンルにもあります。

ロード画面は CanvasLayer で作り、_process で進捗を監視してバーを更新します。完了したら change_scene_to_packed() でシーンを切り替えます。
# loading_screen.gd(Autoloadに登録)
extends CanvasLayer
@onready var progress_bar: ProgressBar = $ProgressBar
@onready var label: Label = $Label
var _target_path := ""
func load_scene(scene_path: String) -> void:
_target_path = scene_path
show()
var err := ResourceLoader.load_threaded_request(scene_path) # 読み込み開始
if err != OK:
printerr("ロード開始に失敗: %s" % scene_path)
return
set_process(true)
func _process(_delta: float) -> void:
if _target_path.is_empty():
return
var progress := []
match ResourceLoader.load_threaded_get_status(_target_path, progress):
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
progress_bar.value = progress[0] * 100 # バーを進捗に合わせる
label.text = "Loading... %d%%" % int(progress[0] * 100)
ResourceLoader.THREAD_LOAD_LOADED:
var scene := ResourceLoader.load_threaded_get(_target_path)
get_tree().change_scene_to_packed(scene) # 完成したシーンへ切り替え
_target_path = ""
set_process(false)
hide()
ResourceLoader.THREAD_LOAD_FAILED:
label.text = "読み込みに失敗しました"
set_process(false)
呼び出しは1行です。
LoadingScreen.load_scene("res://levels/stage_2.tscn")
ポイントは2つです。
- ロード画面は Autoload に登録する :
change_scene_to_packed()は現在のシーンツリーを丸ごと置き換えます。ロード画面がそのツリーの一部だと 自分ごと消えてしまう ため、Autoloadで常駐させてシーン遷移をまたいで生き残らせます。 - 進捗は
progress[0]から :load_threaded_get_status()が空配列に入れてくれる0.0〜1.0を、そのままProgressBar.valueに反映します。プレイヤーに「あとどれくらいか」を伝えられます。
メモリ管理とWeakRefキャッシュ
「どう読むか」だけでなく「どう手放すか」も同じくらい大切です。大きなゲームでは、使わないリソースを解放しないとメモリが不足します。
まず、Godotは load() したリソースを パスベースでキャッシュ します。同じパスを再度 load() しても、ディスクではなくメモリのキャッシュが返ります(同一インスタンス)。
Godotのリソースは 参照カウント方式 です。参照している変数の数を内部で数え、 0になった瞬間に解放 します(JavaやC#のトレーシング型GCとは別物)。通常の変数で持つと参照が残り解放されませんが、WeakRef は 参照カウントを増やしません 。

これを使うと、「保持したいけど、他が使わなくなったら手放してよい」 ソフトキャッシュ が作れます。
var _cache: Dictionary = {}
func get_resource(path: String) -> Resource:
if _cache.has(path):
var res: Resource = _cache[path].get_ref() # 弱参照から取り出す
if res:
return res # まだ生きていれば再利用
var loaded := load(path)
_cache[path] = weakref(loaded) # 参照カウントを増やさず記録
return loaded
get_ref() が null なら、そのリソースはすでに解放済みなので load() で読み直します。アクセス頻度が高いものは通常の変数で持つ方が効率的で、WeakRefは「使うかもしれないが、メモリ節約を優先したい」リソース向きです。
設計の指針をまとめると、こうなります。

| 方針 | 具体策 |
|---|---|
| ステージ単位で管理 | 遷移時に、そのステージ専用リソースの参照を破棄する |
| preloadは最小限に | 全シーンで使う小物だけ。ステージ固有は load() |
| WeakRefでソフトキャッシュ | 再利用しうるが常時保持は不要なリソースに |
| 大きいリソースは非同期 | テクスチャアトラスや3Dモデルは load_threaded_request() |
おまけ:先に知っておくと良いこと
- 同じパスの二重リクエストに注意 :
load_threaded_request()を同じパスで2回呼ぶとエラーになります。複数箇所から呼ぶ可能性があるなら、先にload_threaded_get_status()で状態を確認します。 - 型ヒントで安全に :
load_threaded_request(path, "PackedScene")のように第2引数へ型名を渡すと、型チェック付きでロードできます。 - セーブデータのロードとは別物 :ここで扱ったのは「アセット(シーンや画像)の読み込み」です。プレイ状況の保存・復元は セーブ/ロードシステム の担当で、
ResourceLoaderの非同期APIはそこでも活用できます。
まとめ
preload()はパース時に先読み。小さく頻繁に使うものに最適(パスは定数のみ)load()は実行時。動的パスや条件付きロードに使う(初回だけディスクI/O)- 大きいシーンは
load_threaded_request()の3ステップで 非同期 に読み、メインスレッドを止めない - ロード画面は Autoload に置き、
load_threaded_get_status()の進捗でバーを更新、完了でchange_scene_to_packed() - リソースは 参照カウント 管理。
WeakRefで参照を増やさないソフトキャッシュを作れる
まずは重いステージ遷移を load_threaded_request() +進捗バーに置き換えて、フリーズが消える手応えを確かめてみてください。「待たせるけど固まらない」が、快適なゲームの土台になります。
さらに学ぶために
- セーブ/ロードシステムの実装 ——セーブデータの非同期読み込みにも応用できる
- カスタムリソースの作成と活用 ——ロード対象になるデータの作り方
- Object Pooling完全ガイド ——読み込んだシーンの生成コストを抑える
- Godot公式ドキュメント:Background loading ——非同期ロードの一次情報