Sampling the light level for stealth gameplay
How visible is the player? In a stealth game that is mostly a question of how much light falls where
they stand. The bake already knows: every light probe carries the light that reaches it, and
HammeriteMap.light_level_at() reads them at any
point - including what the lamps switched since the bake no longer give.
Hammerite measures; what counts as dark enough is your game's to decide.
1. Read it
var level: float = map.light_level_at(point)
point is in map space, where the brushes are authored. Under a HammeriteMap3D that has been
moved or turned, convert first:
func level_at(map_3d: HammeriteMap3D, world_point: Vector3) -> float:
return map_3d.map.light_level_at(map_3d.to_local(world_point))
The number is the light the nearest probes that can see the point carry, weighted by distance as the shaders weight them - so what is measured is what is drawn, and a wall between the point and a bright probe keeps that probe out of it. It counts:
- every baked light, switchable or not;
- each switchable light at its current strength - turn a lamp off and the reading drops at once;
- the bounce light the bake worked out.
It reads 0.0 where no probe can see the point: in the dark, and where the bake never reached.
2. Make it mean something
The level is not normalised to anything. It is in the bake's own units, so a sunlit courtyard may read in the tens, a lamplit room a few, and a back room well under one. Measure it in a place whose brightness you know - a spot you would call fully lit - and put your curve there:
@export var fully_lit_level: float = 6.0 # light_level_at, measured under the hall's lamps
func visibility(level: float) -> float:
return clampf(level / fully_lit_level, 0.0, 1.0)
A game wanting a finer feel shapes that further - a Curve, a few thresholds for a light gem. Retune
it when the lighting is retuned: doubling every lamp's energy doubles every reading.
HammeriteLightCurve is not that curve. It shapes how surfaces
look to the eye, on the surface's scale, and nothing in it touches this reading.
3. Sample the body, not a point
A player crouched with their head in a pool of lamplight is seen. Read a few points up the body and take what the game wants - the brightest, for a strict guard; the mean, for a forgiving one:
const HEIGHTS: Array[float] = [8.0, 36.0, 64.0] # feet, waist, head, in map units
func body_level(map: HammeriteMap, at: Vector3) -> float:
var brightest: float = 0.0
for height: float in HEIGHTS:
brightest = maxf(brightest, map.light_level_at(at + Vector3.UP * height))
return brightest
Keep the points inside the room: one exactly on the floor, or inside a wall, sees no probe and reads dark.
Each reading is a probe lookup and a line-of-sight test per probe found - microseconds with the native kernel, more without. A few points a few times a second is plenty for a light gem; ask on the main thread.
4. Lights the game switches
A lamp entity of the switchable types (light_point_dynamic, light_directional_dynamic) is baked
into its own slot and can be dimmed or put out while the game runs. Every switch reaches
light_level_at() immediately, so a player who puts out a torch is darker to the guards that same
frame:
map_3d.trigger_named("hall_torch", false) # off; true is on, null toggles
Or through what the bake recorded of it, by entity or by light group:
var torches: HammeriteBakedLight = map.get_baked_light_for_group(&"hall_torches")
if torches:
torches.set_modulation(0.25)
modulation is 0.0 for none of its light and 1.0 for all of it. Lights in one group share a
slot and are switched together. A light that is not switchable is in the reading too, but stays as
baked.
5. When there is something to read
Probes are placed and lit by the bake (Baking from a script), and saved with the map:
a baked map reads correctly as soon as it is loaded. An unbaked map reads 0.0 everywhere.
A new bake or a variant switch replaces the map's probes. Anything you derived from the readings - a
cached darkness map for guard AI, say - should be rebuilt on map.light_probes_baked and
map.light_probes_changed.
See also
- Probe lighting - the probes themselves, and lighting a character with them.
- Hearing for stealth gameplay - the other half of being noticed.
HammeriteBakedLight,HammeriteDynamicLight3D,HammeriteEyeAdaptation3D, which reads the same level at the camera to set the exposure.