Skip to content
Menu

Docs · Make it look right

Lighting and shadows

20 minutes Uses: AmbientLight, Light, Occluder, Sprite, set_number, pulse_number, set_flag

By the end

  • A room that stays dark until a lamp lights it
  • Crates that cast shadows away from the lamp
  • A lamp that flickers, from a script
On this page

Make the room dark

Lighting starts from darkness. Once a Light is on in a room, everything in it is drawn as dark as the room’s ambient colour, and each Light brings what it reaches back up toward its own colours. A room with no Light in it draws everything at its own colour, as your rooms have so far.

A room of grey walls and floor with three orange crates and a small figure, every surface evenly bright
No Light in the room, so every surface is drawn at its own colour.
  1. In the Hierarchy, add a node with + Add node → Ambient Light.
  2. Set its Color to a very dark blue, close to black.

Nothing changes yet. The Ambient Light sets how dark the room is once it is lit, and the next section lights it.

Color
The colour a part of the room no light reaches is left at. Its alpha is ignored.
Enabled
Whether it sets the room’s darkness. Unticked, the room falls back to a dim blue rather than to full brightness.

Only the first enabled Ambient Light in the Hierarchy counts. A lit room without one uses the same dim blue.

A lamp

  1. Add a node with + Add node → Light, and drag it to where the lamp hangs.
  2. The room goes dark, apart from a warm pool around the lamp. The circle in the Viewport is the light’s Radius: drag the dot on it to make the light reach further.
A lamp node selected at the left of a dark room, a large circle around it in the Viewport. The Inspector shows its Light: Kind Point, Radius 400, Intensity 1.35, a warm Color, Channel 0 and Softness 0
The circle is the Radius. The light fades from full Intensity at its middle to nothing at the circle.
Enabled
Whether it lights anything.
Kind
The shape it lights. See the next section.
Radius
How far it reaches, in world units. At 0 a Point, Cone or Directional light lights nothing. For a Sprite or Freeform light it is only how far its shadows reach.
Intensity
How bright it is at its middle.
Color
Its colour.
Softness
How wide the lamp itself is, in world units. At 0 its shadows have hard edges; above it, they soften.
Channel
How its light lands on the room. 0 is the room’s own lighting, and where every lamp belongs until one needs to do something else. 1 adds its light on top, so it can brighten a wall past the wall’s own colour. 2 takes light away. 3 paints its colour over the room, as strongly as it reaches.
The same room dark and cold blue, lit warm by one lamp near its left wall, the far crates dim
One lamp against a dark ambient. A warm lamp in a cold, dark room reads as light; the same hue for both reads as a brighter patch.

The shape of a light

KindWhat it lights
PointEverything around it, out to its Radius. The default.
ConeA wedge pointing along Direction, Angle wide. The Angle is in radians: 1.57 is a quarter turn. The wedge’s edge fades over the outer fifth of each side.
DirectionalEverything in view, evenly, from one Direction, like sunlight. It has no middle to fade from, and where its node stands does not matter. Its Radius is how far its shadows reach; at 0 it lights nothing.
SpriteThe rectangle Size, through the picture in Texture: where the picture is bright, the light gets through. With no picture, a soft glow fills the rectangle. Radius is only how far its shadows reach.
FreeformA shape you draw, its Outline, softened for Falloff world units in from its edge, up to half the outline’s narrowest width. Radius is only how far its shadows reach.
A dark room lit by a wedge of warm light from a lamp high on the left, pointing down and to the right, with crates casting shadows inside the wedge. The Inspector shows the lamp’s Light, Kind Cone, Radius 480, Direction 0.6 by 0.8 and Angle 0.9, and under it a Light Shader with Shader soft.wgsl
A Cone with soft.wgsl for its light shader: the shader’s fade, and a hard edge to the wedge.

In the Viewport, a Cone or Directional light has an arrow you drag to aim it, and a Cone has a handle for its width. A Freeform light’s points are handles too: double-click its edge to add a point, and press Backspace over one to take it away. A new Freeform light has no points, so start it with three: press + beside Outline three times and type where each goes.

