~3 min read

PackedScene and Resource

A universal scene/prefab container and data-only assets.

PackedScene — Scene and Prefab in One

Godot’s key architectural feature: a scene and a prefab are the same entity. A .tscn file stores a tree of nodes; this file can be opened as the project’s root scene or instantiated as a child in another scene. Inside the engine, both variants are a PackedScene resource.

res://
├── scenes/
│   ├── main.tscn               ← the main level scene
│   ├── player.tscn             ← the player "prefab"
│   ├── enemy.tscn              ← the enemy "prefab"
│   └── ui/
│       └── hud.tscn            ← UI as a scene

In the Inspector, Player.tscn has the same nodes and components as Main.tscn. Any scene is a template that can be embedded.

Web

This is React: the same <UserCard /> renders both as the /users/me page and as a widget in a list. There is no separation of “this is a component, and that is a page.”

Unity

In Unity, a Scene (.unity) and a Prefab (.prefab) are two different formats. In Godot they are one. Accordingly, “nested prefabs” are simply nested scenes.

Instantiation

In the editor: right-click a node → Instantiate Child Scene → choose a .tscn. Or drag-and-drop from the FileSystem into the Scene.

In code:

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() creates the scene’s root node and all of its children. preload() loads at script compile time (statically), while load() loads at runtime.

Inherited Scenes — More Powerful Than Prefab Variants

In Godot you can create a scene inherited from another. This gives the same model as Unity’s Prefab Variant, but without a separate mechanism.

  1. Create Enemy.tscn — a base enemy.
  2. New Inherited Scene → choose Enemy.tscn.
  3. Save it as EnemyElite.tscn — this is an inherited scene.
  4. In the Inspector, on any node you override properties (they are highlighted in bold/blue). You can even add new nodes.

Changes to the base Enemy.tscn propagate to EnemyElite.tscn, except for the overrides.

Hierarchical scene composition

A scene can contain another scene as a child, and that one can contain a third. The depth is unlimited. The result is a clear “level → room → door” hierarchy, where each door is its own .tscn with its own logic. Change door.tscn and all doors in the project change.

Editable Children

By default, a nested scene appears as a single node (the root) — the children are hidden, “sealed.” To override a child node of a nested scene, choose Right Click → Editable Children. The children then become visible and available for override.

Resource — Data-Only Assets

Resource is the base class for everything that is not a node. It is serialized to .tres (text) or .res (binary). Suitable for:

  • Configs (weapon stats, recipes).
  • Databases (an item list).
  • Curve / Gradient / Material / Mesh — these are all Resources.
  • Custom data objects in your game.

A Resource is reference-counted: it is deleted automatically when there are no references.

Custom Resource — an Analog of ScriptableObject

You create a GD script that inherits Resource, with @export fields and a 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

Now in FileSystem → New Resource → WeaponData → save as pistol.tres, shotgun.tres. Each is an independent asset with unique values.

Usage:

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)

In the Inspector you drag pistol.tres into the data field — the weapon fires like a pistol. Switch to shotgun.tres — like a shotgun. No code changes needed.

A Resource is referenced as shared

By default, if two nodes in the Inspector use the same WeaponData.tres, and you change a value in code (weapon.data.damage = 5), this modifies the asset on disk in the editor. For runtime this is normal; in the editor it is undesirable (you could ruin the asset by accident).

If you need a separate instance, call data.duplicate() or enable the resource_local_to_scene = true property.

Built-in vs External Resources

In a .tscn file, a scene can contain:

  • External resources — references to separate .tres files.
  • Built-in resources — embedded inside the scene (when you create a resource directly in the Inspector via [empty] → New).

Built-in is convenient for one-off resources of a specific scene (for example, a door’s specific material). External is for reusable ones (one WeaponData.tres for 10 enemies).

Comparison with Unity

UnityGodot
Scene (.unity)PackedScene (.tscn) — root scene
Prefab (.prefab)PackedScene (.tscn) — instantiated
Prefab VariantInherited Scene
Nested PrefabSimply a nested scene
Prefab OverrideEditable Children + per-property override
ScriptableObjectCustom Resource (.tres)

In the next chapter we put it all together — a practical FPS controller built on CharacterBody3D.