~2 мин чтения

PackedScene и Resource

Универсальный контейнер сцены/префаба и data-only ассеты.

PackedScene — сцена и префаб в одном

Главная архитектурная особенность Godot: сцена и префаб — одна и та же сущность. Файл .tscn хранит дерево узлов; этот файл можно открыть как корневую сцену проекта или инстанциировать как ребёнка в другую сцену. Внутри Engine оба варианта — PackedScene-ресурс.

res://
├── scenes/
│   ├── main.tscn               ← главная сцена уровня
│   ├── player.tscn             ← "префаб" игрока
│   ├── enemy.tscn              ← "префаб" врага
│   └── ui/
│       └── hud.tscn            ← UI как сцена

В Inspector у Player.tscn те же узлы и компоненты, что и у Main.tscn. Любая сцена — это шаблон, который можно встраивать.

Веб

Это React: один и тот же <UserCard /> рендерится и как страница /users/me, и как виджет в списке. Никакого разделения “это компонент, а это страница”.

Unity

В Unity Scene (.unity) и Prefab (.prefab) — два разных формата. В Godot — один. Соответственно, “вложенные префабы” — это просто вложенные сцены.

Инстанцирование

В редакторе: правый клик на узел → Instantiate Child Scene → выбрать .tscn. Или drag-and-drop из FileSystem в Scene.

В коде:

const ENEMY_SCENE := preload("res://scenes/enemy.tscn")

func spawn_enemy(at: Vector3) -> void:
    var enemy = ENEMY_SCENE.instantiate() as Node3D
    enemy.global_position = at
    add_child(enemy)

instantiate() создаёт корневой узел сцены и все её дети. preload() загружает на этапе компиляции скрипта (статично), load() — в runtime.

Inherited Scenes — мощнее Prefab Variants

В Godot можно создать сцену, унаследованную от другой. Это даёт ту же модель, что Prefab Variant в Unity, но без отдельного механизма.

  1. Создайте Enemy.tscn — базовый враг.
  2. New Inherited Scene → выберите Enemy.tscn.
  3. Сохраните как EnemyElite.tscn — это inherited scene.
  4. В Inspector у любого узла переопределяете свойства (выделятся жирным/синим). Можно даже добавлять новые узлы.

Изменения базового Enemy.tscn пропагируются на EnemyElite.tscn, кроме override’ов.

Hierarchical scene composition

Сцена может содержать другую сцену как ребёнка, а та — третью. Глубина не ограничена. Получается чёткая иерархия “уровень → комната → дверь”, где каждая дверь — своя .tscn с собственной логикой. Изменили door.tscn — изменились все двери проекта.

Editable Children

По умолчанию вложенная сцена выглядит как один узел (корень) — дети скрыты, “запечатаны”. Чтобы переопределить дочерний узел вложенной сцены, нажмите Right Click → Editable Children. Тогда дети станут видны и доступны для override.

Resource — data-only ассеты

Resource — базовый класс для всего, что не узел. Сериализуется в .tres (текст) или .res (бинарный). Подходит для:

  • Конфигов (статы оружия, рецепты).
  • Базы данных (список предметов).
  • Curve / Gradient / Material / Mesh — всё это Resource.
  • Custom data объекты в вашей игре.

Resource — reference-counted: автоматически удаляется, когда нет ссылок.

Custom Resource — аналог ScriptableObject

Создаёте GD-скрипт, наследующий Resource, с @export-полями и class_name:

# weapon_data.gd
class_name WeaponData extends Resource

@export var display_name: String = "Pistol"
@export var icon: Texture2D
@export var damage: int = 10
@export var fire_rate: float = 0.4
@export var fire_sound: AudioStream
@export var projectile_scene: PackedScene

Теперь в FileSystem → New Resource → WeaponData → сохранить как pistol.tres, shotgun.tres. Каждый — независимый ассет с уникальными значениями.

Использование:

extends Node3D
class_name Weapon

@export var data: WeaponData
@onready var muzzle: Marker3D = $Muzzle

var _next_fire_time: float = 0.0

func try_fire() -> void:
    var now = Time.get_ticks_msec() / 1000.0
    if now < _next_fire_time:
        return
    _next_fire_time = now + data.fire_rate

    var sfx = AudioStreamPlayer3D.new()
    sfx.stream = data.fire_sound
    add_child(sfx)
    sfx.play()
    sfx.finished.connect(sfx.queue_free)

    var bullet = data.projectile_scene.instantiate()
    bullet.global_transform = muzzle.global_transform
    get_tree().current_scene.add_child(bullet)

В Inspector перетягиваете pistol.tres в поле data — оружие стреляет как пистолет. Меняете на shotgun.tres — как дробовик. Изменения в коде не нужны.

Resource ссылается shared

По умолчанию, если у двух узлов в Inspector один WeaponData.tres, и вы поменяете значение в коде (weapon.data.damage = 5), это изменит ассет на диске в редакторе. Для рантайма это норма, в редакторе — нежелательно (испортите ассет случайно).

Если нужен отдельный экземпляр, делайте data.duplicate() или включите свойство resource_local_to_scene = true.

Built-in vs External resources

В .tscn-файле сцена может содержать:

  • External resources — ссылки на отдельные .tres-файлы.
  • Built-in resources — встроенные внутрь сцены (когда вы создаёте ресурс прямо в Inspector через [empty] → New).

Built-in удобно для одноразовых ресурсов конкретной сцены (например, специфический материал двери). External — для переиспользуемых (одна WeaponData.tres на 10 врагов).

Сравнение с Unity

UnityGodot
Scene (.unity)PackedScene (.tscn) — корневая сцена
Prefab (.prefab)PackedScene (.tscn) — инстанцированная
Prefab VariantInherited Scene
Nested PrefabПросто вложенная сцена
Prefab OverrideEditable Children + per-property override
ScriptableObjectCustom Resource (.tres)

В следующей главе соберём всё вместе — практический FPS-контроллер на CharacterBody3D.