Skip to content

DayNightCycle

Install

Please install the @canvasengine/presets package first.

bash
npm install @canvasengine/presets

Then, you can use the presets in your project.

Overview

DayNightCycle lights a scene according to an in-game hour. It is not a darkness overlay:

  • the whole scene is color graded by time of day: blue, washed-out moonlight at night, pink dawn, neutral day, golden hour, purple dusk;
  • lights reveal the real colors under them with their own tint (a warm lamp paints the cobblestones orange), instead of just punching holes in a dark veil;
  • each light has a halo glowing in the air;
  • street lamps fade in one by one at dusk, flames can flicker, and windows follow their own schedule (on after sunset, off at bedtime);
  • a vignette deepens the night.

It comes with a reactive game clock (createGameClock / useGameClock) to drive it and build a time UI.

Like NightAmbient, the filter attaches to the Viewport when present, otherwise to the parent container. Light positions are in world coordinates and follow the camera and zoom.

Basic Usage

html
<Canvas>
  <Viewport worldWidth={1920} worldHeight={1440} drag={true} wheel={true}>
    <Sprite image="town.png" />
    <DayNightCycle time={clock.time} lights={lights} />
  </Viewport>

  <Text text={clock.label} x={16} y={16} color="#ffffff" />
</Canvas>

<script>
  import { DayNightCycle, useGameClock } from '@canvasengine/presets'

  // 10 game minutes per real second, starting at 17:30
  const clock = useGameClock({ time: 17.5, speed: 10 })

  const lights = [
    // Street lamp: switches on automatically when it gets dark
    { x: 875, y: 61, radius: 230, color: '#ffc070', halo: 0.95, haloSize: 0.12, flicker: 0.1 },
    // Window: lit from 19:00 to 23:30
    { x: 169, y: 306, radius: 62, color: '#ffcf7a', halo: 0.45, haloSize: 0.32, schedule: [19, 23.5] },
    // Magic fountain with a cold light
    { x: 960, y: 700, radius: 150, color: '#7fd4ff', intensity: 0.55, threshold: 0.55 },
  ]
</script>

Game Clock

js
import { createGameClock, useGameClock } from '@canvasengine/presets'

// Inside a component: advanced automatically every frame
const clock = useGameClock({ time: 8, day: 1, speed: 1, paused: false })

// Anywhere else: call clock.advance(deltaMs) yourself
const manual = createGameClock({ time: 8 })
MemberTypeDescription
timeSignal<number>Hour of the day, 0 to 24 (18.5 = 18:30)
daySignal<number>Day counter, incremented at midnight
speedSignal<number>Game minutes per real second (1 = a day lasts 24 real minutes, 60 = one hour per second)
pausedSignal<boolean>Stops the clock
labelComputed<string>"HH:MM"
phaseComputed<'night' | 'dawn' | 'day' | 'dusk'>Current phase
progressComputed<number>0 at midnight to 1 at the next midnight, handy for a day bar
advance(ms)functionMoves time forward
setTime(hour), setSpeed(value)functionJump in time, change speed
pause(), resume(), togglePause()functionPause control

formatClock(hour) and phaseAt(hour) are also exported.

Lights

FieldTypeDefaultDescription
x, ynumber | Signal<number>-World position of the light source
radiusnumber160Reach of the light pool on the ground
colorstring | number'#ffb45a'Light color
intensitynumber1Brightness
halonumber0.6Strength of the glow in the air (0 = none)
haloSizenumber0.22Glow size relative to radius
flickernumber0Flame flicker, 0 to 1
thresholdnumberrandomDarkness level (0 to 1) at which the light switches on. Random thresholds make lamps light up one by one
schedule[on, off]-Explicit hours, can cross midnight ([19, 1]). Overrides threshold
enabledboolean | Signal<boolean>trueManual switch

Up to 64 lights are rendered.

Props

PropTypeDefaultDescription
timenumber | Signal<number>-Hour of the day
lightsDayNightLight[] | Signal<DayNightLight[]>[]Light sources
cycleDayLightingKey[]DEFAULT_DAY_CYCLEGrading keyframes
lightIntensitynumber | Signal<number>1Multiplies every light
vignettenumber | Signal<number>1Multiplies the vignette

Sun Shadows

sunShadowAt(hour) returns an ambientLight for SpriteShadows: shadows stretch west in the morning, stay short at noon and stretch east in the evening. At night a faint moonlight keeps sprites grounded, and lit street lamps can cast their own shadows.

html
<Viewport>
  <Sprite image="town.png" />
  <SpriteShadows ambientLight={sun} lights={lampShadows} maxShadows={2} />
  <Sprite image="hero.png" anchor={[0.5, 1]} shadowCaster={{ height: 90 }} />
  <DayNightCycle time={clock.time} lights={lights} />
</Viewport>

<script>
  const sun = computed(() => sunShadowAt(clock.time()))
  const darkness = computed(() => sampleDayLighting(clock.time()).lights)
  const lampShadows = computed(() => lamps.map((lamp, index) => ({
    x: lamp.x,
    y: lamp.y + 105, // lantern stands ~105px above its base
    z: 105,
    radius: 420,
    intensity: lightLevel(lamp, index, clock.time(), darkness(), 0),
  })))
</script>
OptionDefaultDescription
sunrise, sunset6, 18.5Hours of sunrise and sunset
maxElevation62Sun elevation at noon, in degrees (lower = longer noon shadows)
intensity1Shadow strength in daylight
moonlight0.22Night shadow strength (0 = none)
noonShadow'south''south' keeps shadows in front of characters (readable top-down); 'north' is the physical northern-hemisphere sun

Custom Cycle

The grading is defined by keyframes interpolated over 24 hours. Use sampleDayLighting(hour) to read the current values, for example to fade night-only elements:

js
import { DEFAULT_DAY_CYCLE, sampleDayLighting } from '@canvasengine/presets'

const spookyCycle = DEFAULT_DAY_CYCLE.map((key) =>
  key.lights === 1 ? { ...key, ambient: [0.18, 0.28, 0.22], saturation: 0.2 } : key
)

// 0 during the day, 1 at night
const nightFactor = computed(() => sampleDayLighting(clock.time()).lights)
html
<DayNightCycle time={clock.time} lights={lights} cycle={spookyCycle} />
<Weather effect="fireflies" alpha={nightFactor} />
Key fieldDescription
hourHour of the keyframe
ambient[r, g, b] light multiplier applied to the scene
saturation1 = unchanged, 0 = grayscale
vignetteEdge darkening, 0 to 1
lightsDarkness level used by artificial lights, 0 to 1