Chris' Tutorials
Docs/Calendar Time

Release Migration

1.x to 2.0 Migration

Move World Time and Calendar Time 1.x projects to the Calendar Time 2.0 runtime shape.

StatusCurrent
Versionv2.0
UpdatedFrom plugin repo source
Source note:This page rendered plugin-owned docs or generated metadata inside unified Astro docs shell.

Calendar Time 2.0 is a major clean-break release. It replaces the older World Time / 1.x runtime shape with a final 2.0 architecture centered on:

  • GameClock: the runtime Resource that owns canonical integer microseconds, calendar state, the per-clock TimeSignalBus, per-clock save/load, and optional ageing.
  • TimeHost: the scene Node that advances one or more clocks from Godot process, physics process, or manual drive mode.
  • ClockGroupSerializer: the multi-clock save/load facade returned by time_host.get_group_serializer() and used by time_host.load_state(save_data).

Code and scenes need a one-time rename/wiring pass. Save files are designed to auto-migrate on load through the 2.0 save DTO path.

Quick Start

  1. Back up your project and any user:// save files.
  2. Replace the old plugin folder with the 2.0 addons/calendar_time/ folder.
  3. Open and re-save scenes/resources that used World Time / Calendar Time nodes or resources so Godot refreshes Inspector data.
  4. Update scripts using the rename tables below.
  5. Add or update a TimeHost and assign one or more GameClock resources to TimeHost.clocks.
  6. Route app-level group loads through time_host.load_state(save_data) so each clock publishes clock_state_loaded after state restore.
  7. Run a save/load smoke test and confirm clocks reload to the exact saved time.

Core Rename Table

Old name (World Time / 1.x) New name (2.0) Notes
World Time Calendar Time Product/plugin branding.
addons/world_time/ addons/calendar_time/ Plugin folder.
WorldTimePlugin CalendarTimePlugin Editor plugin class.
GameTimeSystem TimeHost Scene Node that drives assigned GameClock resources.
TimeState GameClock / TimeSnapshot Runtime state lives on GameClock; save DTO data uses TimeSnapshot.
WorldTimeSerializer ClockGroupSerializer Multi-clock facade via time_host.get_group_serializer().
WorldAgeSystem AgeService Optional per-clock ageing service, usually assigned on GameClock.
AgeingComponent AgeComponent Node attached to ageable game objects.
AgeingSettings AgeSettings Ageing configuration Resource.
AgeingSceneReplacement AgeSceneReplacement Age-threshold scene replacement node.
AddWhenAgeing AddWhenAged Age-threshold resource addition node.
WorldTimeClock removed Bind consumers to GameClock and clock.signal_bus directly.

Common Property Renames

Old property New property Notes
time_state clock / clocks UI/components bind one GameClock; TimeHost drives clocks[].
calendar on old system nodes calendar on GameClock Calendar schema belongs to the clock Resource.
ageing_component age_component Node-path exports.
ageing_settings age_settings Ageing resources and components.
world_age_system age_service Save/load and ageing setup.
DateChangeEvent.new DateChangeEvent.new_date Avoids collision with GDScript constructor naming.
DateChangeEvent.old DateChangeEvent.old_date Paired with new_date.
TimeOfDay.color TimeOfDay.tint_color Time-of-day resources.
TimeOfDayLightSettings.color TimeOfDayLightSettings.light_color Lighting resources.
EventDay.color EventDay.accent_color Event day resources.
TimeOfDayToggle.target TimeOfDayToggle.target_node Demo example script, no longer core plugin API.
TimeOfDayToggle.property TimeOfDayToggle.target_property Demo example script, no longer core plugin API.
CloseButton.target CloseButton.target_node Demo example script, no longer core plugin API.
AddAfterGameSeconds.add_target AddAfterGameSeconds.target_node Age/timer helper nodes.
AddAfterGameSeconds.amount AddAfterGameSeconds.seconds_amount Age/timer helper nodes.
AddWhenAged.target_resource AddWhenAged.target_age_state_resource Age trigger resources.
AddWhenAged.amount AddWhenAged.age_amount Age trigger resources.

Scene Wiring Changes

1.x style

Older projects often had a time system node that owned time state directly. Consumers reached into that node or a state object for current time and signals.

