logo
DayNightCycle

vehicle · vehicle-combat

DayNightCycle

1
Unlock

Install with the CLI:

bjs download DayNightCycle

DayNightCycle

Game time that moves the sun, so an afternoon drive turns into a night drive.

What it does

DayNightCycle.core turns an hour into a lighting state: where the key light comes from, its colour and strength, the sky fill, the haze colour and how bright reflections are. It also gives one number for everything else, darkness, which is 0 at noon and 1 at midnight. Headlights and street lamps key off it.

The key light is the sun by day and the moon by night. Both use the same directional light, and the swap happens when both are at their dimmest, so the shadows never jump.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add DayNightCycle
    bjs download DayNightCycle

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Sun": {
    "components": {
      "DirectionalLight": {
        "direction": [-0.4, -0.8, -0.3],
        "intensity": 4.8,
        "shadowEnabled": true,
        "shadowExtent": 64
      }
    }
  },
  "Ambient": {
    "components": {
      "HemisphericLight": { "intensity": 0.12 }
    }
  },
  "Sky": {
    "components": {
      "DayNightCycle": {
        "hour": 16.2,
        "hoursPerMinute": 2.5,
        "sun": "Sun",
        "ambient": "Ambient",
        "follow": "Player"
      }
    }
  }
}

It creates no lights. It picks up the handles the scene's lights announce and drives them every frame. Call registerDayNightCycleExtension(adapter) at bootstrap. Without it the clock still lights the scene, but reflections stay at daytime strength and skies does nothing.

Darkness from the hour

TypeScript
export function sunHeightAt(hour: number): number {
  return Math.sin(((wrapHour(hour) - 6) / 24) * Math.PI * 2);
}

/** 0 day .. 1 night. */
export function darknessAt(hour: number): number {
  return 1 - smoothstep(-0.14, 0.2, sunHeightAt(hour));
}

The sun's height is a sine wave that crosses the horizon at 6 and 18. Darkness stays at 0 until the sun is low, then climbs fast. At sunset it is 0.63, and about half an hour later it reaches 1. The whole dusk takes about 76 game minutes, which is under half a real minute at the default hoursPerMinute. With the default lightsOnAt, dayNight.nightfall fires at about 17:50.

HDR skies

Give it skies and it lights the scene with real sky photos (HDRIs) as the day turns:

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add DayNightCycle
    bjs download DayNightCycle

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Sky": {
    "components": {
      "DayNightCycle": {
        "sunIntensity": 4.8,
        "ambientDay": 0.12,
        "environmentDay": 0.6,
        "environmentNight": 0.3,
        "skies": [
          {
            "url": "/vehiclecombat/skies/qwantani_afternoon.env",
            "sunHeight": 0.65,
            "sunU": 0.6003,
            "level": 1
          },
          {
            "url": "/vehiclecombat/skies/qwantani_sunset.env",
            "sunHeight": 0.1,
            "sunU": 0.5999,
            "level": 1
          },
          {
            "url": "/vehiclecombat/skies/qwantani_dusk.env",
            "sunHeight": -0.06,
            "sunU": 0.6062,
            "level": 1.6
          },
          {
            "url": "/vehiclecombat/skies/qwantani_night.env",
            "sunHeight": -0.5,
            "sunU": 0.783,
            "level": 1
          }
        ]
      }
    }
  }
}
  • url is a prefiltered .env sky. Make one from an .hdr with Babylon's EnvironmentTextureTools.CreateEnvTextureAsync.
  • sunHeight is the sine of the sun's angle above the horizon when the photo was taken. The clock shows the sky shot nearest the current sun height.
  • sunU is where the sun sits across the photo (0 at the left edge, 1 at the right). The sky is turned every frame so its sun lines up with the light casting the shadows. The glint on a car roof then agrees with its shadow.
  • level evens out skies shot at different exposures.

All skies start loading at once, and a sky still loading is never put up, so a swap never flashes. With glossy, physically based materials the sky does most of the fill lighting, so keep the HemisphericLight low. The skies in the Endless Rampage scene are Poly Haven's CC0 "Qwantani" set by Greg Zaal and Jarod Guest.

If the scene also has an EnvironmentTexture, the DayNightCycle sky takes over as soon as its photo loads. EnvironmentTexture then only covers the first moments, and engines without the DayNight plugin.

Shadow box

A directional light's shadow covers a box centred on the light. Leave the light at the origin and a car three kilometres down the road has no shadow. So every frame the sun is moved to sit shadowDistance back up its own ray from the follow entity, a little ahead of the car.

Settings

FieldDefaultWhat it does
hour15Starting hour, 0 to 24.
hoursPerMinute3Game hours per real minute. 0 stops the clock.
sun"Sun"Entity carrying the DirectionalLight this clock drives.
ambient"Ambient"Entity carrying the HemisphericLight this clock drives.
follow"Player"Entity the sun's shadow box is kept centred on.
shadowDistance90How far up the light's ray the sun sits from follow.
lightsOnAt0.42Darkness at which dayNight.nightfall fires (headlights on).
fogtrueDrive the scene fog too.
fogDensityDay0.0038Fog density at noon.
fogDensityNight0.0075Fog density at midnight.
sunIntensity1.35Sun strength when it is high.
moonIntensity0.2Moonlight strength at midnight.
ambientDay0.62Sky fill at noon.
ambientNight0.1Sky fill at midnight.
environmentDay0.9Reflection strength at noon.
environmentNight0.06Reflection strength at midnight.
sunAzimuth0.6Compass bearing the sun rises from, radians (0 = +X).
skies[]HDR sky photos to light the scene with. Empty keeps the scene's own sky and only dims it.

Events

EventPayloadDirection
dayNight.changed{ entityId, hour, darkness, phase }emits when darkness moves by 0.01, and at least every 0.25 s. phase is day, dusk, night or dawn
dayNight.nightfall{ entityId, hour }emits when darkness rises past lightsOnAt
dayNight.daybreak{ entityId, hour }emits when darkness falls back below lightsOnAt
dayNight.time.set{ hour }listens, jumps the clock
dayNight.speed.set{ hoursPerMinute }listens, changes how fast the day goes
directionallight.created{ entityId, handle }listens, picks up the sun light
hemisphericlight.created{ entityId, handle }listens, picks up the ambient light

Needs

  • DirectionalLight: plays both the sun and the moon.
  • HemisphericLight: soft fill from above.
  • MeshPrimitive: tells it where the car is.

More like this

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search