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
lightingblock reset it to full bright. - Tile lights come from legend entries that have a
lightkey. On load,EmitterScannerfinds 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 (
flicker0) 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 placementsand 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 |