Integrating WorldTime in a C# Godot Game

How to wire the WorldTime plugin into a game project using the addon + templates pattern.

Installation

# Linux / macOS
ln -s /path/to/plugins/WorldTime/demos/addons/world_time \
      /path/to/game/Godot/addons/world_time

# Windows (Admin CMD)
mklink /j game\Godot\addons\world_time plugins\WorldTime\demos\addons\world_time

This makes [GlobalClass] types (WorldTimeCalendar, TimeSignalBus, etc.) available in the Godot inspector.

2. Copy templates (optional)

cp -r plugins/WorldTime/demos/templates/world_time/ \
      game/Godot/templates/world_time/

Templates are copy-and-customize — modify them for your game's UI layout.

3. Compile override

If your game .csproj has <Compile Remove="addons\**\*.cs" />, add:

<Compile Include="addons\world_time\**\*.cs" />

Architecture

Game Bootstrap
  └── WorldTimeBootstrap (RefCounted, owns everything)
        ├── TimeProvider      — read-only time queries
        ├── TimeSignalBus     — Godot signal bridge
        └── WorldTimeEcsRuntime — ECS-backed time simulation
              └── WorldTimeAdvanceSystem — advances clock each tick

Key types:

Type Role
WorldTimeBootstrap Entry point — creates runtime, wires provider + signal bus
TimeProvider Read-only: CurrentDate, TimeOfDay, IsPaused, TotalGameSeconds
TimeSignalBus Bridges Core C# events → Godot [Signal]s
WorldTimeEcsRuntime ECS world with time state + advance system
WorldTimeCalendar [GlobalClass] Resource for inspector calendar config

Quick Start (Code-Only)

The simplest integration — no scenes, no inspector wiring:

using Godot;
using MoonBark.CalendarTime.Godot;
using MoonBark.CalendarTime.Types;

public partial class GameRoot : Node
{
    private WorldTimeBootstrap? _worldTime;

    public override void _Ready()
    {
        var calendar = GameCalendar.Default12MonthCalendar(startYear: 1);
        _worldTime = WorldTimeBootstrap.CreateStandalone(calendar);

        // Listen for day changes
        _worldTime.Provider.OnDateChanged += (newDate, oldDate) =>
            GD.Print($"Day {oldDate.Day} → Day {newDate.Day}");

        // Listen for time-of-day changes
        _worldTime.SignalBus.OnTimeOfDayChanged += (newTime, prevTime) =>
            GD.Print($"Time: {prevTime.Hours}:00 → {newTime.Hours}:00");
    }

    public override void _Process(double delta)
    {
        // Advance time each frame (unscaled real seconds)
        _worldTime?.Advance((float)delta);
    }

    public override void _ExitTree()
    {
        (_worldTime as IDisposable)?.Dispose();
    }
}

Quick Start (With Templates)

Use the provided template scenes for UI:

using Godot;
using MoonBark.CalendarTime.Godot;
using MoonBark.CalendarTime.Godot.UI;
using MoonBark.CalendarTime.Types;

public partial class GameRoot : Node
{
    private WorldTimeBootstrap? _worldTime;

    [Export] public CalendarDisplay? CalendarDisplay { get; set; }
    [Export] public Label? TimeLabel { get; set; }

    public override void _Ready()
    {
        var calendar = GameCalendar.Default12MonthCalendar(startYear: 1);
        _worldTime = WorldTimeBootstrap.CreateStandalone(calendar);

        // Wire template nodes
        if (CalendarDisplay != null)
            CalendarDisplay.Provider = _worldTime.Provider;

        _worldTime.Provider.OnTimeAdvanced += OnTimeAdvanced;
    }

    private void OnTimeAdvanced(float deltaMinutes, float currentTime, uint dayNumber, uint tickCount)
    {
        if (TimeLabel == null) return;
        var time = _worldTime!.Provider.TimeOfDay;
        var h = (int)time;
        var m = (int)((time - h) * 60);
        TimeLabel.Text = $"{h:D2}:{m:D2}";
    }

    public override void _Process(double delta)
    {
        _worldTime?.Advance((float)delta);
    }

    public override void _ExitTree()
    {
        (_worldTime as IDisposable)?.Dispose();
    }
}

Using Templates

Template .tscn files are in templates/world_time/ui/. Each wraps a C# script:

Template Script Purpose
date_time_display.tscn DateTimeDisplay.cs Shows formatted time + date
calendar_display.tscn CalendarDisplay.cs Full calendar with month tabs
time_control_panel.tscn TimeControlPanelNode.cs Pause / Play / Fast-Forward buttons
time_of_day_light.tscn TimeOfDayLightNode.cs Binary day/night light

