~2 мин чтения

Загрузка ресурсов — preload, load, threaded

Как Godot загружает ассеты — синхронно, асинхронно, с прогрессом.

В Godot нет отдельной системы вроде Unity Addressables — встроенный механизм покрывает большинство сценариев через несколько API.

Три способа загрузки

1. preload — статичная, на этапе парсинга

const ENEMY_SCENE := preload("res://scenes/enemy.tscn")
const FIRE_SOUND := preload("res://audio/fire.ogg")

func spawn() -> void:
    var enemy = ENEMY_SCENE.instantiate()
    add_child(enemy)

preload — это директива компилятора GDScript. Ресурс загружается, когда сам скрипт загружается (один раз, кешируется). Самый быстрый способ при инстанцировании.

Плюс: ноль пауз в рантайме. Минус: ассет всегда в памяти, пока скрипт жив. Не подходит для тяжёлой геометрии, которая нужна не всегда.

2. load — динамическая, синхронная

func load_level(name: String) -> void:
    var path = "res://levels/%s.tscn" % name
    var scene = load(path) as PackedScene
    if scene == null:
        push_error("Failed to load %s" % path)
        return
    get_tree().change_scene_to_packed(scene)

load блокирует поток, пока не загрузит. Подходит для лёгких ресурсов в моменты, где пауза не заметна.

3. ResourceLoader.load_threaded_request — асинхронная

Для тяжёлых сцен/моделей:

var loading_path: String

func start_load(scene_path: String) -> void:
    loading_path = scene_path
    ResourceLoader.load_threaded_request(scene_path)

func _process(_delta: float) -> void:
    if loading_path.is_empty():
        return

    var progress := []
    var status = ResourceLoader.load_threaded_get_status(loading_path, progress)

    match status:
        ResourceLoader.THREAD_LOAD_IN_PROGRESS:
            $LoadingBar.value = progress[0] * 100.0  # 0..1
        ResourceLoader.THREAD_LOAD_LOADED:
            var resource = ResourceLoader.load_threaded_get(loading_path)
            _on_loaded(resource)
            loading_path = ""
        ResourceLoader.THREAD_LOAD_FAILED:
            push_error("Load failed: ", loading_path)
            loading_path = ""

func _on_loaded(scene: PackedScene) -> void:
    get_tree().change_scene_to_packed(scene)

progress — array, где progress[0] это float 0..1 прогресса. Можно строить настоящий loading screen.

Загрузка в фоне во время gameplay

Это аналог Unity Addressables LoadAssetAsync: пока игрок проходит уровень, в фоне грузим следующий. По достижении 100% — мгновенный переход.

Подсказка типа

load и load_threaded_get возвращают Resource. Чтобы получить типизированный объект, используйте as:

var scene := load(path) as PackedScene
var texture := load(tex_path) as Texture2D
var data := load(cfg_path) as WeaponData

Если cast не удался — будет null.

PCK-пакеты

Godot позволяет упаковывать ассеты в PCK-файлы (.pck — Pack file) и загружать в рантайме. Используется для DLC, hot updates, mod-поддержки:

# Загрузить PCK в текущий проект
if not ProjectSettings.load_resource_pack("res://dlc/level_pack_2.pck"):
    push_error("Failed to load DLC pack")
else:
    # Теперь доступны ассеты из pack по их путям
    var dlc_scene = load("res://dlc_levels/extra_01.tscn")

PCK создаётся через Project → Export в режиме “Export PCK/Zip” (без бинарника движка).

Веб

Code splitting: import('./heavy-module') асинхронно — браузер грузит chunk по сети. load_threaded_request — то же концептуально, но для ассетов.

Unity

Unity Addressables ↔ Godot PCK + ResourceLoader. У Godot нет такой удобной remote-CDN интеграции из коробки, но базовые механизмы есть.

Кеширование

load дважды по одному пути вернёт тот же Resource (рефкаунтная природа Resource):

var a = load("res://item.tres")
var b = load("res://item.tres")
print(a == b)  # true — это один и тот же объект

Если хотите независимую копию (например, чтобы менять без побочных эффектов):

var clone = a.duplicate()

Для глубокого клона — duplicate(true) (рекурсивно дублирует вложенные ресурсы).

resource_local_to_scene

Свойство Resource, которое заставляет каждую сцену клонировать ресурс при загрузке. Полезно для материалов, которые должны быть уникальны на каждом инстансе:

var mat = StandardMaterial3D.new()
mat.resource_local_to_scene = true
# Теперь при инстансах сцены каждый получит свою копию

Освобождение памяти

Resource удаляется автоматически по reference count. Если у вас Singleton-ссылки или global arrays хранят ассеты, явно занулите их:

my_cache.clear()  # массив со ссылками
preloaded_scene = null

После последней ссылки на следующий кадр Godot почистит память.

Что делать, когда что-то “не выгружается”

  1. Используйте Resource Monitor в Debugger → Monitor → Objects: показывает живые объекты в памяти. Если число только растёт — утечка.
  2. Remote Scene Tree — позволяет инспектировать запущенную сцену из редактора. Если узел “должен был быть удалён, но висит” — увидите.
  3. queue_free() вместо free() — освобождение в конце кадра, безопаснее для текущих сигналов.

В следующей главе — сборка и оптимизация.