Dialog UI

PanelContainer anchors, CanvasLayer, portraits, and Control vs Node2D positioning.

stablegodotuidialog
On this page
Dialog PanelContainer anchored to bottom center of the viewport
Correct placement: PanelContainer under a CanvasLayer, anchors at the bottom center — not world position.

Scene shape

scenes/dialog.tscn:

CanvasLayer (dialog_box)
└── PanelContainer  [group: dialog_box]  ← script lives HERE
    └── MarginContainer
        └── HBoxContainer
            ├── Portrait (TextureRect 192×192)
            └── VBoxContainer
                ├── Instructions
                ├── SpeakerName
                └── DialogText (RichTextLabel)

The script extends PanelContainer, not the CanvasLayer. The group is on the panel so get_first_node_in_group("dialog_box") returns the scripted node.

Bottom-center anchoring

In the scene (anchors preset bottom-wide / center):

anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -420
offset_top = -236
offset_right = 420
offset_bottom = -20

In _ready() we reinforce the bottom margin:

offset_bottom = -20.0
offset_top = offset_bottom - 216.0

Why not position = Vector2(...)?

Control nodes under a properly anchored parent use anchors + offsets. Setting position like a Node2D fights the layout system and often jumps when the viewport resizes.

Bug: dialog appears at the top of the screen

Symptom

Box stuck under the clock / top edge after a refactor.

Cause

Anchors left at default (top-left) while offsets assumed bottom positioning — or the panel was briefly parented as a Node2D-style child of the world instead of under a CanvasLayer.

Fix

Put UI on a CanvasLayer. Set anchors to bottom (anchor_top = anchor_bottom = 1.0) and use negative offset_top / offset_bottom to rise above the edge.

Lesson

World coords (Node2D.global_position) and viewport UI (Control anchors) are different spaces. Camera movement should not move dialog — that is why CanvasLayer exists.

Stretch / different screen sizes

project.godot uses:

window/stretch/mode="canvas_items"
window/stretch/aspect="expand"

Anchored bottom-center panels survive resolution changes better than absolute pixel placement.

Portraits

Clerk portrait loads from a constant path; guest portrait is passed into start_dialog. is_player on each line swaps which texture the TextureRect shows.

Null path after UI restructure

Symptom

Invalid access to property or key 'position' on a base object of type 'null instance'

(or .text / .texture on null)

Cause

@onready var x = $Old/Path no longer exists after you renamed nodes (e.g. moved labels under VBoxContainer).

Fix

Fix the path, or use %UniqueName / groups. Re-open the scene and reassign @onready targets.

Lesson

Any $A/B/C is a compile-time string, runtime lookup. Refactors break silent until the line runs.