G1.3 — Scenes: Composition & Instancing¶
What you'll learn
- Build, save, and recognize a scene (
.tscn) as a reusable template — a tree of nodes. - Instance a scene in the editor and at runtime with
PackedScene.instantiate(). - Choose
preload(compile time) versusload(runtime) for getting a scene into code. - See that editing the scene file updates every instance, except where an instance overrides.
How it applies
- Copy-pasted nodes become 50 divergent copies. Build an enemy by hand fifty times and a design change means fifty edits — and the day you miss two, you have three enemy variants you did not intend. A scene is the single source of truth for a structure; instancing reuses it without duplicating it.
- An un-added instance is inert.
instantiate()builds a node tree in memory; until youadd_childit, it is an orphan (G1.2) — it exists but does not run or draw. The "I spawned an enemy and nothing happened" bug is usually a missingadd_child. - The 3.x method name is a trap. In Godot 4 the method is
instantiate(); the oldinstance()from 3.x errors. Tutorials and snippets from the 3.x era will steer you wrong here. - Local overrides hide scene edits. If an instance overrides a property, later changes to that property in the scene file do not reach that instance. Knowing this explains why "I changed the scene but this one copy didn't update."
Try it first
You have designed an enemy: a body node, a sprite, a collision shape, and a floating health bar — four nodes configured just so. The level needs fifty of them, and you expect to keep tweaking the design for weeks. Before reading on, decide: how do you place fifty without hand-building fifty, and so that one design change updates all fifty at once? Name the mechanism you would reach for.
Concepts¶
A scene is a reusable tree of nodes¶
A scene is a tree of nodes saved to a .tscn file. Everything you built in G1.2 — a root with
children — is a scene the moment you save it. The key idea is that a scene is a template, much
like a class is a template for objects: you design it once, then create as many instances as you
need. The player, each enemy type, a projectile, a UI panel — each is its own scene, instanced where
used.
This is the answer to the "Try it first" prompt: build the enemy once as a scene, then instance it fifty times. Each instance is a live copy of the template; change the template and every instance follows.
Instancing in the editor¶
With a scene saved, you add an instance of it inside another scene: in the Scene dock, use Instantiate
Child Scene (the link-shaped button) and pick the .tscn. The instance appears as a single node
(expandable to show its inner tree). Fifty instances of enemy.tscn are fifty copies that all track
the one file.
Instancing at runtime¶
To spawn instances from code, you load the scene as a PackedScene and call instantiate():
const EnemyScene := preload("res://enemy.tscn") # a PackedScene, loaded at compile time
func spawn() -> void:
var enemy := EnemyScene.instantiate() # build a fresh node tree from the template
add_child(enemy) # add it to the tree so it becomes live
enemy.position = Vector2(100, 100)
Three steps, and all three matter: load the PackedScene, instantiate() it (which returns a node
tree), and add_child it (without which it is an orphan, G1.2). The method is instantiate() in
Godot 4 — not instance(), which was the Godot 3.x spelling and will error here.
Example
The "reuse without duplication" payoff, in code:
const EnemyScene := preload("res://enemy.tscn")
func spawn_wave(count: int) -> void:
for i in range(count):
var e := EnemyScene.instantiate()
add_child(e)
e.position = Vector2(i * 40, 0)
Fifty enemies, one template. Redesign enemy.tscn — add a shadow, retune the collision shape — and
all fifty spawned next run reflect it, with no edit to this loop. This is exactly the mechanic the
game books' reusable component scenes are built on; the architecture of those components is their
subject, the instancing mechanic is this chapter's.
preload versus load¶
Both turn a path into a usable resource; they differ in when:
preload("res://x.tscn")runs at compile time: the resource is loaded as the script is parsed, so it is ready before any code runs. Use it for scenes/resources known at edit time (the common case). It must take a constant string path.load("res://x.tscn")runs at runtime, when that line executes. Use it when the path is computed or the load should be deferred (a level chosen at runtime, an asset loaded on demand).
preload is the default reach; load is for when you genuinely cannot know the path until the game is
running.
Scene edits propagate; local overrides do not¶
Because instances track the scene file, editing the .tscn updates every instance — that is the whole
benefit. The exception: if you change a property on an individual instance, that becomes a local
override, and later changes to that property in the scene file no longer reach that instance (the
override wins). This is intentional (one enemy can be recolored without forking the scene), but it is
also the explanation when "I edited the scene and one copy didn't update" — that copy has a local
override on the property you changed. Godot marks overridden properties in the Inspector with a revert
arrow.
Walkthrough¶
- Build a small scene: a
Node2Droot namedCoinwith aSprite2Dchild (any texture). Save it asres://coin.tscn. This is your template. - Create a second scene with a
Noderoot. Use Instantiate Child Scene to add two instances ofcoin.tscn. Move them apart. Confirm each is a singleCoinnode in the Scene dock. - Open
coin.tscnand change the sprite (scale it up, swap the texture). Reopen the second scene: both instances reflect the change. One edit, every instance. - Spawn from code: attach a script to the second scene's root with
const CoinScene := preload("res://coin.tscn"), and in_readyinstantiate one,add_childit, and set itsposition. Run with F6 and confirm a third coin appears. Then remove theadd_childline and confirm the coin does not appear — the orphan.
Optional sanity check
On one instanced Coin, change a property in the Inspector (e.g. modulate to a tint). Note the
revert arrow appears — that property is now a local override. Change the same property in
coin.tscn. The overridden instance ignores the scene change; the un-overridden one follows it.
That is local-override-versus-scene-edit, seen directly.
Self-check quiz¶
Q1 — Why instance one enemy.tscn fifty times instead of copy-pasting the enemy's nodes fifty times?
A. Copy-pasting is impossible in Godot. B. Instances all track the one scene file, so a single design change updates all of them; copies diverge and must each be edited. C. Instances run faster than copies. D. There is no real difference.
Reveal answer
B. A scene is a single source of truth; its instances follow edits to the file, so fifty instances cost one edit to change. Hand-made copies are independent and must each be updated, inviting divergence. A is false (you can copy-paste — it is just the worse choice). C is not the reason. D ignores the maintenance difference that is the entire point.
Q2 — var e := EnemyScene.instantiate() runs, but the enemy never appears. Most likely missing line?
A. e.start()
B. add_child(e) — without it the instance is an orphan, not in the tree.
C. e.visible = true
D. preload should have been load.
Reveal answer
B. instantiate() builds the tree in memory; add_child is what puts it into the SceneTree
so it becomes live and draws (G1.2). A invents a method. C is unlikely — nodes default to
visible; the issue is that an orphan does not draw at all. D is unrelated to whether the node
appears.
Q3 — A tutorial says var e = EnemyScene.instance() but it errors in your Godot 4 project. Why?
A. The path is wrong.
B. instance() is the Godot 3.x name; in Godot 4 the method is instantiate().
C. You must use load, not preload.
D. PackedScene cannot be instanced from code.
Reveal answer
B. The method was renamed instance() → instantiate() in Godot 4, so 3.x-era snippets
error. A is possible in general but not the issue described (the method name is the symptom).
C is unrelated to the method name. D is false — instancing from code is exactly what PackedScene
is for.
Integration question¶
Q4 — open
A spawner does const BatScene := preload(\"res://bat.tscn\") and, in a loop, BatScene.instantiate()
fifty times, setting each one's position, but never calls add_child. Nothing appears. After fixing
that, the designer edits bat.tscn to tint every bat darker (changing its modulate), but a few
specific bats in one level keep their old color. Diagnose both, connecting each to a chapter concept,
and note why the second one is intended behavior rather than a bug.
Reveal expected answer
Nothing appears because instantiate() only builds each bat's node tree in memory; without
add_child, every bat is an orphan (G1.2) — not in the SceneTree, so it does not draw or run.
The fix is to add_child(bat) each instance after instantiating it. A few bats keep their old
color because those instances carry a local override on modulate: someone set that
property on those specific instances, and a local override shields the instance from later changes
to the same property in bat.tscn. This is intended — it is what lets one bat be recolored
without forking the scene — so it is not a bug in the engine but a
consequence of how instancing balances "follow the template" against "allow per-instance
differences." The remedy is to clear the override (the revert arrow in the Inspector) on the bats
that should track the scene. Both halves come from the same model: a scene is a single template,
instances follow it once they are in the tree, and overrides are the deliberate exception.