Scene Tree and Node Paths

$, get_node, @onready, groups, unique names, and null instance errors.

stablegodotdebuggingarchitecture
On this page
Dialog UI whose script paths target nested Control children
Deep paths like $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

  1. Print get_path() from _ready.
  2. print(get_children()) / inspect remote scene tree while running.
  3. Prefer groups for cross-scene finds (dialog, player).
  4. 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

FragileMore stable
$"../HUD/Panel/Label"group "dialog_box"
assuming child ordernamed child
autoload missing in --scriptrun 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.