Lighting and Environment in Decentraland
RULE: Use Smart Items for lights when the Creator Hub MCP is available
When the user asks to add a light and the Creator Hub MCP tools are present (mcp__creator-hub__*), always search_catalog first before manually creating an entity with LightSource. The Creator Hub catalog includes ready-made light Smart Items:
- Spotlight (
87c829b3-8e8d-4fcb-9f43-85d3aa9084f6, categorylights) -- comes with a real GLB model (assets/asset-packs/spotlight/spotlight.glb), a pre-configuredcore::LightSource, built-in Turn On / Turn Off / Toggle actions, and properinspector::Configfor the Creator Hub UI. - Point Light (category
lights) -- same pattern: GLB placeholder, pre-configuredcore::LightSourceof type Point, built-in actions. - There is also a decorative "Spotlight" (category
decorations) that is a visual-only fixture model with noLightSource-- make sure you pick from thelightscategory when the user wants actual illumination.
Use place_smart_item with the assetId and a position. It handles everything: downloading the GLB files, resolving asset paths, setting up asset-packs::Placeholder, asset-packs::Script, asset-packs::Actions, and inspector::Config components. The entity is immediately usable in the editor with action triggers wired up.
Only fall back to manual create_entity + set_component core::LightSource when:
- No Creator Hub MCP tools are available (pure code workflow).
- The user needs a light with no visual model (invisible light source).
- The user needs non-standard light parameters that a Smart Item does not expose.
The manual LightSource component API documented below remains the authoritative reference for both paths -- Smart Items use the same underlying component.
RULE: 3D model light fixtures do not emit light
Light-looking geometry in a GLB model (lamp meshes, bulb shapes, glowing filaments) is purely visual -- it does not cast actual dynamic light in Decentraland. The renderer treats it as regular geometry (possibly with an emissive material for a glow effect, but no illumination of surrounding objects). To get real dynamic lighting from a fixture model, attach a LightSource component to the same entity or to a child entity positioned at the light source. When a user has a scene model with built-in light fixtures, proactively mention this: "The light fixtures in your model are decorative geometry only -- I need to add LightSource components for actual illumination."
Point Lights
Emit light in all directions from a position:
import { engine, Transform, LightSource } from '@dcl/sdk/ecs'
import { Vector3, Color3 } from '@dcl/sdk/math'
const light = engine.addEntity()
Transform.create(light, { position: Vector3.create(8, 3, 8) })
LightSource.create(light, {
type: LightSource.Type.Point({}),
color: Color3.White(),
intensity: 16000 // candela
})
Colored Point Light
LightSource.create(light, {
type: LightSource.Type.Point({}),
color: Color3.create(1, 0.5, 0), // Warm orange
intensity: 16000,
range: 15 // Maximum distance in meters
})
Defaults (from the protocol): active true, color white, intensity 16000 candela, range -1 (auto), shadow false. color is Color3 (RGB, each 0–1).
Spot Lights
Emit a cone of light in a direction:
import { Quaternion } from '@dcl/sdk/math'
const spotlight = engine.addEntity()
Transform.create(spotlight, {
position: Vector3.create(8, 4, 8),
rotation: Quaternion.fromEulerDegrees(-90, 0, 0) // Point downward
})
LightSource.create(spotlight, {
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
color: Color3.White(),
intensity: 16000
})
innerAngle— full-brightness cone angle (degrees). Default21.8. Min0, max179.outerAngle— outer fade angle (degrees). Default30. Max179.innerAnglecannot exceedouterAngle— if it does, the engine clamps them to the same value.- The light direction follows the entity's forward vector (set via Transform rotation).
typeis a discriminated union. To read/mutate spot params at runtime, narrow first:if (comp.type?.$case === 'spot') { comp.type.spot.innerAngle = 30 }
Shadows
Enable shadows on point or spot lights:
LightSource.create(spotlight, {
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
shadow: true,
intensity: 800
})
Note: shadows are only rendered for spot lights, not point lights. shadow is a top-level boolean on the component (not inside Spot/Point).
Shadow Mask Textures (Gobos)
Project a pattern through the light:
const maskedLight = LightSource.getMutable(spotlight)
maskedLight.shadowMaskTexture = Material.Texture.Common({
src: 'assets/Images/lightmask1.png'
})
- Set
shadowMaskTexture = undefinedto remove the mask again. - The mask projects light shape (e.g. a window pattern) — simulating caustics/soft shadows. Used on spot lights.
Toggling Lights
// Toggle on/off
const lightData = LightSource.getMutable(light)
lightData.active = !lightData.active
Light Limits
- A scene may create many lights (the engine test scene spawns ~9 across 2 parcels); the renderer decides how many render.
- Depending on the player's quality settings, between ~4 and ~10 lights render at once. If the scene has more than that, only the closest lights to the player are rendered.
- Up to ~3 shadow-casting lights render at once.
- The renderer auto-culls lights based on quality settings and proximity.
- Intensity is in candela (lumens/m² at 1m, i.e. lumens/4π). Default
16000. rangedefault is-1→ auto-computed asintensity^0.25(fourth root, in meters). Set an explicitrangeto override — this also limits a light's influence and saves performance.- Spread lights out so few are near the player at once (only the closest ones render).
SkyboxTime (Day/Night Cycle)
Use SkyboxTime for atmosphere — nighttime scenes with point lights create dramatic environments.
Fixed Time in scene.json
Set a permanent time of day without code. Two valid locations:
// Genesis City scene — top-level
{ "skyboxConfig": { "fixedTime": 43200 } }
// World — inside worldConfiguration
{ "worldConfiguration": { "name": "my-name.dcl.eth", "skyboxConfig": { "fixedTime": 36000 } } }
Time values (seconds since midnight, full day = 86400): 0 = midnight, 21600 = 6 AM, 43200 = noon, 64800 = 6 PM (dusk), 86400 = full day.
Precedence (verified against the engine test scenes): worldConfiguration.skyboxConfig.fixedTime wins over top-level skyboxConfig.fixedTime; either JSON value is in turn overridden at runtime by a SkyboxTime component on engine.RootEntity.
Read Current World Time
import { getWorldTime } from '~system/Runtime'
executeTask(async () => {
const time = await getWorldTime({})
console.log('Seconds since midnight:', time.seconds)
})
Change Time Dynamically
import { engine, SkyboxTime, TransitionMode } from '@dcl/sdk/ecs'
// Set time of day (must target the root entity)
SkyboxTime.create(engine.RootEntity, { fixedTime: 43200 }) // Noon
// Change with transition direction
SkyboxTime.createOrReplace(engine.RootEntity, {
fixedTime: 64800, // Dusk (6 PM)
transitionMode: TransitionMode.TM_BACKWARD // TM_FORWARD (0, default) or TM_BACKWARD (1)
})
// Remove the component to hand control back to global/world time
SkyboxTime.deleteFrom(engine.RootEntity)
transitionMode(optional) sets the animation direction when the time changes. DefaultTM_FORWARD.- The component must live on
engine.RootEntity. Deleting it reverts to the scene.json/world time (or the global day/night cycle).
Day/Night Cycle System
let currentTime = 43200
const CYCLE_SPEED = 100 // Time units per second
function dayNightCycle(dt: number) {
currentTime = (currentTime + CYCLE_SPEED * dt) % 86400
SkyboxTime.createOrReplace(engine.RootEntity, {
fixedTime: currentTime
})
}
engine.addSystem(dayNightCycle)
Realm Info
Detect which realm (server) the player is connected to:
import { getRealm } from '~system/Runtime'
executeTask(async () => {
const realm = await getRealm({})
console.log('Realm:', realm.realmInfo?.realmName)
console.log('Network:', realm.realmInfo?.networkId)
console.log('Base URL:', realm.realmInfo?.baseUrl)
})
Emissive Materials (Glow Effects)
For a visual glow without casting light on surroundings:
import { engine, Material } from '@dcl/sdk/ecs'
import { Color4, Color3 } from '@dcl/sdk/math'
// Self-illuminated material (emissiveColor uses Color3, not Color4)
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0, 0, 0, 1),
emissiveColor: Color3.create(0, 1, 0), // Green glow
emissiveIntensity: 2.0
})
Note: emissive materials don't illuminate other surrounding entities, they just have a glow effect on them.
Combining Emissive + LightSource
For an object that both glows visually and casts light:
// Visual glow on the mesh
Material.setPbrMaterial(bulb, {
emissiveColor: Color3.create(1, 0.9, 0.7),
emissiveIntensity: 1.5
})
// Actual light emission
LightSource.create(bulb, {
type: LightSource.Type.Point({}),
color: Color3.create(1, 0.9, 0.7),
intensity: 200,
range: 10
})
Shadow Quality
shadow is a top-level optional boolean on the LightSource component (default false). There is no shadow-type enum — quality is automatic and distance-based. Spot({...}) accepts only innerAngle? and outerAngle?.
import { LightSource } from '@dcl/sdk/ecs'
// Spot light with shadows enabled
LightSource.create(spotEntity, {
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
shadow: true, // top-level boolean
intensity: 800
})
Constraints:
- Shadows are only supported for spot lights; point lights do not cast shadows.
- Max 3 shadow-casting lights rendered at a time — disable
shadowon lights that don't need it. Spot lights with shadows suit dramatic effects such as flashlights. - Shadow quality/culling is automatic, based on the light's distance from the player. Exact distances vary by light type and the player's quality settings; general rule:
| Distance from player | Result |
|---|---|
| < 10 m | Soft shadows (high quality) |
| 10–20 m | Hard shadows (low quality) |
| > 20 m | No shadows rendered |
- The light itself keeps illuminating at much larger distances — it is only disabled when the player is more than 160 m away (10 parcels). This makes LightSource suitable for large-scale setups like stage lighting at live events, where most of the audience is far from the fixtures.
- Lights only render while the player is standing inside the scene; outside, they are not rendered.
Need advanced material effects? See the advanced-rendering skill for metallic, roughness, transparency, texture maps, texture tweens, and texture modes.
Platform Support
- Desktop (Unity explorer): Full support (point lights, spot lights, shadows).
- Mobile (Godot explorer):
LightSource(scene dynamic lights) ships in v1.13.0 (September 2026). Until then the mobile renderer ignores the component. - Bevy explorer: Full support (point lights, spot lights, shadows).
Verified against docs commit 09c5818 (mobile parity tracker, Aug 2026).
Gotchas
rangeleft unset (-1) is auto-derived from intensity asintensity^0.25— small intensities give surprisingly short range. Setrangeexplicitly for predictable falloff.shadowonly affects spot lights; setting it on a point light has no effect.- Animating a light's direction: put a
Tween/TweenSequence(Rotate mode) on the light entity — the beam follows the entity's forward vector. SkyboxTimeonRootEntityoverrides any scene.jsonfixedTime;deleteFromreverts to it.- Baked light geometry is inert. GLB models with lamp/bulb meshes do not emit light -- add a
LightSourcecomponent for real illumination. See the rule above. - Smart Item
Placeholderneeds a resolved file path, not a template variable. Setting{assetPath}/spotlight.glbas thesrcin anasset-packs::Placeholderresults in an invisible/broken gizmo.place_smart_itemresolves this automatically to e.g.assets/asset-packs/spotlight/spotlight.glb. If you must setPlaceholdermanually viaset_component, use the real on-disk path.
Example scenes
Engine-team test scenes (real API, exercised against the engine):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,4-dynamic-lights — point & spot LightSource: toggle active, color, range, intensity, spot inner/outer angle at runtime, shadow-mask (gobo) swapping, and tweened light rotation.
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/2,0-skybox-scene-json — fixed skybox time via top-level
skyboxConfig.fixedTime; reads it back withgetSceneInformation. - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/3,0-skybox-world-json — fixed skybox time via
worldConfiguration.skyboxConfig.fixedTime(World variant); demonstrates the worldConfiguration-wins precedence. - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/2,1-skybox-sdk-scene-a and https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/3,1-skybox-sdk-scene-b — runtime
SkyboxTimeonRootEntitywithTransitionMode, plusdeleteFromto return to global time.