Age crops and NPCs on game days in Godot
Pick the right Calendar Time ageing model: discrete date ticks for crops and birthdays, continuous clock seconds for buffs and spoilage.
This recipe ages things on the right clock. Crops and birthdays want calendar days ("takes 3 game-days"); buffs and spoilage want game seconds ("lasts 5 game-minutes"). Calendar Time offers both models — pick per system, not per project — and your game derives its own facts from the clock without ever overriding it.
1. Age crops one stage per game day
Crops want discrete calendar-date ageing. Listen to
clock.signal_bus.date_changed and advance one growth stage per tick. Each
calendar day fires exactly once no matter how fast the clock runs, so speeding
up time brings harvest sooner without skipping stages:
func _ready() -> void:
clock.signal_bus.date_changed.connect(_on_date_changed)
var growth_stage: int = 0
const MAX_STAGE: int = 3
func _on_date_changed(event: DateChangeEvent) -> void:
if growth_stage < MAX_STAGE:
growth_stage += 1
_update_sprite(growth_stage)NPC birthdays work the same way: one tick per game-year per character. For a full farm loop built on this tick — soil plots, seasons, save-safe harvests — see the farming system recipe.
2. Age buffs in continuous game seconds
Effects that should scale with clock speed and pause with the clock —
potion expiry, cooldowns, food spoilage — belong on the continuous model.
AgeService tracks elapsed game time from GameClock.game_seconds_advanced:
var age_service := AgeService.new()
age_service.clock = clock
var component := AgeComponent.new()
component.total_game_seconds = 300.0 # 5 game-minutes
age_service.register(component)
# Later: how much is left?
var remaining := component.remaining_game_secondsChoosing wrong here is the classic bug: date ticks make buffs survive fast-forwarding, while second-counting makes crops skip stages. The model picker with both APIs is Ageing: Two Time Models.
3. Derive game facts; never override the clock
When systems need "is it night" for decisions — shopkeepers closing, guards changing patrols, encounters rolling — derive a game-owned fact from the clock's authoritative time-of-day. The clock owns the stage (objective, save-stable); your game owns what each system counts as night:
# Shopkeeper policy: dusk counts as closed. Guard policy may differ —
# same clock, different facts, no drift between systems.
var is_closed := _tod_stage == TOD.DUSK or _tod_stage == TOD.NIGHTRead the authoritative stage first and keep any fallback strictly weaker, so the clock always wins once it can speak. The full authority pattern is in Game Facts Driven by Clock Time.