Patterns Worth Reusing

Compact jam-ready recipes extracted from Night Audit.

stablegodotgdscriptarchitecture
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

Press E interaction prompt example
Recipe result: range flag + highlight labels.

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

Dialog UI recipe result
Recipe result: bottom dialog with per-line speaker.
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

Room clamped camera in stairwell
Recipe result: limits follow the active room.

Child Camera2D on player; set limit_* from the smallest overlapping room rect; expand limits to at least one screen.


Bake nav from rectangles

Nav and collision overlay
Recipe result: generated walls + walkable visualization.

Maintain walkable Rect2s → traversable outlines → NavigationServer2D.bake_from_source_geometry_data. Overlap regions slightly.