camera-control

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Camera Control in Decentraland

Decentraland 中的摄像机控制

Reading Camera State

读取摄像机状态

Access the camera's current position and rotation via the reserved
engine.CameraEntity
:
typescript
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)
engine.CameraEntity
is read-only — never write to its Transform. To move a camera under scene control, drive a VirtualCamera instead (see VirtualCamera below).
通过保留实体
engine.CameraEntity
访问摄像机的当前位置和旋转:
typescript
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)
engine.CameraEntity
是只读的——切勿写入它的 Transform。要在场景控制下移动摄像机,请改为驱动 VirtualCamera(见下文的 VirtualCamera)。

Camera Mode Detection

摄像机模式检测

Check whether the player is in first-person or third-person:
typescript
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)
检查玩家当前处于第一人称还是第三人称:
typescript
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

摄像机模式值

typescript
CameraType.CT_FIRST_PERSON // First-person view
CameraType.CT_THIRD_PERSON // Third-person view (default)
typescript
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:
typescript
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)
})
使用
CameraMode.onChange
,仅在玩家于第一人称和第三人称之间切换时收到通知——这比每帧轮询更节省开销:
typescript
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)

CameraModeArea(在区域内强制指定摄像机模式)

Force a specific camera mode when the player enters an area (e.g. force first-person in tight indoor spaces):
typescript
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.
玩家进入某个区域时强制指定特定的摄像机模式(例如,在狭窄的室内空间中强制使用第一人称):
typescript
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
})
当玩家离开该区域时,摄像机会恢复为玩家首选的模式。

VirtualCamera (Cinematic Cameras)

VirtualCamera(电影级摄像机)

Create scripted camera positions for cutscenes or special views:
typescript
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),
	},
	// fov: 45, // optional: field of view in degrees (overrides the Explorer default)
})

// Activate the virtual camera (createOrReplace on first activation:
// getMutable throws if MainCamera doesn't exist yet)
MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cinematicCam })

// Return to normal camera (component now exists, so getMutable is safe)
MainCamera.getMutable(engine.CameraEntity).virtualCameraEntity = undefined
为过场动画或特殊视角创建脚本化的摄像机位置:
typescript
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),
	},
	// fov: 45, // optional: field of view in degrees (overrides the Explorer default)
})

// Activate the virtual camera (createOrReplace on first activation:
// getMutable throws if MainCamera doesn't exist yet)
MainCamera.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cinematicCam })

// Return to normal camera (component now exists, so getMutable is safe)
MainCamera.getMutable(engine.CameraEntity).virtualCameraEntity = undefined

Transition Modes

过渡模式

typescript
VirtualCamera.Transition.Speed(1.0) // Speed-based smooth transition
VirtualCamera.Transition.Time(2) // Time-based transition (2 seconds)
Keep transition speeds between 0.5 and 3.0 for comfortable camera movement.
typescript
VirtualCamera.Transition.Speed(1.0) // Speed-based smooth transition
VirtualCamera.Transition.Time(2) // Time-based transition (2 seconds)
将过渡速度保持在 0.5 到 3.0 之间,以确保摄像机移动舒适流畅。

Look-At Target

注视目标

