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.
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.”
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.
- Create
Enemy.tscn— a base enemy. - New Inherited Scene → choose
Enemy.tscn. - Save it as
EnemyElite.tscn— this is an inherited scene. - 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.
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.
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
.tresfiles. - 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
| Unity | Godot |
|---|---|
| Scene (.unity) | PackedScene (.tscn) — root scene |
| Prefab (.prefab) | PackedScene (.tscn) — instantiated |
| Prefab Variant | Inherited Scene |
| Nested Prefab | Simply a nested scene |
| Prefab Override | Editable Children + per-property override |
| ScriptableObject | Custom Resource (.tres) |
In the next chapter we put it all together — a practical FPS controller built on CharacterBody3D.