dcl-regenesislabs/opendcl

camera-control

Control camera behavior in Decentraland scenes.

First seen Feb 25, 2026

Installation

$ npx skills add dcl-regenesislabs/opendcl --skill camera-control

Summary

  • 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).

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from dcl-regenesislabs/opendcl · top by installs.

npx skills add dcl-regenesislabs/opendcl

Browse all from dcl-regenesislabs/opendcl

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 17
License Apache-2.0
Default branch main
Open issues 2
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,545 B
  • docs SUMMARY.md 454 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 36 installs

SKILL.md

Camera Control in Decentraland

Authoring split

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.

CameraType values for CameraModeArea.mode

value enum meaning
0 CTFIRSTPERSON Force first-person inside the zone
1 CTTHIRDPERSON Force third-person inside the zone
2 CT_CINEMATIC Force cinematic camera inside the zone

Reading Camera State

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)

Camera Mode Detection

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)

Camera Mode Values

CameraType.CT_FIRST_PERSON  // First-person view
CameraType.CT_THIRD_PERSON  // Third-person view (default)

CameraModeArea (Force Camera in a Region)

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.

VirtualCamera (Cinematic Cameras)

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

Transition Modes

VirtualCamera.Transition.Speed(1.0)  // Speed-based smooth transition
VirtualCamera.Transition.Time(2)     // Time-based transition (2 seconds)

Look-At Target

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

Tracking Camera Position

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)

Common Patterns

Camera-Triggered Events

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)

Following an NPC

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 InputModifier from the advanced-input skill to prevent player movement during cinematic sequences.

Camera vs Colliders (preventing camera-through-wall)

In third-person mode the player's camera can slide through walls if the wall's collider mask doesn't include CLPOINTER. The camera uses the pointer collider mask for occlusion checks — not CLPHYSICS. 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.

Best Practices

  • Only one VirtualCamera should be active at a time.
  • Use CameraModeArea to force first-person in tight indoor spaces.
  • Keep transition speeds between 0.5 and 3.0 for comfortable camera movement.
  • Read camera state via engine.CameraEntity — never try to write to it directly.
  • For look-at detection, combine camera position with raycasting (see add-interactivity skill).
  • Camera control is read-only outside of VirtualCamera and CameraModeArea — you cannot directly move the player's camera.