Make the virtual camera track an entity:
typescript
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.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cinematicCam })
lookAtEntity
can be
engine.PlayerEntity
to keep the camera aimed at the moving player (verified:
2,22-virtual-cameras
). To bake a fixed look direction into the camera's own rotation instead, use
Quaternion.fromLookAt(cameraPosition, targetPosition)
in the camera's
Transform
.
VirtualCamera.create(entity)
with no config is valid (defaults) — useful for a camera whose Transform you drive manually (see the controllable-camera pattern below).
VirtualCamera
accepts an optional
fov?: number
field (field of view in degrees). When set, the virtual camera overrides the Explorer's default FOV for as long as it is the active
MainCamera.virtualCameraEntity
. Omitting it uses the Explorer's default. Verified against js-sdk-toolchain
PBVirtualCamera
commit
26cb9251
.
Switching between cameras / deactivating: with
MainCamera
already present, mutate it via
MainCamera.getMutableOrNull(engine.CameraEntity)
; set
virtualCameraEntity
to another VirtualCamera entity to cut/transition to it, or to
undefined
to return to the player's normal camera (verified:
2,22-virtual-cameras
). Only one VirtualCamera is active at a time (
MainCamera.virtualCameraEntity
holds a single entity).
You cannot move the player's real camera directly. To move a camera under scene control, drive the Transform of an active VirtualCamera entity each frame; while it is the
MainCamera.virtualCameraEntity
, the player sees through it (verified:
2,22-virtual-cameras
controllable camera). Pair with
InputModifier
(advanced-input) to disable avatar movement so WASD drives the camera instead.
让虚拟摄像机追踪一个实体:
typescript
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.createOrReplace(engine.CameraEntity, { virtualCameraEntity: cinematicCam })
lookAtEntity
可以设为
engine.PlayerEntity
,使摄像机持续瞄准移动中的玩家(已通过
2,22-virtual-cameras
验证)。如果希望将固定的注视方向直接写入摄像机自身的旋转,请改为在摄像机的
Transform
中使用
Quaternion.fromLookAt(cameraPosition, targetPosition)
。
VirtualCamera.create(entity)
不带配置调用也是有效的(使用默认设置)——适用于需要手动驱动其 Transform 的摄像机(见下方的可控摄像机模式)。
VirtualCamera
接受可选的
fov?: number
字段(以度为单位的视场角)。设置后,只要该虚拟摄像机是活动的
MainCamera.virtualCameraEntity
,它就会覆盖 Explorer 的默认 FOV。省略该字段则使用 Explorer 的默认值。已针对 js-sdk-toolchain 的
PBVirtualCamera
提交
26cb9251
进行验证。
**切换摄像机/停用:**当
MainCamera
已存在时,通过
MainCamera.getMutableOrNull(engine.CameraEntity)
修改它;将
virtualCameraEntity
设为另一个 VirtualCamera 实体可切换或过渡到该摄像机,设为
undefined
则返回玩家的普通摄像机(已通过
2,22-virtual-cameras
验证)。同一时间只能有一个 VirtualCamera 处于活动状态(
MainCamera.virtualCameraEntity
仅保存一个实体)。
你无法直接移动玩家的真实摄像机。要在场景控制下移动摄像机,请每帧驱动活动的 VirtualCamera 实体的 Transform;在它是
MainCamera.virtualCameraEntity
期间,玩家将通过它观看场景(已在
2,22-virtual-cameras
的可控摄像机场景中验证)。将其与 InputModifier(advanced-input)配合使用,可禁用角色移动,让 WASD 改为控制摄像机。

Tracking Camera Position

跟踪摄像机位置

Poll camera position each frame by reading
Transform.get(engine.CameraEntity).position
inside a system. For a full worked zone-tracking system (
cameraZoneSystem
), see
{baseDir}/references/camera-patterns.md
→ "Tracking Camera Position (camera zone system)".
在 system 内读取
Transform.get(engine.CameraEntity).position
,即可每帧轮询摄像机位置。完整的区域跟踪系统(
cameraZoneSystem
)示例,请参阅
{baseDir}/references/camera-patterns.md
→ "Tracking Camera Position (camera zone system)"。
## Camera and Colliders
## Camera 与碰撞体
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.
ts
// 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,
})
当玩家的摄像机在第三人称模式下移动时,摄像机可能会被碰撞体阻挡,也可能不会,具体取决于分配给相关实体的碰撞层。要避免摄像机穿墙,必须为希望阻挡摄像机的实体同时分配
ColliderLayer.CL_PHYSICS
和
ColliderLayer.CL_POINTER
。
ts
// 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

常见模式

For full worked patterns, see
{baseDir}/references/camera-patterns.md
:
  • Camera-Triggered Events — use camera position/proximity to trigger actions when the player looks at an area.
  • Following an NPC (camera-follows-NPC) — track an NPC by driving a VirtualCamera's Transform each frame (guardrail on why this works lives in the VirtualCamera section above).
  • Mouselook Camera (FPS-style) — drive a VirtualCamera with
    PrimaryPointerInfo.screenDelta
    (pixel delta per frame, keeps working while cursor is locked). Accumulate into yaw/pitch, clamp pitch [-85,+85], combine with PointerLock + InputModifier
    disableAll
    . Desktop only (screenDelta always 0 on mobile). See
    {baseDir}/references/camera-patterns.md
    → "Mouselook Camera".
Freezing player during cutscenes? Combine VirtualCamera with
InputModifier
from the advanced-input skill to prevent player movement during cinematic sequences.
有关完整的可运行模式,请参阅
{baseDir}/references/camera-patterns.md
:
  • 摄像机触发的事件 — 使用摄像机的位置或距离,在玩家看向某个区域时触发操作。
  • 跟随 NPC(camera-follows-NPC) — 通过每帧驱动 VirtualCamera 的 Transform 来追踪 NPC(其有效性的安全说明见上文的 VirtualCamera 部分)。
  • 鼠标视角摄像机(FPS 风格) — 使用
    PrimaryPointerInfo.screenDelta
    驱动 VirtualCamera(每帧的像素增量,在光标锁定时仍然有效)。将增量累积到 yaw/pitch 中,将 pitch 限制在 [-85,+85],并结合 PointerLock + InputModifier
    disableAll
    。仅支持桌面端(移动端上 screenDelta 始终为 0)。参见
    {baseDir}/references/camera-patterns.md
    → "Mouselook Camera"。
需要在过场动画期间冻结玩家? 将 VirtualCamera 与 advanced-input skill 中的 InputModifier 结合使用,以防止玩家在电影式序列期间移动。

Example scenes

示例场景