Skip to content

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) versus load (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 you add_child it, 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 missing add_child.
  • The 3.x method name is a trap. In Godot 4 the method is instantiate(); the old instance() 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

  1. Build a small scene: a Node2D root named Coin with a Sprite2D child (any texture). Save it as res://coin.tscn. This is your template.
  2. Create a second scene with a Node root. Use Instantiate Child Scene to add two instances of coin.tscn. Move them apart. Confirm each is a single Coin node in the Scene dock.
  3. Open coin.tscn and change the sprite (scale it up, swap the texture). Reopen the second scene: both instances reflect the change. One edit, every instance.
  4. Spawn from code: attach a script to the second scene's root with const CoinScene := preload("res://coin.tscn"), and in _ready instantiate one, add_child it, and set its position. Run with F6 and confirm a third coin appears. Then remove the add_child line 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.