Scene Tree and Node Paths
$, get_node, @onready, groups, unique names, and null instance errors.
On this page
$MarginContainer/HBoxContainer/Portrait break if you rename nodes — that is the usual null instance source.Lookups we use
$AnimatedSprite2D # child
$MarginContainer/HBoxContainer/Portrait
get_node("Clerk")
get_tree().get_first_node_in_group("dialog_box")
get_tree().get_nodes_in_group("room")
@onready defers the lookup until _ready, which is correct for scene-owned children — still fails if the path is wrong.
Why paths break
Moving SpeakerName from $Label to $VBoxContainer/SpeakerName leaves old scripts holding null.
Instancing also changes ownership: a path that worked inside dialog.tscn may be wrong when the script is on a different node than you think (script on PanelContainer, not CanvasLayer).
Debugging null instance
- Print
get_path()from_ready. print(get_children())/ inspect remote scene tree while running.- Prefer groups for cross-scene finds (dialog, player).
- For editor-assigned refs,
@export var dialog_box: NodePath+get_node(dialog_box)is more honest than deep$strings — we mostly used groups instead.
Unique names
Godot %MyNode (unique name in owner scene) is a good next step when a deep path keeps breaking. Not heavily used in this project yet — recommended.
Fragile vs stable references
| Fragile | More stable |
|---|---|
$"../HUD/Panel/Label" | group "dialog_box" |
| assuming child order | named child |
autoload missing in --script | run as real scene |
Hotel note
Room art positions live in hotel.tscn; walkable rects live in hotel_layout.gd. That is intentional duplication — moving a room requires updating both. Documented in design.md / DEVELOPING.md.