TimeOfDay

TimeOfDay is an environment object that drives the level day/night cycle.

It calculates sun elevation and azimuth from a normalized time value and broadcasts updates to subscribed environment systems such as sky, sun, scatter sky, exposure, and lighting components.

TimeOfDay is commonly used with advanced lighting and a compatible sky object, such as ScatterSky.

TimeOfDay only has a visible lighting effect when other environment objects listen to it or use its sun/time data. By itself, it is a global time controller, not a sky renderer.

Basic example

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0.15,
  "time": 0.15,
  "play": false,
  "azimuthOverride": 0,
  "dayScale": 1,
  "nightScale": 2,
  "enableStartTimeFallBack": false,
  "position": [0, 0, 0],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [1, 1, 1]
}

Important fields

Field Type Description
class string Must be "TimeOfDay".
name string Scene object name, commonly "tod".
axisTilt number Sun path tilt in degrees.
dayLength number Length of a full virtual day in real-world seconds.
startTime number Time used when the level starts.
enableStartTimeFallBack bool Compatibility mode for old/non-normalized time setups.
time number Current normalized time of day. Hidden in inspectors.
play bool Whether time advances automatically.
azimuthOverride number Fixed azimuth angle. Non-zero disables normal axisTilt azimuth behavior.
dayScale number Time multiplier while the sun is up.
nightScale number Time multiplier while the sun is down.

Time values

TimeOfDay uses normalized time:

0.0  -> start of cycle
0.25 -> quarter through cycle
0.5  -> halfway through cycle
0.75 -> three quarters through cycle
1.0  -> wraps to 0.0

When assigned through setTimeOfDay, time is wrapped into the 0..1 range:

mTimeOfDay = time - floor(time);

Examples:

Input Stored time
0.15 0.15
1.15 0.15
-0.1 0.9

A common daylight start value is around:

"startTime": 0.15

The exact visual result depends on sky/sun setup and azimuthOverride.


startTime

"startTime": 0.15

startTime is copied into the current time when the object is added.

If compatibility fallback is disabled:

time = startTime

If enableStartTimeFallBack is enabled and azimuthOverride is non-zero, the engine applies an offset:

time = (startTime + 0.75) % 1.0

This exists for compatibility with older levels.


time

"time": 0.15

time is the current normalized time of day.

It is a protected field and hidden in inspectors. Setting it calls setTimeOfDay(), which wraps it and immediately updates sun position.

For level files, startTime is usually more important than time, because onAdd() resets time from startTime.


Playback

play

"play": true

When play is true, the time advances every simulation tick.

The update uses:

deltaTime = tickSeconds * simulationTimeScale * dayOrNightScale
time += deltaTime / dayLength

If play is false, the time remains fixed unless changed by script/editor or animation.


dayLength

"dayLength": 1800

dayLength is the length of one full virtual day in real-world seconds.

Examples:

dayLength Meaning
600 10-minute day
1800 30-minute day
3600 1-hour day
86400 Real-time 24-hour day

Use longer values for slow natural progression and shorter values for visible day/night cycles.


dayScale and nightScale

"dayScale": 1,
"nightScale": 2

dayScale and nightScale multiply elapsed time depending on whether the sun is considered up or down.

In code, day/night selection is based on the normalized sun elevation:

day scale is used when elevation is > 350 degrees or from 0 to < 190 degrees
night scale is used otherwise

This means a level can make nights pass faster than days:

"dayScale": 1,
"nightScale": 4

Sun position

TimeOfDay calculates:

  • Sun elevation
  • Sun azimuth
  • Normalized elevation for color/day-night calculations

These values are broadcast through TimeOfDay::smTimeOfDayUpdateSignal.


axisTilt

"axisTilt": 23.44

axisTilt is the angle in degrees between the global equator and tropic.

The default Earth-like value is:

23.44

In the simplified calculation used by this implementation, axis tilt affects the sun path when azimuthOverride is not set.


azimuthOverride

"azimuthOverride": 0

When azimuthOverride is non-zero, the sun keeps a constant azimuth angle through the day.

This disables normal axisTilt-based azimuth behavior.

In this mode:

elevation = ((time + 0.25) % 1.0) * 360 degrees
azimuth = azimuthOverride

The 0.25 offset compensates for the azimuth override time shift used by older content.

Use this when you want a fixed sun compass direction while still allowing elevation to change.


Color targets

TimeOfDay initializes an internal sun color curve.

The curve maps normalized elevation to colors for:

  • Day
  • Dawn
  • Dusk
  • Night

The built-in color targets range from:

0 radians -> high noon
PI radians -> midnight

The default curve gradually transitions from white daylight to warm dawn/dusk colors and darker bluish night colors.

These targets are internal in this implementation and not exposed as persistent fields in the shown code.


