skills/camera-control/SKILL.md
Control camera behavior in Decentraland scenes. CameraMode detection, CameraModeArea (force first/third person in regions), VirtualCamera (cinematic scripted cameras), MainCamera (read position/rotation), and camera-triggered events. Use when the user wants camera control, cutscenes, cinematic views, forced camera modes, or camera tracking. Do NOT use for input restriction during cutscenes (see advanced-input for InputModifier).
npx skillsauth add dcl-regenesislabs/opendcl camera-controlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
CameraModeArea and VirtualCamera are supported in main-entities.ts — both static-by-nature components belong there. MainCamera is NOT supported because it lives on the reserved engine.CameraEntity; activate a virtual camera at runtime in src/index.ts.
VirtualCamera.lookAtEntity accepts an entity name in main-entities.ts (resolved to an Entity ID at build time, same as Transform.parent).
// main-entities.ts
cinematic_cam: {
components: {
Transform: {
position: { x: 12, y: 4, z: 8 },
rotation: { x: 0, y: 0.7071, z: 0, w: 0.7071 }
},
VirtualCamera: {
defaultTransition: { transitionMode: { $case: 'time', time: 2 } },
lookAtEntity: 'shopkeeper' // name of another entity in this file
}
}
},
first_person_zone: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
CameraModeArea: {
area: { x: 16, y: 2, z: 16 },
mode: 0 // CameraType.CT_FIRST_PERSON
}
}
}
Activate the cinematic camera at runtime:
// src/index.ts
import { engine, MainCamera } from '@dcl/sdk/ecs'
export function main() {
const cam = engine.getEntityOrNullByName('cinematic_cam')
if (cam) MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cam })
}
The reserved engine.CameraEntity and engine.PlayerEntity are engine-managed and have no representation in main-entities.ts.
CameraModeArea.mode| value | enum | meaning | |---|---|---| | 0 | CT_FIRST_PERSON | Force first-person inside the zone | | 1 | CT_THIRD_PERSON | Force third-person inside the zone | | 2 | CT_CINEMATIC | Force cinematic camera inside the zone |
Access the camera's current position and rotation via the reserved engine.CameraEntity:
import { engine, Transform } from '@dcl/sdk/ecs'
function trackCamera() {
if (!Transform.has(engine.CameraEntity)) return
const cameraTransform = Transform.get(engine.CameraEntity)
console.log('Camera position:', cameraTransform.position)
console.log('Camera rotation:', cameraTransform.rotation)
}
engine.addSystem(trackCamera)
Check whether the player is in first-person or third-person:
import { engine, CameraMode, CameraType } from '@dcl/sdk/ecs'
function checkCameraMode() {
if (!CameraMode.has(engine.CameraEntity)) return
const cameraMode = CameraMode.get(engine.CameraEntity)
if (cameraMode.mode === CameraType.CT_FIRST_PERSON) {
console.log('First person camera')
} else if (cameraMode.mode === CameraType.CT_THIRD_PERSON) {
console.log('Third person camera')
}
}
engine.addSystem(checkCameraMode)
CameraType.CT_FIRST_PERSON // First-person view
CameraType.CT_THIRD_PERSON // Third-person view (default)
Force a specific camera mode when the player enters an area:
import { engine, Transform, CameraModeArea, CameraType } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const fpArea = engine.addEntity()
Transform.create(fpArea, { position: Vector3.create(8, 1.5, 8) })
CameraModeArea.create(fpArea, {
area: Vector3.create(6, 4, 6), // 6x4x6 meter box
mode: CameraType.CT_FIRST_PERSON // Force first-person inside
})
When the player leaves the area, the camera reverts to their preferred mode.
Create scripted camera positions for cutscenes or special views:
import { engine, Transform, VirtualCamera, MainCamera } from '@dcl/sdk/ecs'
import { Vector3, Quaternion } from '@dcl/sdk/math'
const cinematicCam = engine.addEntity()
Transform.create(cinematicCam, {
position: Vector3.create(8, 5, 2),
rotation: Quaternion.fromEulerDegrees(-20, 0, 0)
})
VirtualCamera.create(cinematicCam, {
defaultTransition: {
transitionMode: VirtualCamera.Transition.Speed(1.0)
}
})
// Activate the virtual camera
MainCamera.getMutable(engine.CameraEntity).virtualCameraEntity = cinematicCam
// Return to normal camera
MainCamera.getMutable(engine.CameraEntity).virtualCameraEntity = undefined
VirtualCamera.Transition.Speed(1.0) // Speed-based smooth transition
VirtualCamera.Transition.Time(2) // Time-based transition (2 seconds)
Make the virtual camera track an entity:
const target = engine.addEntity()
Transform.create(target, { position: Vector3.create(8, 1, 8) })
VirtualCamera.create(cinematicCam, {
lookAtEntity: target,
defaultTransition: {
transitionMode: VirtualCamera.Transition.Speed(2.0)
}
})
// Activate
MainCamera.getMutable(engine.CameraEntity).virtualCameraEntity = cinematicCam
Poll camera position each frame for camera-triggered events:
import { engine, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
let lastNotifiedZone = ''
function cameraZoneSystem() {
if (!Transform.has(engine.CameraEntity)) return
const camPos = Transform.get(engine.CameraEntity).position
let currentZone = ''
if (camPos.y > 10) {
currentZone = 'sky'
} else if (camPos.x < 4) {
currentZone = 'west'
} else {
currentZone = 'center'
}
if (currentZone !== lastNotifiedZone) {
lastNotifiedZone = currentZone
console.log('Camera entered zone:', currentZone)
}
}
engine.addSystem(cameraZoneSystem)
Use the camera position to trigger actions when the player looks at a specific area:
function cameraLookTrigger() {
const camTransform = Transform.get(engine.CameraEntity)
const targetPos = Vector3.create(8, 2, 8)
const distance = Vector3.distance(camTransform.position, targetPos)
if (distance < 5) {
// Player is close — check if camera is pointing at target
// Use raycasting for precise look detection (see add-interactivity skill)
}
}
engine.addSystem(cameraLookTrigger)
Move camera to track an NPC by updating a VirtualCamera's Transform:
function followNpcCamera(dt: number) {
const npcPos = Transform.get(npcEntity).position
const camTransform = Transform.getMutable(cinematicCam)
// Position camera behind and above the NPC
camTransform.position = Vector3.create(
npcPos.x - 2,
npcPos.y + 3,
npcPos.z - 2
)
}
engine.addSystem(followNpcCamera)
Freezing player during cutscenes? Combine VirtualCamera with
InputModifierfrom the advanced-input skill to prevent player movement during cinematic sequences.
In third-person mode the player's camera can slide through walls if the wall's collider mask doesn't include CL_POINTER. The camera uses the pointer collider mask for occlusion checks — not CL_PHYSICS. Set both masks on walls and architecture that the camera should bounce off:
// main-entities.ts
wall: {
components: {
Transform: { position: { x: 8, y: 1.5, z: 16 } },
GltfContainer: {
src: 'models/wall.glb',
visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
}
}
}
For GLBs that ship with invisible collider meshes (Creator Hub asset packs), set invisibleMeshesCollisionMask: 3 instead. Default of CL_PHYSICS only would let the camera pass through.
CameraModeArea to force first-person in tight indoor spaces.engine.CameraEntity — never try to write to it directly.add-interactivity skill).development
Capture screenshots of the running Decentraland preview to verify scene changes visually. Covers camera movement, interaction actions, and visual debugging. Use when the preview is running and you need to check what the scene looks like, debug visual issues, or verify layout. Do NOT use for code changes (make changes first, then screenshot).
development
Cross-cutting runtime APIs for Decentraland SDK7 scenes. Use when the user needs async operations (executeTask), HTTP requests (fetch, signedFetch), WebSocket connections, timers, realm/scene detection, restricted actions (movePlayerTo, teleportTo, triggerEmote, openExternalUrl), portable experiences, or the testing framework. Do NOT use for UI (see build-ui), multiplayer sync (see multiplayer-sync), or avatar/player data (see player-avatar).
development
Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
data-ai
Player and avatar system in Decentraland. Read player position/profile, customize appearance (AvatarBase), trigger emotes (triggerEmote/triggerSceneEmote), read equipped wearables (AvatarEquippedData), attach objects to players (AvatarAttach), create NPC avatars (AvatarShape), avatar modifier areas, and locomotion settings. Use when the user wants player data, emotes, wearables, NPC avatars, avatar attachments, or movement speed changes. Do NOT use for wallet/blockchain interactions (see nft-blockchain).