How to make a day-night cycle in Godot 4
Drive a sun and moon lighting rig from a Calendar Time GameClock: author times of day, set the timescale, and keep save and load safe.
This recipe builds a full day-night cycle: a clock advances game time, the calendar reports which time of day is active, and a lighting rig turns that into sun color, energy, and ambient tint. Pausing the clock pauses the sky. Saving the clock saves the sky with it.
You need the Calendar Time addon installed and a working clock first. If you have not set one up yet, follow Getting Started and come back.
1. Author your times of day
The calendar owns the cycle schema. Open your GameCalendar resource and
define one TimeOfDay entry per stage you want players to see: dawn, day,
dusk, and night at minimum. Each entry carries the hours it covers plus a
tint_color the lighting reads later.
Keep the list short. Four to six stages is enough for a readable cycle, and every stage you add is one more lighting preset to tune. Name stages for what the player sees ("dawn", "night"), not for internal indices.
2. Drive the clock with a TimeHost
Add a TimeHost to your main scene and assign your GameClock to its
clocks list. The host is the only thing that advances the clock, so there
is exactly one place where time comes from:
# Pause this clock while menus stay responsive.
clock.speed_multiplier = 0.0
# Resume the same clock.
clock.speed_multiplier = 1.0
# Run every clock on this host at double speed.
time_host.time_scale.delta_multiplier = 2.0Use clock.speed_multiplier to pause or resume one timeline.
Use time_host.time_scale.delta_multiplier to change the pace of every clock
the host drives. Never use Engine.time_scale for day and night: it changes
physics and animation pacing for the whole engine session, and it gives you a
second clock to keep in sync.
Pick a timescale that fits your game. A common starting point is one game hour every 30 real seconds: days last 12 real minutes, and lighting transitions stay slow enough to read as atmosphere rather than flicker.
3. Add the sun light
Sun and moon lighting ships in the demo download
(demo/lighting_rigs/), not the plugin ZIP. Copy the rig files into your
project. For 2D, build this scene tree:
World (Node2D)
├── TimeOfDayAmbience (CanvasModulate)
├── TimeOfDayDirectionalLight2D (DirectionalLight2D)
└── LightingRig2D (Node, script: time_of_day_lighting_rig_2d.gd)Assign the same GameClock the host drives, then point the rig at the
CanvasModulate and the directional light and give it a
TimeOfDayLightingSettingsGroup2D. For 3D, the equivalent tree binds a
DirectionalLight3D and a WorldEnvironment through the 3D rig script.
Each time of day gets one settings entry, and the rig blends between the
previous and current entries across the transition.
One layering rule for 2D: CanvasModulate multiplies every pixel while
DirectionalLight2D adds light on top. Let the ambient color carry the tint
and keep the light color near white, or the two layers compound into an
oversaturated frame. Night energy around 0.08 and day energy around 0.45
is a sane starting pair.
Full field maps for both rigs live in Connect Custom Lighting to the Clock, and the shipped presets are catalogued in Time-of-day lighting rigs.
4. React to transitions without owning them
Your game code should read the cycle, never compute it. The TimeHost
creates one DayNightCycleService per clock, and the clock signal bus
announces every change:
time_of_day_changed(tod, last_tod)fires with bothTimeOfDayresources, so handlers read authored data instead of deriving the stage.day_night_transition_progress_changed(progress)hands you aGameTimeProgresswhoseupdated(ratio)signal runs from 0 to 1 across the blend.date_time_changed(new, old)fires every tick for per-frame bindings like a 3D sun angle recomputed fromclock.date_time().
Sync from the current clock instant in _ready() before connecting, because
the change signals only fire on change. Disconnect everything in
_exit_tree(). The script example in
Connect Custom Lighting to the Clock shows this
lifecycle in full and is safe to copy.
5. Keep save and load safe
Save the clock through the host authority, not by storing hours and minutes in your own variables:
var save_data: Dictionary = time_host.get_group_serializer().to_dict()
time_host.load_state(save_data)Restore order matters. Restore the Calendar Time clock first through
TimeHost.load_state(), then restore game-domain state, then re-evaluate
anything derived (shop hours, NPC schedules) from clock.date_time() or the
next clock_state_loaded callback. Anything that subscribes to
clock_state_loaded picks up the restored instant at once instead of waiting
for another tick.
Never persist a second copy of the current time, an elapsed-seconds counter, or a derived flag like "is night". Those are views of the clock. They go stale the moment the clock changes without them.
Common mistakes
- Driving the sky from
_process(delta)accumulation instead of the clock. Two time sources drift apart; the clock alone decides. - Using
Engine.time_scaleas the day-night pause button. Pause the clock. - Writing per-stage
if hour > 18chains in game code. Author stages on the calendar and readTimeOfDayresources from the signals. - Tinting hard in both
CanvasModulateand the directional light. Pick one layer to carry color. - Running an automatic
TimeHostand a manual clock driver at the same time. Choose one driver per clock.
Try the demo
The Calendar Time demo runs this exact recipe: a clock driving day and night lighting with adjustable timescale, pausable and save-safe. Try it in your browser on the Calendar Time itch.io page before wiring your own scene.