Timers and Scheduling

create_timer dwell, hourly clock, spawn cooldown, time_scale, avoiding await spaghetti.

stablegodotgdscriptnpc
On this page
Clock HUD showing Day 1 8AM over the main hallway
Clock HUD on a CanvasLayer. Hours advance via create_timer; keys 1–4 change Engine.time_scale.

Patterns we use

One-shot dwell

await get_tree().create_timer(_dwell_duration(step)).timeout

Guards after await: still AT_STATION, same schedule step, not in conversation.

Hourly clock loop

while _clock_running:
	await get_tree().create_timer(seconds_per_hour).timeout
	if not _clock_running:
		return
	advance_hour()

stop_hourly_clock() flips the flag; the loop exits on the next iteration.

Spawn cooldown with generation token

Cancel stale awaits when midnight resets arrivals:

func cancel_pending_spawn() -> void:
	_spawn_generation += 1

func _wait_and_spawn() -> void:
	var generation := _spawn_generation
	await get_tree().create_timer(_spawn_cooldown_seconds).timeout
	if generation != _spawn_generation:
		return
	_spawn_next()

Game time vs real time

  • seconds_per_hour maps real seconds → one in-game hour.
  • Periods (TIME_PERIODS) map hourmorning / etc. for dialog.
  • Engine.time_scale (keys 1–4) speeds timers and physics; player speed is compensated.

Avoid giant await chains

Prefer:

  1. State + timer callback / await with guards
  2. Signals (checked_in, dialog_closed, midnight_reached)

Over:

await walk()
await talk()
await wait()
await walk()
# brittle when player interrupts

Interrupts (talk while walking) are why guests use flags + signals instead of one linear await script.

Schedule data

JSON steps: { "station": "BAR", "dwell_seconds": 20 }. Front desk waits on dialog, not dwell.