【Godot】Godotの動的ロードとリソース管理 - load・preload・バックグラウンド読み込み

作成: 2026-02-08最終更新: 2026-07-09

Godotのload()とpreload()の使い分け、ResourceLoaderによる非同期バックグラウンド読み込み、進捗バー付きロード画面の実装、WeakRefを使ったメモリ管理まで、図とコードで実践的に解説します。

「シーンを切り替えた瞬間だけカクッと固まる」「大きなステージに入るときにロード画面を出したい」——ゲームが育ってくると、必ずリソースの 読み込み方 が課題になります。実はGodotでは、リソースを いつ・どうやって読むか を選ぶだけで、体感の快適さが大きく変わります。

考え方はシンプルで、「すぐ使う小さいものは先に読んでおく」「大きいものは必要になってから読む」「巨大なものはバックグラウンドで読みながらロード画面を出す」の3段階です。この記事では、preload()load() の使い分けから、ResourceLoader による 非同期ロード 、進捗バー付き ロード画面 の実装、そして WeakRef を使った メモリ管理 までを解説します。

リソースの動的ロードのイメージ。ディスクからゲームへリソースが読み込まれ、必要なものを必要なタイミングで供給する様子

この記事でわかること

  • preload()load() の違いと 使い分け
  • ResourceLoader による 非同期バックグラウンド読み込み の3ステップ
  • 進捗バー付きロード画面 の作り方(change_scene_to_packed
  • 参照カウントと WeakRef によるメモリ管理

Sponsored

preload()とload()の使い分け

Godotのリソース読み込みには2つの基本関数があります。 いつ読むかパスを変数にできるか が違うので、用途で使い分けます。

preloadとloadの違いの図。preloadはスクリプトのパース時に、定数パスのリソースをまとめて先読みする。loadは実行時に、変数で組み立てたパスのリソースを必要になってから読む

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.load_threaded_requestで別スレッドに読み込みを依頼、2.読み込みが裏で進む間もメインスレッド(ゲーム)は止まらず動き続け、load_threaded_get_statusで進捗を監視、3.完了したらload_threaded_getでリソースを受け取る

非同期ロードは 「依頼 → 進捗監視 → 取得」 の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_RESOURCE4種の状態

tips: progress空配列で渡す のは「参照渡しで値を返す」Godotの規約です。GDScriptの関数は複数の値を直接返せないため、空配列を渡して progress[0] に進捗を格納してもらいます。また、第3引数 use_sub_threads = true にすると、テクスチャやメッシュなどの サブリソースも並列 で読み込まれ、さらに速くなります。

Sponsored

実践:進捗バー付きロード画面を組む

非同期ロードを覚えたら、それをプレイヤーに見せる ロード画面 を作りましょう。RPGのダンジョン遷移、ステージ制アクションの面切り替え、オープンワールドのエリア移動——「重いシーンへ移る間、進捗バーで待たせる」場面はどのジャンルにもあります。

進捗バー付きロード画面の流れの図。CanvasLayerのロード画面が非同期ロードを開始し、load_threaded_get_statusで得た進捗でプログレスバーを更新(60%など)、完了したらchange_scene_to_packedで目的のシーンへ切り替える

ロード画面は 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参照カウントを増やしません

参照カウントとWeakRefの違いの図。通常の変数でリソースを持つと参照カウントが1以上のままでメモリに残り続ける。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でソフトキャッシュ、大きいリソースは非同期ロード、という4つの方針
方針具体策
ステージ単位で管理遷移時に、そのステージ専用リソースの参照を破棄する
preloadは最小限に全シーンで使う小物だけ。ステージ固有は load()
WeakRefでソフトキャッシュ再利用しうるが常時保持は不要なリソースに
大きいリソースは非同期テクスチャアトラスや3Dモデルは load_threaded_request()
Sponsored

おまけ:先に知っておくと良いこと

  • 同じパスの二重リクエストに注意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() +進捗バーに置き換えて、フリーズが消える手応えを確かめてみてください。「待たせるけど固まらない」が、快適なゲームの土台になります。

さらに学ぶために