Lifecycle behavior

When a TimeOfDay object is added:

  1. It initializes time from startTime.
  2. It optionally applies compatibility fallback offset.
  3. It calculates sun position.
  4. It sets global bounds.
  5. It adds itself to the scene.
  6. It executes its onAdd script callback.
  7. It enables processing.
  8. It stores the last signaled time.

When removed, it is removed from the scene.


Animation

TimeOfDay supports animated transition toward a target angle.

The method:

animate(f32 time, f32 speed)

takes:

Parameter Meaning
time Target time angle in degrees, clamped to 0..360.
speed Animation speed in degrees per second.

Internally, current normalized time is converted to degrees:

currentDegrees = timeOfDay * 360

If the target is behind the current time, it wraps forward by 360 degrees so animation always proceeds forward through the day.

Example concept:

current = 90 degrees
target = 45 degrees
actual target = 405 degrees

The animation stops when it reaches the target.


Example setups

Static morning lighting

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0.15,
  "time": 0.15,
  "play": false,
  "azimuthOverride": 0,
  "dayScale": 1,
  "nightScale": 1,
  "enableStartTimeFallBack": false
}

Use this for a fixed time of day.


Active day/night cycle

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 2400,
  "startTime": 0.2,
  "time": 0.2,
  "play": true,
  "azimuthOverride": 0,
  "dayScale": 1,
  "nightScale": 3,
  "enableStartTimeFallBack": false
}

This creates a 40-minute full cycle with nights moving 3x faster than days.


Fixed sun direction with changing height

{
  "class": "TimeOfDay",
  "name": "tod",
  "axisTilt": 23.44,
  "dayLength": 1800,
  "startTime": 0.12,
  "time": 0.12,
  "play": false,
  "azimuthOverride": 135,
  "dayScale": 1,
  "nightScale": 1,
  "enableStartTimeFallBack": false
}

Use azimuthOverride when you want the sun to rise/lower along a fixed direction for art direction.


Fast preview cycle

{
  "class": "TimeOfDay",
  "name": "tod_preview",
  "axisTilt": 23.44,
  "dayLength": 120,
  "startTime": 0,
  "time": 0,
  "play": true,
  "azimuthOverride": 0,
  "dayScale": 1,
  "nightScale": 1,
  "enableStartTimeFallBack": false
}

Useful for quickly checking lighting transitions.


Interaction with other objects

TimeOfDay does not render the sky or sun by itself.

It is intended to work with environment objects that listen for time updates, such as:

  • ScatterSky
  • Sun/lighting objects
  • Advanced lighting environment systems
  • Exposure/tonemapping systems
  • Other objects connected to TimeOfDay::smTimeOfDayUpdateSignal
If the sky or lighting does not react to time changes, check that a compatible sky/sun object exists and is connected to the TimeOfDay update signal.

Best practices

  • Use one primary TimeOfDay object per level.
  • Use startTime for the level’s initial time.
  • Keep time and startTime consistent in saved files for clarity.
  • Disable play for fixed-lighting maps.
  • Enable play only when the level should have a moving day/night cycle.
  • Use realistic dayLength values unless you intentionally want fast cycling.
  • Use higher nightScale to make night shorter in gameplay.
  • Leave enableStartTimeFallBack disabled for new content.
  • Use azimuthOverride only when you need fixed sun direction.
  • Confirm a compatible sky/lighting object exists, such as ScatterSky.

Common issues

TimeOfDay has no visible effect

Possible causes:

  • No compatible sky object exists.
  • The level is not using compatible advanced lighting setup.
  • ScatterSky or sun object is not connected to the TimeOfDay update signal.
  • play is false and the time is not being changed.
  • The level uses static lighting or another system overrides sun direction.

Level starts at the wrong time

Check:

"startTime": 0.15

Remember that onAdd() initializes current time from startTime.

If enableStartTimeFallBack is enabled and azimuthOverride is non-zero, a compatibility offset is applied.

For new levels, use:

"enableStartTimeFallBack": false

Time changes too quickly

Increase:

"dayLength": 3600

Also check:

"dayScale": 1,
"nightScale": 1

and simulation time scale.


Nights are too long

Increase nightScale:

"nightScale": 3

This makes time pass faster while the sun is down.


Sun moves in an unexpected direction

Check:

"azimuthOverride": 0

If azimuthOverride is non-zero, it forces a fixed azimuth and disables the normal azimuth calculation.

Also check axisTilt.


Summary

TimeOfDay is the level day/night controller.

It stores normalized time, advances it when play is enabled, calculates sun elevation and azimuth, and broadcasts time updates to sky/lighting systems. Use it with a compatible sky object such as ScatterSky for visible day/night lighting changes.

Last modified: June 2, 2026

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.