camera-control

Installation
SKILL.md

Camera Control in Decentraland

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)

React to Camera Mode Changes

Use CameraMode.onChange to get notified only when the player toggles between first and third person — cheaper than polling every frame:

import { CameraMode, engine } from '@dcl/sdk/ecs'

CameraMode.onChange(engine.CameraEntity, (camera) => {
	if (!camera) return
	// camera.mode is 0 for first person, 1 for third person
	console.log('Camera mode changed:', camera.mode)
})

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)

## Camera and Colliders

When a player's camera moves in 3rd person mode, the camera might be blocked by colliders or not, depending on the collision layers assigned to the entities. To avoid the camera from going through walls, you must assign both the ColliderLayer.CL_PHYSICS and the ColliderLayer.CL_POINTER layers to the entities that you want to block the camera.

// NO CAMERA GOING THROUGH THE WALL
// default (both pointer and physics use the invisible geometry)
GLTFContainer.create(myEntity, {
	src: '/models/myModel.gltf',
})

// NO CAMERA GOING THROUGH THE WALL
// Both use the same invisible geometry
GltfContainer.create(myEntity2, {
	src: '/models/myModel.gltf',
	invisibleMeshesCollisionMask:
		ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
})

// NO CAMERA GOING THROUGH THE WALL
// Both use the same visible geometry
GltfContainer.create(myEntity2, {
	src: '/models/myModel.gltf',
	visibleMeshesCollisionMask:
		ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
})

// YES CAMERA GOES THROUGH THE WALL
// physics and pointer are on different layers
GltfContainer.create(myEntity2, {
	src: '/models/myModel.gltf',
	invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS,
	visibleMeshesCollisionMask: ColliderLayer.CL_POINTER,
})

// YES CAMERA GOES THROUGH THE WALL
// physics and pointer are on different layers
GltfContainer.create(myEntity2, {
	src: '/models/myModel.gltf',
	invisibleMeshesCollisionMask: ColliderLayer.CL_POINTER,
	visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS,
})

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.

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
Related skills

More from decentraland/sdk-skills

Installs
3
GitHub Stars
3
First Seen
Apr 13, 2026