2.0 style

Create or duplicate a GameClock Resource, assign its calendar, then wire it explicitly:

@export var clock: GameClock
@export var time_host: TimeHost

func _ready() -> void:
    time_host.clocks = [clock]
    clock.signal_bus.game_seconds_advanced.connect(_on_game_seconds_advanced)
    clock.signal_bus.clock_state_loaded.connect(_on_clock_state_loaded)

For designer-authored scenes, assign TimeHost.clocks in the Inspector. auto_attach_unbound_clocks defaults to false; set it to true only for legacy scenes that intentionally rely on auto-register behavior.

Signals

Signals are consumed through clock.signal_bus.

Need 2.0 signal
Time advanced by seconds clock.signal_bus.game_seconds_advanced(amount, total)
Date/time changed clock.signal_bus.date_time_changed(new, old)
Date changed clock.signal_bus.date_changed(event)
Day finished clock.signal_bus.day_finished(finished_date)
Time of day changed clock.signal_bus.time_of_day_changed(current, previous)
Day/night transition progress clock.signal_bus.day_night_transition_progress_changed(progress)
Event day started clock.signal_bus.event_day_started(event_day)
Clock speed changed clock.signal_bus.clock_speed_changed(speed_multiplier)
State loaded clock.signal_bus.clock_state_loaded()

Example:

func _ready() -> void:
    clock.signal_bus.game_seconds_advanced.connect(_on_game_seconds_advanced)
    clock.signal_bus.day_night_transition_progress_changed.connect(_on_day_night_progress)
    clock.signal_bus.clock_state_loaded.connect(_refresh_from_loaded_clock)

clock_state_loaded is emitted by time_host.load_state(save_data) after the group serializer restores state. Use it to refresh UI, day/night visuals, and any derived state that should update immediately after load.

Saving And Loading

Per-clock save

Use this when you only own one clock and do not need group composition:

var save_data: Dictionary = clock.to_dict()
clock.from_dict(save_data)

clock.from_dict(save_data) publishes clock_state_loaded after restoring the clock.

Multi-clock save

Use the host facade for normal app-level saves:

var save_data: Dictionary = time_host.get_group_serializer().to_dict()
time_host.load_state(save_data)

The current group shape is:

{
    "clocks": {
        "<clock_id>": {
            "clock_id": "<clock_id>",
            "time_state": { ... },
            "age_states": { ... },
            "age_service_state": { ... }
        }
    }
}

age_states and age_service_state are present only when the clock has an age_service.

Each GameClock has a stable clock_id. Resource-backed clocks derive it from resource_path. Runtime-only clocks get a session-unique runtime id, which is useful inside the current process but not stable across separate game launches. Set clock_id_override for runtime clocks that must load across separate game sessions.

Pre-2.0 save data that used the top-level 1.x WorldTimeSerializer shape is migrated when loaded through time_host.load_state(save_data). That true legacy shape used time_state, age_states, and world_age_system_state at the save root; Calendar Time loads it into the first bound clock positionally. Early grouped pre-release compatibility saves under clocks are still tolerated: entries with clock_id load by id, entries without clock_id fall back to positional matching, and entries with unknown ids are skipped instead of being loaded into an unrelated clock.

Float game_seconds and legacy game_ticks are migrated by the TimeSnapshot DTO path. The save key time_state remains for compatibility even though the current runtime object is GameClock and the DTO is TimeSnapshot.

Time Driving

TimeHost.drive_mode controls how clocks advance:

Mode Use
PROCESS Normal frame-driven game time.
PHYSICS_PROCESS Physics-step-driven game time.
MANUAL Tests, replay, fixed-step, ECS, or deterministic-friendly integrations.

For deterministic-friendly simulation, use integer microseconds:

time_host.drive_mode = TimeHost.DriveMode.MANUAL
time_host.drive_microseconds(16_666)

auto_increment_time is preserved as a backward-compatible alias for drive_mode:

  • auto_increment_time = true is equivalent to drive_mode = PROCESS.
  • auto_increment_time = false is equivalent to drive_mode = MANUAL.
  • drive_mode = PHYSICS_PROCESS reads back as auto_increment_time == true because it is still an auto-driven mode.

