Patterns Worth Reusing
Compact jam-ready recipes extracted from Night Audit.
On this page
Top-down CharacterBody2D controller
Problem: Move on X/Y without gravity.
extends CharacterBody2D
@export var speed := 200.0
func _physics_process(_d: float) -> void:
var v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = v * speed
move_and_slide()
Gotcha: Strip gravity from the platformer template. Bind WASD in the Input Map.
Press-E interaction
Problem: Talk when near a body.
var in_range := false
func _on_area_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
in_range = true
func _unhandled_input(event: InputEvent) -> void:
if in_range and event.is_action_pressed("interact"):
start_conversation()
get_viewport().set_input_as_handled()
Gotcha: Mark input handled so UI does not double-fire.
Multi-line dialog
var lines: Array[Dictionary] = []
var i := 0
var open := false
func start(lines_in: Array) -> void:
if lines_in.is_empty(): return
lines.assign(lines_in)
i = 0
open = true
show_line()
func advance() -> void:
i += 1
if i >= lines.size(): close()
else: show_line()
Gotcha: .assign for typed arrays; reset i every open.
Show/hide dialog UI
visible = true # open
visible = false # close
# script on PanelContainer under CanvasLayer
Gotcha: Keep on CanvasLayer so camera movement does not drag it.
Bottom-align a dialog panel
Anchors: left/right 0.5, top/bottom 1.0. Offsets: negative top/bottom, ± half-width on x.
Swap NPC SpriteFrames
$AnimatedSprite2D.sprite_frames = data.sprite_frames
$AnimatedSprite2D.play("idle_down")
Enum state machine
enum State { IDLE, WALK, WAIT }
var state := State.IDLE
func _physics_process(_d: float) -> void:
match state:
State.WALK: _move()
_: velocity = Vector2.ZERO
Wait using a Timer
await get_tree().create_timer(seconds).timeout
# re-validate state after await
Gotcha: Use a generation counter if the wait can be cancelled.
Safe cross-scene reference
var box = get_tree().get_first_node_in_group("dialog_box")
if box == null: return
Animation from movement
if get_real_velocity().length() > 2.0:
play_walk()
else:
play_idle()
Room-clamped follow camera
Child Camera2D on player; set limit_* from the smallest overlapping room rect; expand limits to at least one screen.
Bake nav from rectangles
Maintain walkable Rect2s → traversable outlines → NavigationServer2D.bake_from_source_geometry_data. Overlap regions slightly.