Lighting - UQcsse3200/2026-studio-1 GitHub Wiki

Lighting is driven entirely by the level's map JSON. A map with no lighting block is fully bright, so only levels that opt in become dark.

How it works

  • Ambient light is the base darkness of the level. Each room sets it when it loads, and maps without a lighting block reset it to full bright.
  • Tile lights come from legend entries that have a light key. On load, EmitterScanner finds every matching tile on every layer. Adjacent tiles of the same symbol are merged into a rectangle, and lights are spaced along it, so a long lava river gets a few lights, not one per tile.
  • Player light follows the player. It is owned by the room, so it is disposed with the room.
  • Sub-level ambient: when the player crosses into another sub-level (for example dungeon to nether), ambient fades to that sub-level's setting over 0.5s.
  • Lights are drawn by LightService, between the world and the UI.

Adding lighting to a map

1. Add a lighting block (top level of the map JSON)

"lighting": {
  "ambient": { "color": "background_dark", "intensity": 0.25 },
  "player":  { "radius": 3.5, "color": "resinous_torch", "flicker": 0.05 },
  "subLevels": {
    "dungeon": { "ambient": { "color": "background_dark", "intensity": 0.35 } },
    "nether":  { "ambient": { "color": "lava_glow", "intensity": 0.2 } }
  }
}
Field Meaning
ambient.color Theme name (see below) or hex such as "ffcc88"
ambient.intensity 0 to 1. Lower is darker. It multiplies the ambient colour, so a dark colour at low intensity is near black.
player.radius Light radius in world units (one tile is 0.5)
player.color Theme name or hex
player.flicker 0 to 1, how much the radius varies
subLevels.<id>.ambient Overrides ambient for the sub-level with that id. Missing fields inherit the map-wide ambient.

Everything is optional. Missing values use defaults, and bad values log a warning instead of failing the level.

2. Make tiles emit light

Add light keys to a legend entry. Any tile using that symbol, on any layer, becomes an emitter.

"~": {
  "type": "HAZARD",
  "texture": "images/level1/river-phlegethon-512px.png",
  "light": "point",
  "lightColour": "lava_glow",
  "lightRadius": "2.5",
  "flicker": "0.15"
}
Key Meaning Default
light Must be present to emit. Only "point" exists. none (no light)
lightColour Theme name or hex white
lightRadius World units, minimum 0.5 2
flicker 0 to 1 0

Note the spelling: lightColour on tiles, color (or colour) in the lighting block.

3. Pick a colour

Theme names live in the LightColour enum and are case-insensitive: background_dark, resinous_torch, ashen_torch, oil_lamp, elysian_fade, lava_glow. Add new ones to the enum so every level shares the same palette. Hex is accepted for one-offs.

Tips and limits

  • Use radii of about 2 to 4. The map is only 28 by 16 world units.
  • There is a budget of 40-160 tile lights per map. Past that, lights are thinned and a warning is logged.
  • Lights ignore walls (xray), so a light near a sub-level boundary can light the other side. Keep radii below the distance to the boundary.
  • Steady lights (flicker 0) are cheap. Flickering lights are recalculated every frame, so don't flicker everything.
  • If a light doesn't appear, check the console for Tile light placements and for colour warnings. Lights outside the camera view aren't drawn.

Code map

Class Role
LightService Owns the RayHandler, ambient, and ambient fades
LightingConfigParser / LightingConfig Parse the lighting block
EmitterScanner Turns legend light keys into light placements
LightComponent One light, fixed or following an entity
LightColour Named colours
LevelGameArea.spawnLighting() Applies all of the above when a room loads