Nodes & Scenes In Depth
Godot Fundamentals
Chapter 3 · Nodes & Scenes In Depth
Chapter 1 introduced the scene tree conceptually; Chapter 2 covered GDScript itself without touching nodes at all. This chapter puts both together: how to actually navigate a scene tree from code, how to create new nodes at runtime by instancing a whole scene, and a first look at signals — Godot's own way for one node to tell another that something happened, without the two needing to know much about each other.
Navigating the Scene Tree
Once a script is attached to a node, it can reach other nodes in the same tree in a few ways.
| Method | Gets |
|---|---|
get_parent() | This node's own parent |
get_children() | An Array of every direct child |
get_node("Path") | A specific descendant, by relative path |
$Path | Shorthand for get_node("Path") — used constantly in real code |
$Path for anything set up in the editor — it's shorter, and Godot's own
editor autocompletes real node names as you type it, catching a typo immediately rather than failing
silently at runtime.
Instancing Scenes at Runtime
Every scene saved to a .tscn file can be loaded and created fresh from a script — this is how
a game spawns things that don't exist yet when the game starts: a bullet, an enemy, a pickup.
| Call | When it runs | Use it when... |
|---|---|---|
preload("res://x.tscn") | At compile time, once | The path is a fixed, known string |
load("res://x.tscn") | At runtime, each call | The path is built dynamically (e.g. from a variable) |
.instantiate() | Whenever called | Creates one real, independent copy of that scene as a node |
.instance().
That was the Godot 3 method name; Godot 4 renamed it to .instantiate(). If a tutorial's code
throws an "Invalid call" error on that exact line, this rename is very often why.
An instanced node exists in memory but isn't part of the visible scene tree until add_child()
actually attaches it — exactly like Chapter 2's typed Array: creating the object and placing it
somewhere are two separate steps.
Removing a node
The reverse operation is queue_free() — it schedules the node for deletion at the end of the
current frame, rather than deleting it immediately mid-execution (which can cause errors if other code is
still using it that same frame).
A First Look at Signals
A signal is Godot's own way for a node to announce "something happened" without needing to know who, if anyone, is listening. Any node can declare a custom signal, emit it when the relevant thing occurs, and any other node can connect a function to run whenever that signal fires.
Godot's built-in nodes already come with their own signals too — a Button has a
pressed signal, an Area2D has a body_entered signal, and so on. Custom
signals declared with signal work exactly the same way, connected and emitted with the identical
syntax.
on_click handler, a custom pub/sub system), the
shape is the same idea: something emits an event, and zero or more listeners react to it, with neither side
needing a direct reference to the other's internals. What's different is that this pattern is a first-class
part of the language itself here, declared with signal and connected with .connect(),
rather than something you'd build yourself with a list of callback functions the way you might in plain
Python.
Coding Challenges
get_children() to print the name of every child, and
separately uses $Body to print that one child's name directly.
.tscn file. From a different
script, preload it, instantiate three copies with .instantiate(), and
add_child() each one at a different position so all three appear in the running scene.
coin_collected(amount: int) on one node. Emit it with a
sample amount, and connect a function on a different node that prints a message using the amount it
received.
Quick Reference — Nodes & Scenes In Depth
get_parent(),get_children(),get_node("Path")/$Pathpreload("res://x.tscn")(fixed path) vs.load(...)(dynamic path).instantiate()creates a node from a PackedScene —.instance()was the old Godot 3 nameadd_child(node)attaches it to the tree;queue_free()removes it safely at end of framesignal name(params)declares a signal;signal_name.emit(values)fires it;node.signal_name.connect(func)listens for it