G1.2 — Nodes & the SceneTree¶
What you'll learn
- State what a node is, and that its type determines what it can do.
- Read the base hierarchy
Node → CanvasItem → {Node2D, Control}and choose the right base. - Understand the SceneTree: the live tree of nodes, rooted at one root, that drives the game.
- See that parent/child means ownership, and that a node outside the tree gets no callbacks.
How it applies
- The wrong base type silently lacks abilities. A UI element built on
Node2Dis positioned by raw coordinates instead of anchors and will not adapt to window size; a world object built onControlfights the layout system. Picking the base from the hierarchy is picking the abilities and the rules the node plays by. - Freeing a parent frees its children. Parent/child is ownership: remove the parent and the whole subtree goes with it. That is a feature (delete an enemy, its health bar and hitbox go too) and a hazard (free the wrong parent and lose nodes you needed). Knowing the rule prevents both surprises.
- A node outside the tree is inert. If a node is created but never added as a child, it gets no
_ready, no_process, no input, and draws nothing — the "my script isn't running" bug whose cause is that the node was never in the tree. Being in the tree is what makes a node live. - Order in the tree is order of execution and drawing. Nodes process and draw in tree order, so two nodes' relationship in the tree affects which draws on top and which updates first — a common source of "the UI is behind the world" or "this updated a frame late."
Concepts¶
What a node is¶
A node is the basic unit of a Godot game: a named object with a type, a set of properties, and
optionally children. The type is the important part — it decides what the node can do. A Sprite2D
can show a texture; a Button can be clicked; a Timer can count down; a plain Node does nothing
visual but is a fine home for logic. You build a game by assembling nodes into a tree and attaching
scripts (Part A) to give them behavior.
The base hierarchy, and choosing a base¶
Every node type descends from Node. The branch you care about in 2D:
Node (base of everything; logic/managers live here)
└── CanvasItem (anything that draws in 2D)
├── Node2D (positioned in the 2D world: sprites, bodies, areas)
└── Control (UI: laid out by anchors and containers)
Node— no position, no drawing. The right base for a manager, a state holder, an autoload, a pure-logic component.Node2D— has a 2D transform (position,rotation,scale). The base for things that exist in the world: the player, enemies, projectiles, pickups.Control— laid out by anchors and containers (G2.4), not by a raw position. The base for UI: labels, buttons, health bars, menus.
Node2D and Control both descend from CanvasItem, the common 2D-drawing base — which is why both
can be visible and modulated, but they are positioned by different systems. Mixing them up (a
Control where you needed world placement, or a Node2D where you needed responsive UI) is the
node-level version of the "wrong base class" lesson from L1.1.
Example
A tiny scene and the base each node should have:
Main (Node — just organizes; no transform needed)
├── Player (Node2D — exists in the world, has a position)
│ └── Sprite2D
└── HUD (CanvasLayer + Control children — UI, pinned to the screen)
└── HealthBar (Control)
Player is a Node2D because it occupies world space; HealthBar is a Control because it is UI
laid out on screen. Putting HealthBar under Player as a Node2D would make it move and scale
with the world camera instead of staying pinned — the reason UI sits under a CanvasLayer (G2.3).
The SceneTree¶
When the game runs, all active nodes form the SceneTree — a single tree rooted at a root viewport
(the game window). The Scene dock you edit in is the design-time view of a scene; at runtime, the
running scene is placed into this one big tree. get_tree() from any node returns the SceneTree
object, which drives the main loop, delivers the per-frame callbacks (G2.1), and manages pause and
scene changes.
A node becomes part of the tree when it is added as a child of a node already in the tree (via
add_child, or by being part of a scene that is loaded). Until then it is an orphan: it exists in
memory but is not live.
Parent/child is ownership¶
The parent/child relationship is ownership, not mere grouping:
- Freeing a node frees its entire subtree.
queue_free()on a parent takes its children with it. - A
Node2D's transform is relative to its parent: move the parent and the children move with it (G2.2). This is why you group a character and its sprite, weapon, and hitbox under one parent. - Processing, visibility, and pause cascade down the tree.
So the structure is not cosmetic. Where a node sits decides what owns it, what it moves with, and when it lives and dies. The guidance (which the game books lean on heavily) is to make parent/child reflect ownership: if removing the parent should logically remove the child, the child belongs under it.
A node outside the tree is inert¶
Because the SceneTree drives everything, a node that is not in the tree receives no lifecycle
callbacks, no input, and does not draw. Creating a node with .new() and forgetting to add_child it
is the classic "I wrote _ready but it never runs" bug — _ready fires when the node enters the
tree, and an orphan never does. Being in the tree is the difference between a node that exists and a
node that participates.
Walkthrough¶
- In a new scene, add a plain
Nodeas the root. Under it, add aNode2D, and under that, aSprite2D(assign it any texture, or Godot's icon). Note the Scene dock shows this as a tree. - Select the
Node2Dand move it in the 2D viewport. TheSprite2Dchild moves with it — the child's position is relative to the parent. Now move theSprite2Dalone; it moves within the parent. - Add a
Labeldirectly under the rootNode. Try to position it like a world object; notice it is aControland behaves by anchors, not free placement (full treatment in G2.4). This is theNode2D-versus-Controldistinction, felt. - Run with F6. In the editor's Remote scene tree (you meet it properly in G2.8), observe that the running game's tree mirrors what you built — that live tree is the SceneTree.
Optional sanity check
Select the root Node, attach a script, and in _ready print "root ready". Run — it prints. Now,
in that script, do var loose := Node.new() with its own _ready that prints "loose ready", but do
not add_child(loose). Run again: "loose ready" never prints, because the node never entered the
tree. Add add_child(loose) and it prints. That is the orphan-node rule, demonstrated.
Self-check quiz¶
Q1 — You are building a health bar that should stay pinned on screen. Which base type, and why?
A. Node2D, because it has a position you can set.
B. Control (under a CanvasLayer), because UI is laid out by anchors and pinned to the screen, not placed in world space.
C. Plain Node, because it is the simplest.
D. Sprite2D, because it draws.
Reveal answer
B. A health bar is UI; Control nodes are laid out by anchors/containers and, under a
CanvasLayer, stay fixed on screen regardless of the world camera. A would tie the bar to world
coordinates so it scrolls with the camera. C cannot draw or lay out UI. D is a world sprite, not
a layout-aware UI element.
Q2 — You call var n = Node.new() and give it a _ready that prints, but it never prints. Why?
A. _ready only runs in the main scene.
B. The node was never added to the tree (add_child), so it is an orphan and never receives _ready.
C. Node.new() is invalid.
D. _ready requires @onready.
Reveal answer
B. _ready fires when a node enters the tree; a node created with .new() but never
added as a child stays an orphan and gets no callbacks. A is false — _ready runs for any node
that enters the tree, not only the main scene. C is valid code. D confuses a variable annotation
with the lifecycle callback.
Q3 — What happens to a node's children when you queue_free() the node?
A. The children are reparented to the root automatically. B. The children are freed too — the whole subtree goes, because parent/child is ownership. C. Nothing; children are independent. D. Only direct children are freed, not grandchildren.
Reveal answer
B. Parent/child expresses ownership, so freeing a node frees its entire subtree, children and grandchildren alike. A invents automatic reparenting. C and D understate the rule — the whole subtree is freed, which is exactly why you group owned nodes (sprite, hitbox, bar) under the thing that owns them.
Integration question¶
Q4 — open
You build an enemy as Node2D with child nodes for its sprite, a hitbox, and a floating health bar,
and you put the health bar's Control directly under the enemy Node2D. Two problems appear: the
health bar scrolls and scales oddly with the camera instead of floating cleanly, and when the enemy
dies you call queue_free() on the enemy and worry whether you must also free the hitbox and bar.
Resolve both using this chapter's ideas about base types and parent/child ownership.
Reveal expected answer
The health-bar behavior is the Node2D-versus-Control distinction: a Control placed
under a world Node2D is dragged through the world transform and camera, so it scales and
scrolls instead of behaving like screen UI. The fix is to put UI under a CanvasLayer (or
otherwise out of the world transform) so anchors lay it out against the screen — world objects
are Node2D, UI is Control under a CanvasLayer (G2.3–G2.4). The cleanup worry is
answered by parent/child being ownership: because the sprite, hitbox, and bar are children of
the enemy, queue_free() on the enemy frees the entire subtree automatically — you do not free
them individually. That is precisely why owned parts are grouped under the thing that owns them:
one free call disposes the whole unit. The chapter's two threads — choose the base for the rules
you want, and let the tree express ownership — each resolve one half of the problem.