Calendar Time supports two distinct ageing mechanics. Pick the one that matches your game design.
Model 1: Clock-time ageing (continuous)
"This effect lasts N game-seconds."
AgeService tracks elapsed game time from GameClock.game_seconds_advanced.
Ageing scales with clock speed — if you speed up the clock, ageing speeds up
proportionally.
Use for:
- Potion expiry ("expires in 5 game-minutes")
- Buff/debuff durations
- Cooldown timers
- Real-time effects that should pause when the clock pauses
# One AgeService per clock; the demo assigns it where a clock lacks one.
var age_service:= AgeService.new()
age_service.clock= clock
# Scene usage: an AgeComponent node with `clock` and `age_preset` bound.
# In code:
var component:= AgeComponent.new(my_preset, clock)
add_child(component)
# Later: how much time has elapsed for this component?
var elapsed:= component.age_state.get_total_elapsed()
var current:= component.age_state.currentAddAfterGameSeconds is a convenience node: it adds a child node after
N game-seconds have elapsed on the bound clock.
Model 2: Calendar-date ageing (discrete)
"This crop takes 3 game-days to mature."
Listen to clock.signal_bus.date_changed and advance growth stages
discretely. Each calendar day that passes advances growth once, regardless
of clock speed. One advance can cross several days at once, such as a
sleep-to-morning jump, a large timescale step, or a loaded offline span.
It still emits a single date_changed, and event.change carries the whole
signed day span. Read that span instead of assuming one stage per signal.
This is the farming-game model (Stardew Valley, Rune Factory, Harvest Moon).
Use for:
- Crop growth ("3 days to harvest")
- Building construction ("7 days to complete")
- Character aging ("1 year per game-year")
- Seasonal changes
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:
# `change` is the signed day span of this advance, not always 1.
growth_stage= mini(MAX_STAGE, growth_stage+ maxi(event.change,0))
_update_sprite(growth_stage)DateChangeEvent also carries old_date, new_date, and the calendar that
produced the change. To get one callback per crossed day, drive the clock with
advance_through_microseconds(). It emits a date_changed per boundary with
change of 1 or -1.
DateChangeEvent, DateChangeSpawner, DateChangeRandomizer, and
DateChangeRandomToggler are ready-made nodes for common date-triggered
behaviors.
Choosing between the models
| Question | Model |
|---|---|
| Should it pause when the clock pauses? | Both do |
| Should it scale smoothly with clock speed? | Model 1 (clock-time) |
| Should it advance in discrete day/month/year steps? | Model 2 (calendar-date) |
| Is it a duration ("lasts N seconds")? | Model 1 |
| Is it a progression ("takes N days")? | Model 2 |
| Farming, construction, seasons? | Model 2 |
| Cooldowns, expiry, buffs? | Model 1 |
Combining both models
Both models read from the same GameClock.
Pausing the clock pauses both.
Saving and loading preserve both: Model 1 through AgeComponent
serialization, Model 2 through the calendar date in TimeSnapshot.
AgeService internals
AgeService is a per-clock Resource (not a singleton). Each clock can
have its own AgeService with independent components. The service
subscribes to game_seconds_advanced and advances the registry states that
AgeComponent nodes read.
AgeStateRegistry tracks visual states per clock. AgeState defines
what a component looks like at each threshold (sprite frame, material,
scale). AgePreset bundles named presets for quick authoring.
AgeSceneReplacement swaps a node's scene at an age threshold — useful
for "sapling → tree" or "ruins → rebuilt house" transitions.
Using this in a game?
Calendar Time v2.1.2 — full source, playable demos, commercial license
The guide above is the same setup the shipped demos use. Try the build in your browser before you buy it.