Wiring templates in code

// After creating WorldTimeBootstrap:
CalendarDisplay.Provider = _worldTime.Provider;
DateTimeDisplay.Provider = _worldTime.Provider;

TimeControlPanel.PauseRequested += () => _worldTime.Pause();
TimeControlPanel.ResumeRequested += () => _worldTime.Resume();
TimeControlPanel.SpeedChanged += multiplier => _worldTime.SetTimeScale(multiplier);

Wiring templates in the inspector

Template nodes have [Export] properties. Assign TimeProvider in the inspector:

  1. Instance the template scene in your scene tree
  2. Select the node
  3. In Inspector, assign the Provider field

Note: TimeProvider is RefCounted (not Resource), so it cannot be assigned via inspector drag-and-drop. Use code wiring or the WorldTimeBootstrap pattern instead.

Calendar Configuration

var calendar = new GameCalendar
{
    DisplayName = "Farming Calendar",
    DaysPerWeek = 7,
    StartDayOfWeekIndex = 0
};

var year = new GameYear();
year.Months.Add(new GameMonth(28, "Early Spring"));
year.Months.Add(new GameMonth(28, "Late Spring"));
year.Months.Add(new GameMonth(28, "Early Summer"));
calendar.Years.Add(year);

calendar.TimeScale = new TimeScale(60, 60, 24, 720f); // 2 real min = 1 game day

var bootstrap = WorldTimeBootstrap.CreateStandalone(calendar);

Via inspector (using addon resources)

  1. Create a WorldTimeCalendar resource in the inspector
  2. Add WorldTimeYear entries to the Years array
  3. Add WorldTimeMonth entries to each year's Months array
  4. Convert to Core: var coreCalendar = calendarResource.ToCore();

Pre-built calendars

// Standard 12-month, 365-day calendar
var calendar = GameCalendar.Default12MonthCalendar(startYear: 1);

// Custom time scale: 1 real second = 1 game hour
calendar.TimeScale = TimeScale.OneHourPerSecond();

// Custom time scale: 2 real minutes = 1 game day
calendar.TimeScale = TimeScale.TwoMinutesPerDay();

Time Events

C# events (on TimeProvider)

_provider.OnTimeOfDayChanged += (newTime, prevTime) => { };
_provider.OnDateChanged += (newDate, oldDate) => { };
_provider.OnTimeAdvanced += (deltaMinutes, currentTime, dayNumber, tickCount) => { };

Godot signals (on TimeSignalBus)

_signalBus.OnTimeAdvanced += evt => { };
_signalBus.OnDateChanged += evt => { };
_signalBus.OnTimeOfDayChanged += (newTime, prevTime) => { };

// Or connect via Godot signal system (GDScript compatible)
_signalBus.Connect(nameof(TimeSignalBus.TimeAdvancedSignal), Callable.From(...));

Pause and Speed Control

_worldTime.Pause();
_worldTime.Resume();

// Set speed: 1 real second = 60 game seconds (1 game minute)
_worldTime.SetTimeScale(60f);

// Set speed: 1 real second = 3600 game seconds (1 game hour)
_worldTime.SetTimeScale(3600f);

Save/Load

// Save
string json = _worldTime.SaveToJson();
FileAccess.WriteAllText("user://save.json", json);

// Load
string json = FileAccess.ReadAllText("user://save.json");
_worldTime.LoadFromJson(json);

The save includes AccumulatedRealMicroseconds (exact int64) + calendar state. ElapsedGameSeconds (float) is derived on load.

Fixed-Step Simulation (Production)

For deterministic simulation, use a fixed-step timer instead of _Process(delta):

public const float FixedStepSeconds = 1f / 60f;

private float _accumulator;

public override void _Process(double delta)
{
    _accumulator += (float)delta;

    while (_accumulator >= FixedStepSeconds)
    {
        _worldTime!.Advance(FixedStepSeconds);
        _accumulator -= FixedStepSeconds;
    }
}

This avoids Engine.TimeScale compounding and ensures deterministic tick counts.

Engine.TimeScale Warning

Do NOT use Engine.TimeScale to control WorldTime speed. Godot's _Process(delta) already multiplies delta by Engine.TimeScale, which compounds with GameSecondsPerRealSecond:

gameDelta = (realWall × Engine.TimeScale) × GameSecondsPerRealSecond

Use GameSecondsPerRealSecond or tick-count scaling instead. Keep Engine.TimeScale = 1.0.

Reference