GameClock.current_microseconds is the canonical value. game_seconds() and date_time() are derived views.

Ageing Migration

Ageing is optional in 2.0 and is per-clock.

  • Replace WorldAgeSystem with AgeService.
  • Replace AgeingComponent with AgeComponent.
  • Assign the same GameClock used by the relevant gameplay system.
  • Bind each AgeComponent.clock to that GameClock; components resolve their registry from clock.age_registry.
  • Use clock.age_registry instead of the old process-wide singleton registry.

Most projects can migrate object ageing by assigning age_service on the relevant GameClock, updating renamed node/resource classes, binding each AgeComponent.clock, and re-saving affected scenes. Template clock resources show the supported .tres shape: a GameClock can include both calendar = ExtResource(...) and age_service = ExtResource(...).

File And Template Paths

Older path/name 2.0 path/name
addons/world_time/ addons/calendar_time/
template world_time_systems.tscn templates/calendar_time/calendar_time_services.tscn
ageing/ageing_component.gd ageing/age_component.gd
ageing/ageing_settings.gd ageing/age_settings.gd
ageing/add_when_ageing.gd ageing/add_when_aged.gd
ageing/ageing_scene_replacement.gd ageing/age_scene_replacement.gd

If you use uid:// references dragged through the Inspector, Godot may keep many links intact. If you use load() / preload() paths, update them manually.

Minimal Migration Checklist

  • Back up your project and saves.
  • Replace the addon folder with addons/calendar_time/.
  • Replace old time system nodes with TimeHost.
  • Create/assign GameClock resources and bind them to TimeHost.clocks.
  • Move calendar assignment onto each GameClock.
  • Move signal connections to clock.signal_bus.
  • Update app-level load paths to time_host.load_state(save_data).
  • Update ageing class/property names if your project uses ageing.
  • Bind each AgeComponent.clock to the same GameClock whose age registry it should use.
  • Remove any old AgeStateRegistry.get_singleton() usage and replace it with clock.age_registry.
  • Re-save scenes and resources in the Godot editor.
  • Run your save/load smoke test and confirm clocks reload to the exact saved time.

Demo Example Scripts (No Migration Required Unless You Used Them Directly)

Three small helper scripts used to live in the addon. They are now example scripts in the demo because they are conveniences, not core runtime. Consumers who need the behavior can copy the script from the demo into their own project.

Demo script Purpose Replace with
res://demo/scripts/calendar_time_examples/close_button.gd Button that hides a target Control on press. Inline pressed signal handler: button.pressed.connect(func(): target.hide()).
res://demo/scripts/calendar_time_examples/time_of_day_animated_sprite_2d.gd AnimatedSprite2D that switches its animation on time_of_day_changed. Plain AnimatedSprite2D + subscribe to clock.signal_bus.time_of_day_changed and call play("...").
res://demo/scripts/calendar_time_examples/time_of_day_toggle.gd Node that toggles a target property on time_of_day_changed. Plain Node + subscribe to clock.signal_bus.time_of_day_changed and set(...) the target property yourself.

If you were using these classes in your own scenes, the migration is copy-paste: drag the example script into your project's scripts folder and update the script reference in the scene. The class identifiers (class_name) were removed so two projects can include the example scripts without colliding on global class names.

These example scripts are part of the Calendar Time demo (demo.tar.gz) and the test scene (test/calendar_time/scenes/test_calendar_display.tscn) references them through the demo path. The plugin payload (plugin.tar.gz) no longer contains them.

Troubleshooting

Time does not advance

Check that TimeHost.clocks contains at least one GameClock, drive_mode is not MANUAL unless you call drive_microseconds() / drive_seconds(), and time_scale.delta_multiplier is not zero.

UI does not refresh after load

Use time_host.load_state(save_data) instead of calling the group serializer's from_dict() directly. The host method publishes clock_state_loaded after restore.

A runtime clock loads as a different clock next session

Set clock.clock_id_override to a stable StringName before saving.

Old Inspector fields show missing property warnings

Open the scene/resource, reassign renamed exports if needed, and save. This is expected for clean-break renamed fields.

Source

MIGRATION.md

Plugin docs root:gdscript/plugins/calendar_time_dev/docs