Shadows

Light passes through everything until something blocks it. An Occluder is a box that blocks light: give one to each thing that should cast a shadow.

  1. Select a crate, press Add component, type occluder and choose OccluderComponent.
  2. Set its Size to the crate’s, or drag its handles in the Viewport. A shadow falls away from the lamp, and it moves when you move the crate or the lamp.
The dark room lit by the lamp on the left, the crates casting dark wedges away from it across the floor
Each crate carries an Occluder, so each casts a shadow away from the lamp.
The largest crate selected in the lit room, and in the Inspector its Occluder: Enabled, Size 96 by 96, and an empty Outline
An Occluder is a box, the size of the thing it belongs to.
Enabled
Whether it blocks light.
Size
The box, in the node’s own units, centred on it.
Outline
A shape to cast from in place of the box: three points or more, in the node’s own units. The Viewport draws the shape that casts.

A shadow is only light that did not arrive. It is as dark as the ambient and never darker, and a second lamp shining into it lights it. The box, or the outline, scales and turns with its node, and casts as it is turned. Hide the node and its shadow goes too.

Nothing else casts: a Sprite or a Shape lets light straight through. The one exception is tiles, when their layer is marked Occluder: see Tilemaps.

Sprites in the light

Four rows on a Sprite change how light treats it.

Unshaded
Drawn at full brightness whatever the light: a lit window, a glowing sign, the lamp itself.
Light Only
Shows only as much of it as light reaches, and nothing where no light falls: dust in a beam, a mark that only shows by lamplight.
Normal Map
A picture of the surface’s slopes, so a Point, Cone or Directional light rakes across it, lighting the faces turned to it. Make one from the picture itself in its import settings: see Projects and assets.
Specular
How brightly the surface catches a highlight where a light faces it, from 0 to 1.

An exported game draws normal maps as the editor does. A Freeform light, and a light with a shader of your own, light a normal-mapped sprite flat. Depth from normal maps makes one for a castle wall.

A lamp a script can change

Every number and switch on a Light can be changed while the game runs. set_number writes a number onto another component on the same node, by the field names the Light reference lists: intensity, radius and the rest.

  1. In Assets, choose Create → New script, type flicker and press Create.
  2. In the Code panel, click flicker.rgs in its Scripts list, replace its text with the script below, and press Ctrl+S.
  3. Select the lamp, press Add component, type flicker and choose Flicker.
  4. Press Play. The lamp flickers.
rgs flicker.rgs
/// Makes the Light on this node flicker like a flame.
component Flicker {
    /// How bright the flame burns between flickers.
    #[range(0.0..4.0)]
    brightness: f32 = 1.4,
    /// How far below that a flicker dips.
    #[range(0.0..1.0)]
    dip: f32 = 0.4,
    #[hidden]
    clock: f32 = 0.0,

    fn loop(dt: f32) {
        clock = clock + dt;
        // Two waves that never line up, so the flicker never repeats.
        let wave = (clock * 11.0).sin() * (clock * 7.3).sin();
        let level = brightness - dip * wave.abs();
        set_number("LightComponent", "intensity", level);
    }
}

The dip is smaller than the brightness, so the lamp never quite goes out. If it did, and it was the room’s only Light, the room would drop to its ambient for that instant.

For a slow swell rather than a flicker, a pulse does the work in one call. pulse_number takes a number field to a value and back again, forever:

rgs breathe.rgs
/// Makes the Light on this node swell and fade, forever.
component Breathe {
    /// The brightness it swells to.
    brightest: f32 = 2.5,
    /// Seconds to swell, and the same again to fade.
    seconds: f32 = 1.5,

    fn on_create() {
        pulse_number("LightComponent", "intensity",
            brightest, seconds, .InOutSine);
    }
}

set_flag switches a lamp the same way: set_flag("LightComponent", "enabled", false). Switching off a room’s last Light leaves it as dark as its ambient.