关于 camera-control
面向 Decentraland 场景开发者的相机控制技能,介绍如何设置区域相机模式、运行时启用虚拟相机并读取相机状态。还涵盖相机遮挡碰撞配置及常见镜头跟随模式。
主要功能
- 01设置区域相机模式
- 02创建并启用虚拟相机
- 03读取相机位置与模式
- 04配置相机遮挡碰撞
适用场景
- 01室内区域强制第一人称视角
- 02为过场动画设置镜头
- 03跟随 NPC 或触发相机事件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill camera-control安装后在 Claude Code 中输入/camera-control确认已加载。
详细介绍
Decentraland 中的摄像机控制
内容编写拆分
CameraModeArea 和 VirtualCamera 在 main-entities.ts 中受支持——这两个本质上是静态的组件都应放在这里。MainCamera 不受支持,因为它位于预留的 engine.CameraEntity 上;请在运行时于 src/index.ts 中激活虚拟摄像机。
在 main-entities.ts 中,VirtualCamera.lookAtEntity 接受实体名称(与 Transform.parent 相同,会在构建时解析为实体 ID)。
// 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
}
}
}
在运行时激活电影摄像机:
// 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 })
}
预留的 engine.CameraEntity 和 engine.PlayerEntity 由引擎管理,在 main-entities.ts 中没有对应项。
CameraModeArea.mode 的 CameraType 值
| value | enum | meaning |
|---|---|---|
| 0 | CT_FIRST_PERSON | 在区域内强制使用第一人称视角 |
| 1 | CT_THIRD_PERSON | 在区域内强制使用第三人称视角 |
| 2 | CT_CINEMATIC | 在区域内强制使用电影摄像机 |
读取摄像机状态
通过预留的 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)
检测摄像机模式
检查玩家处于第一人称还是第三人称:
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)
CameraModeArea(在区域内强制使用摄像机模式)
玩家进入某个区域时,强制使用指定的摄像机模式:
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(电影摄像机)
为过场动画或特殊视角创建脚本化摄像机位置:
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)
注视目标
让虚拟摄像机跟踪某个实体:
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
跟踪摄像机位置
每帧轮询摄像机位置,以触发摄像机相关事件:
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)
常见模式
摄像机触发事件
利用摄像机位置,在玩家看向特定区域时触发操作:
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)
跟随 NPC
通过更新 VirtualCamera 的 Transform,移动摄像机以跟踪 NPC:
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)
在过场动画期间冻结玩家? 将 VirtualCamera 与 advanced-input 技能中的
InputModifier结合使用,以防止玩家在电影序列期间移动。
摄像机与碰撞体(防止摄像机穿墙)
在第三人称模式下,如果墙壁的碰撞体掩码不包含 CL_POINTER,玩家的摄像机就可能穿过墙壁。摄像机使用 pointer 碰撞体掩码进行遮挡检测,而不是 CL_PHYSICS。请为摄像机应当碰撞反弹的墙壁和建筑结构设置这两个掩码:
// 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
}
}
}
对于包含不可见碰撞网格的 GLB(Creator Hub 资源包),请改为设置 invisibleMeshesCollisionMask: 3。如果仅使用默认值 CL_PHYSICS,摄像机就会穿过。
最佳实践
- 同一时间只能激活一个 VirtualCamera。
- 在狭窄的室内空间中,使用
CameraModeArea强制切换到第一人称视角。 - 将过渡速度保持在 0.5 到 3.0 之间,以获得舒适的摄像机移动效果。
- 通过
engine.CameraEntity读取摄像机状态——切勿尝试直接写入。 - 要检测注视目标,请结合摄像机位置和射线检测(参见
add-interactivity技能)。 - 除 VirtualCamera 和 CameraModeArea 外,摄像机控制均为只读——无法直接移动玩家的摄像机。