Godot Basics We Used
Scene tree, CharacterBody2D, signals, exports, and other project-relevant fundamentals.
On this page
CharacterBody2D clerk, room art as Sprite2Ds, and a CanvasLayer clock HUD that does not move with the camera.Grounded in what Night Audit actually touches — not a Godot tour.
Scene / node model
Everything is a tree of nodes. Gameplay lives in scenes (.tscn) that instance other scenes.
| Role | Node type we used |
|---|---|
| Player / NPC body | CharacterBody2D |
| Sprite animation | AnimatedSprite2D |
| Physics shape | CollisionShape2D |
| Interact radius | Area2D |
| Screen-space UI | CanvasLayer + Control / PanelContainer |
| Following camera | Camera2D (child of the clerk) |
| Pathfinding | NavigationAgent2D + NavigationRegion2D |
Implemented: scenes/game.tscn instances hotel.tscn, clerk.tscn, dialog, clock, midnight report, accusation.
$NodeName and node paths
$AnimatedSprite2D is shorthand for get_node("AnimatedSprite2D") relative to the script’s node.
$AnimatedSprite2D.play("walk_down")
$NavigationAgent2D.target_position = _stand_position
Nested paths appear in dialog UI via @onready:
@onready var _portrait: TextureRect = $MarginContainer/HBoxContainer/Portrait
If you rename or reparent a node, that path becomes a null instance. See Scene Tree and Node Paths.
Lifecycle callbacks
| Callback | Used for |
|---|---|
_ready() | Wire signals, apply guest data, bake walls/navmesh |
_process | Player input + move_and_slide (clerk) |
_physics_process | Guest steering on the navmesh |
_unhandled_input | Interact (E), dialog advance, time-scale keys |
_exit_tree | Release stand slots when a guest is freed |
Prefer _physics_process for anything that collides; we kept player input in _process and it works because move_and_slide is still called every frame.
@export and inspector wiring
@export var guest_data: GuestData
@export_range(0, 1000) var speed := 120
@export var id: Id # Station enum on a Node2D
Exports let stations pick an id in the editor and let guest speed be tuned without code edits.
Signals
We use signals for loose coupling:
dialog_closed— guest resumes after talkarrived_at_station/checked_in— manager / clockhour_changed/midnight_reached— HUD and day loopArea2D.body_entered/body_exited— interaction range
dialog_box.dialog_closed.connect(_on_station_dialog_closed, CONNECT_ONE_SHOT)
CONNECT_ONE_SHOT avoids leaking connections across conversations.
Scene instancing
Guests are never hand-placed for the full roster. GuestManager loads JSON and instances one packed scene:
var packed: PackedScene = load("res://scenes/guest.tscn")
var guest: Node = packed.instantiate()
guest.guest_data = data
_parent.add_child(guest)
Autoloads
Registered in project.godot:
GameState— day, hour, possession flags, clockGuestManager— stations, stands, spawn queueMusic
Gotcha: --script / --check-only do not load autoloads. See Debugging Godot.
Groups
"player"— clerk body; guests ignore collisions with it"dialog_box"— find the UI without a hard scene path"room"— camera collects Room areas
var dialog_box = get_tree().get_first_node_in_group("dialog_box")