advanced-input
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdvanced Input Handling in Decentraland
Decentraland 中的高级输入处理
For basic click/hover events, see the skill. This skill covers advanced input patterns. Prefer (add-interactivity) for simple entity clicks; use for complex multi-key or polling patterns.
add-interactivitypointerEventsSystem.onPointerDown()inputSystem对于基础点击/悬停事件,请参阅 skill。本 skill 涵盖高级输入模式。对于简单的实体点击,优先使用 ();对于复杂的多按键或轮询模式,请使用 。
add-interactivitypointerEventsSystem.onPointerDown()add-interactivityinputSystemPointer Lock State
Pointer Lock 状态
Detect whether the cursor is captured (first-person mode) or free:
typescript
import { engine, PointerLock } from '@dcl/sdk/ecs'
function checkPointerLock() {
const isLocked = PointerLock.get(engine.CameraEntity).isPointerLocked
if (isLocked) {
// Cursor is captured — player is in first-person control
} else {
// Cursor is free — player can click UI elements
}
}
engine.addSystem(checkPointerLock)检测光标是否已被捕获(第一人称模式)或处于自由状态:
typescript
import { engine, PointerLock } from '@dcl/sdk/ecs'
function checkPointerLock() {
const isLocked = PointerLock.get(engine.CameraEntity).isPointerLocked
if (isLocked) {
// Cursor is captured — player is in first-person control
} else {
// Cursor is free — player can click UI elements
}
}
engine.addSystem(checkPointerLock)Requesting / releasing pointer lock (writable)
请求/解除 Pointer Lock(可写入)
PointerLock.isPointerLocked31,20-pointer-lock-controltypescript
PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false })
// request lock (e.g. from a button)
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = true
// release lock
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = falsePITFALL: throws if was never created on the camera. Call once in before mutating. Writing is a request; the client/player may still control actual capture (e.g. Esc unlocks).
getMutable(engine.CameraEntity)PointerLockPointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false })main()truePointerLock.isPointerLocked31,20-pointer-lock-controltypescript
PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false })
// request lock (e.g. from a button)
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = true
// release lock
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = falsePITFALL: 如果此前从未在相机上创建 , 会抛出异常。在修改之前,先在 中调用一次 。写入 只是发出一个请求;实际的捕获状态仍可能由客户端/玩家控制(例如按 Esc 会解除锁定)。
PointerLockgetMutable(engine.CameraEntity)main()PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false })truePointer Lock Change Detection
检测 Pointer Lock 变化
typescript
PointerLock.onChange(engine.CameraEntity, (pointerLock) => {
if (pointerLock?.isPointerLocked) {
console.log('Cursor locked')
} else {
console.log('Cursor unlocked')
}
})typescript
PointerLock.onChange(engine.CameraEntity, (pointerLock) => {
if (pointerLock?.isPointerLocked) {
console.log('Cursor locked')
} else {
console.log('Cursor unlocked')
}
})Cursor Position and World Ray
光标位置和世界射线
Get the cursor's screen position and the ray it casts into the 3D world:
typescript
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'
function readPointer() {
const pointerInfo = PrimaryPointerInfo.getOrCreateMutable(engine.RootEntity)
console.log('Cursor position:', pointerInfo.screenCoordinates)
console.log('Cursor delta:', pointerInfo.screenDelta)
console.log('World ray direction:', pointerInfo.worldRayDirection)
}
engine.addSystem(readPointer)获取光标的屏幕位置,以及其投射到 3D 世界中的射线:
typescript
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'
function readPointer() {
const pointerInfo = PrimaryPointerInfo.getOrCreateMutable(engine.RootEntity)
console.log('Cursor position:', pointerInfo.screenCoordinates)
console.log('Cursor delta:', pointerInfo.screenDelta)
console.log('World ray direction:', pointerInfo.worldRayDirection)
}
engine.addSystem(readPointer)Field details
字段详情
- (optional Vector2) — cursor position in pixels. Origin is the bottom-left corner of the screen (positive Y = up). When the cursor is locked, freezes at the screen center.
screenCoordinates - (optional Vector2) — how many pixels the mouse moved since the last frame. Positive
screenDelta= right, positivex= up (bottom-left origin). Keeps reporting raw mouse movement while the cursor is locked — unlikeyandscreenCoordinates, which freeze at screen center. This makesworldRayDirectionthe only way to read mouse movement during pointer lock, and the correct input for mouselook / FPS camera controls (see the camera-control skill's mouselook pattern). Desktop only — always reports 0 on mobile.screenDelta - (optional Vector3) — direction from the camera through the cursor. Freezes at center ray while locked.
worldRayDirection - —
pointerTypefor none,0for mouse.1
PITFALL: every field is optional — verified schema and , which guards each read (, ). Use so the component exists before first read, and always null-check the fields. feeds directly into a camera raycast direction (see the "spawn at cursor" pattern in that scene).
0,5-primary-cursor-infopointerInfo.screenCoordinates?.x ?? -666pointerInfo.worldRayDirection?.x.toFixed(2)getOrCreateMutable(engine.RootEntity)worldRayDirection- (可选的 Vector2)——光标在屏幕上的位置,单位为像素。原点位于屏幕左下角(Y 轴正方向为向上)。光标锁定时,该值会固定在屏幕中心。
screenCoordinates - (可选的 Vector2)——自上一帧以来鼠标移动的像素数。x 轴正值表示向右,y 轴正值表示向上(采用左下角原点)。光标锁定期间仍会持续报告原始鼠标移动——不同于会在屏幕中心冻结的
screenDelta和screenCoordinates。因此,worldRayDirection是 Pointer Lock 期间读取鼠标移动的唯一方式,也是 mouselook / FPS 相机控制的正确输入(见 camera-control skill 中的 mouselook 模式)。仅支持桌面端——在移动端始终报告 0。screenDelta - (可选的 Vector3)——从相机穿过光标的方向。锁定时会固定为中心射线。
worldRayDirection - ——
pointerType表示无指针,0表示鼠标。1
PITFALL: 每个字段都是可选的——已验证 schema 和 ,后者对每次读取都进行了保护(、)。使用 ,确保首次读取前组件已经存在,并且始终检查 null。 可直接用作相机 raycast 方向(见该场景中的“在光标处生成”模式)。
0,5-primary-cursor-infopointerInfo.screenCoordinates?.x ?? -666pointerInfo.worldRayDirection?.x.toFixed(2)getOrCreateMutable(engine.RootEntity)worldRayDirectionInput Polling with inputSystem
使用 inputSystem 轮询输入
Per-Entity Input Commands
单实体输入命令
Check if a specific input action occurred on a specific entity:
typescript
import { engine, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
function myInputSystem() {
// Check for click on a specific entity
const clickData = inputSystem.getInputCommand(
InputAction.IA_POINTER,
PointerEventType.PET_DOWN,
myEntity
)
if (clickData) {
console.log('Entity clicked via system:', clickData.hit.entityId)
}
}
engine.addSystem(myInputSystem)The returned command carries data (position and entity) — use when you need to know what was clicked.
hitgetInputCommand()Omit the entity argument to check globally (any entity / no target). Pass to match any action — returns the command for whatever key was pressed, and tells you which one (verified: ).
InputAction.IA_ANYgetInputCommand(InputAction.IA_ANY, PointerEventType.PET_DOWN)cmd.button0,1-input-modifierFor the Tag-based per-entity cookbook (mark entities with a Tag, fetch them with , and poll each with inside a system), see → "Per-Entity Input Command Cookbook (Tag-based)".
engine.getEntitiesByTaggetInputCommand{baseDir}/references/input-patterns.md检查特定实体上是否发生了特定输入操作:
typescript
import { engine, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
function myInputSystem() {
// Check for click on a specific entity
const clickData = inputSystem.getInputCommand(
InputAction.IA_POINTER,
PointerEventType.PET_DOWN,
myEntity
)
if (clickData) {
console.log('Entity clicked via system:', clickData.hit.entityId)
}
}
engine.addSystem(myInputSystem)返回的命令包含 数据(位置和实体)——需要知道点击了什么对象时,请使用 。
hitgetInputCommand()省略实体参数可执行全局检查(任意实体/无目标)。传入 可匹配任意操作—— 会返回任意被按下按键对应的命令,而 会指出具体是哪一个(已验证:)。
InputAction.IA_ANYgetInputCommand(InputAction.IA_ANY, PointerEventType.PET_DOWN)cmd.button0,1-input-modifier有关基于 Tag 的单实体 cookbook(使用 Tag 标记实体、通过 获取实体,并在 system 内使用 轮询每个实体),请参阅 → “Per-Entity Input Command Cookbook (Tag-based)”。
engine.getEntitiesByTaggetInputCommand{baseDir}/references/input-patterns.mdGlobal Input Checks
全局输入检查
Check if a specific key was pressed, regardless of if the player's cursor was pointing at an entity or not.
Use for one-shot actions (fire a weapon, open a door) — it returns true only on the frame the key is first pressed. Use for continuous actions (movement, holding a shield) — it returns true every frame while held.
isTriggered()isPressed()typescript
function globalInputSystem() {
// Was the key just pressed this frame?
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
console.log('E key pressed!')
}
// Is the key currently held down?
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
console.log('F key is held!')
}
}
engine.addSystem(globalInputSystem)无论玩家的光标是否指向实体,都可以检查特定按键是否被按下。
对于一次性操作(开火、开门),使用 ——它仅在按键首次按下的那一帧返回 true。对于持续操作(移动、举盾),使用 ——按键按住期间,它会在每一帧返回 true。
isTriggered()isPressed()typescript
function globalInputSystem() {
// Was the key just pressed this frame?
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
console.log('E key pressed!')
}
// Is the key currently held down?
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
console.log('F key is held!')
}
}
engine.addSystem(globalInputSystem)All InputAction Values
所有 InputAction 值
| InputAction | Key/Button |
|---|---|
| Left mouse button |
| E key |
| F key |
| 1 key |
| 2 key |
| 3 key |
| 4 key |
| Space key |
| W key |
| S key |
| A key |
| D key |
| Control key |
| Shift key (run) |
| Matches any input action (wildcard — use with |
| InputAction | 按键/按钮 |
|---|---|
| 鼠标左键 |
| E 键 |
| F 键 |
| 1 键 |
| 2 键 |
| 3 键 |
| 4 键 |
| Space 键 |
| W 键 |
| S 键 |
| A 键 |
| D 键 |
| Control 键 |
| Shift 键(奔跑) |
| 匹配任意输入操作(通配符——配合 |
Event Types
事件类型
typescript
PointerEventType.PET_DOWN // Button/key pressed
PointerEventType.PET_UP // Button/key released
PointerEventType.PET_HOVER_ENTER // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE // Cursor leaves entitytypescript
PointerEventType.PET_DOWN // Button/key pressed
PointerEventType.PET_UP // Button/key released
PointerEventType.PET_HOVER_ENTER // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE // Cursor leaves entityInputModifier (Movement Restriction)
InputModifier(移动限制)
Restrict or freeze the player's movement:
typescript
import { engine, InputModifier } from '@dcl/sdk/ecs'
// Freeze player completely
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true })
})
// Restrict specific movement (all flags optional; a false/omitted flag is ignored)
InputModifier.createOrReplace(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({
disableWalk: true,
disableJog: true,
disableRun: true,
disableJump: true,
disableEmote: true,
disableDoubleJump: true,
disableGliding: true
})
})
// Restore normal movement
InputModifier.deleteFrom(engine.PlayerEntity)Standard flags (all optional booleans; verified ): , , , , , , , . A /omitted flag is ignored (consumes no bandwidth). and the raw form are equivalent (both seen in test scenes).
input_modifier.gen.d.tsdisableAlldisableWalkdisableJogdisableRundisableJumpdisableEmotedisableDoubleJumpdisableGlidingfalseInputModifier.Mode.Standard({...}){ $case: 'standard', standard: {...} }Important: InputModifier only works in the DCL 2.0 desktop client. It has no effect in the web browser explorer — test with the desktop client if your scene relies on it.
限制或冻结玩家的移动:
typescript
import { engine, InputModifier } from '@dcl/sdk/ecs'
// Freeze player completely
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true })
})
// Restrict specific movement (all flags optional; a false/omitted flag is ignored)
InputModifier.createOrReplace(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({
disableWalk: true,
disableJog: true,
disableRun: true,
disableJump: true,
disableEmote: true,
disableDoubleJump: true,
disableGliding: true
})
})
// Restore normal movement
InputModifier.deleteFrom(engine.PlayerEntity)Standard 标志(均为可选 boolean;已验证 ):、、、、、、、。值为 或省略的标志会被忽略(不消耗带宽)。 与原始的 形式等价(两者都出现在测试场景中)。
input_modifier.gen.d.tsdisableAlldisableWalkdisableJogdisableRundisableJumpdisableEmotedisableDoubleJumpdisableGlidingfalseInputModifier.Mode.Standard({...}){ $case: 'standard', standard: {...} }重要: InputModifier 仅在 DCL 2.0 桌面客户端中有效,在 Web 浏览器 explorer 中不起作用。如果场景依赖此功能,请使用桌面客户端进行测试。
Cutscene Pattern
过场动画模式
For the worked cutscene flow (freeze the player with during a cinematic, then restore movement with ), see → "Cutscene Pattern (freeze player during a cinematic)".
disableAllInputModifier.deleteFrom{baseDir}/references/input-patterns.md有关完整的过场动画流程(在电影播放期间使用 冻结玩家,然后通过 恢复移动),请参阅 → “Cutscene Pattern (freeze player during a cinematic)”。
disableAllInputModifier.deleteFrom{baseDir}/references/input-patterns.mdWASD Movement Pattern
WASD 移动模式
For the WASD-driven custom-entity pattern (poll /// with to move a , plus the note on freezing the avatar with and how polling WASD relates to player movement), see → "WASD Movement Pattern (drive a custom entity)".
IA_FORWARDIA_BACKWARDIA_LEFTIA_RIGHTisPressedTransformInputModifier{baseDir}/references/input-patterns.md有关由 WASD 驱动的自定义实体模式(使用 轮询 /// 来移动 ,以及如何使用 冻结 avatar、WASD 轮询与玩家移动之间的关系),请参阅 → “WASD Movement Pattern (drive a custom entity)”。
isPressedIA_FORWARDIA_BACKWARDIA_LEFTIA_RIGHTTransformInputModifier{baseDir}/references/input-patterns.mdCombining Input Patterns
组合输入模式
For the action-bar / number-key pattern (map – to ability slots via ), see → "Action Bar with Number Keys".
IA_ACTION_3IA_ACTION_6isTriggered{baseDir}/references/input-patterns.md有关操作栏/数字键模式(使用 将 – 映射到技能槽位),请参阅 → “Action Bar with Number Keys”。
isTriggeredIA_ACTION_3IA_ACTION_6{baseDir}/references/input-patterns.mdPlatform detection
平台检测
Detect whether the scene is running on mobile to conditionally adapt controls and UI:
typescript
import { getPlatform, isMobile } from '@dcl/sdk/platform'
// getPlatform() returns 'mobile' | 'desktop' | 'web' | null
// Returns null until the explorer reports its platform (async, shortly after scene start)
// Defer platform-dependent setup until getPlatform() is non-null:
function platformCheckSystem() {
if (getPlatform() === null) return
engine.removeSystem(platformCheckSystem)
if (isMobile()) {
// mobile-specific setup here (e.g. larger UI, touch-friendly interactions)
}
}
engine.addSystem(platformCheckSystem)Import from . Verified against docs commit .
@dcl/sdk/platform17ca7be检测场景是否正在移动端运行,以便根据平台调整控制方式和 UI:
typescript
import { getPlatform, isMobile } from '@dcl/sdk/platform'
// getPlatform() returns 'mobile' | 'desktop' | 'web' | null
// Returns null until the explorer reports its platform (async, shortly after scene start)
// Defer platform-dependent setup until getPlatform() is non-null:
function platformCheckSystem() {
if (getPlatform() === null) return
engine.removeSystem(platformCheckSystem)
if (isMobile()) {
// mobile-specific setup here (e.g. larger UI, touch-friendly interactions)
}
}
engine.addSystem(platformCheckSystem)从 导入。已对照 docs commit 验证。
@dcl/sdk/platform17ca7beMobile considerations
移动端注意事项
Key facts from the mobile docs expansion (commit ):
17ca7be- Touch-only input -- no mouse hover states, keyboard shortcuts, or right-click.
- unsupported on mobile UI -- avoid rounded corners in mobile-targeting scenes.
borderRadius - Static HUD -- the mobile client has fixed on-screen controls (joystick, action buttons) that cannot be repositioned or customized from the scene.
- UI designed for desktop needs ~3x scaling for mobile readability.
- Use (from
ScreenInsetArea) to keep UI inside the device's safe area (notch, home indicator).@dcl/sdk/react-ecs - SDK features not yet on mobile: ParticleSystem, scene dynamic lights (PBPointLight), AudioAnalysis, nine-slice UI tile mode. Check the docs for the latest feature parity tracker.
来自移动端 docs 扩展内容的关键事实(commit ):
17ca7be- 仅支持触摸输入——没有鼠标悬停状态、键盘快捷键或右键操作。
- 移动端 UI 不支持 ——在面向移动端的场景中应避免使用圆角。
borderRadius - 静态 HUD——移动端客户端具有固定的屏幕控件(joystick、操作按钮),无法从场景中重新定位或自定义。
- 面向桌面端设计的 UI 在移动端通常需要约 3 倍缩放才能保证可读性。
- 使用 (来自
ScreenInsetArea),将 UI 保持在设备的安全区域内(刘海、home indicator)。@dcl/sdk/react-ecs - 移动端尚不支持的 SDK 功能: ParticleSystem、场景动态灯光(PBPointLight)、AudioAnalysis、九宫格 UI tile 模式。请查阅 docs 获取最新的功能支持情况跟踪表。
Example scenes
示例场景
Engine-team test scenes exercising these APIs (ground truth):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,1-input-modifier — InputModifier standard flags (incl. /
disableWalk),disableJogto read whichever key was pressed.getInputCommand(IA_ANY, PET_DOWN) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/31,20-pointer-lock-control — writing to request/release cursor capture;
PointerLock.isPointerLocked.PointerLock.onChange - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,5-primary-cursor-info — reading (screen coords/delta/worldRayDirection) each frame; feeding
PrimaryPointerInfointo a camera raycast.worldRayDirection - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/2,22-virtual-cameras — WASD-driven controllable camera via ; toggling InputModifier alongside a VirtualCamera.
isPressed(IA_FORWARD/...) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,0-cube-spawner — system-based per-entity click via +
getEntitiesWith(Cube, PointerEvents).inputSystem.isTriggered(IA_POINTER, PET_DOWN, entity) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/32,20-virtual-camera-mouse-look — driving mouselook camera while pointer locked; shows
PrimaryPointerInfo.screenDeltacontinuing to report raw mouse movement during lock (whilescreenDeltafreezes at screen center).screenCoordinates
Engine 团队用于验证这些 API 的测试场景(基准依据):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,1-input-modifier — Standard 标志(包括
InputModifier/disableWalk),以及使用disableJog读取具体被按下的按键。getInputCommand(IA_ANY, PET_DOWN) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/31,20-pointer-lock-control — 写入 以请求/解除光标捕获,以及使用
PointerLock.isPointerLocked。PointerLock.onChange - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,5-primary-cursor-info — 每帧读取 (屏幕坐标/增量/worldRayDirection),并将
PrimaryPointerInfo输入相机 raycast。worldRayDirection - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/2,22-virtual-cameras — 通过 实现由 WASD 驱动的可控相机,并在 VirtualCamera 旁切换 InputModifier。
isPressed(IA_FORWARD/...) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,0-cube-spawner — 通过 实现基于 system 的单实体点击。
getEntitiesWith(Cube, PointerEvents) + inputSystem.isTriggered(IA_POINTER, PET_DOWN, entity) - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/32,20-virtual-camera-mouse-look — 使用 在 Pointer Lock 期间驱动 mouselook 相机;展示了 Pointer Lock 期间
PrimaryPointerInfo.screenDelta会继续报告原始鼠标移动,而screenDelta会固定在屏幕中心。screenCoordinates
References
参考资料
- — branch-specific worked patterns: Tag-based per-entity input cookbook, cutscene freeze/restore flow, WASD-driven custom entity, action-bar number-key mapping.
{baseDir}/references/input-patterns.md
For basic pointer events and click handlers, see the skill.
add-interactivity- ——分支专用实例模式:基于 Tag 的单实体输入 cookbook、过场动画冻结/恢复流程、WASD 驱动的自定义实体、操作栏数字键映射。
{baseDir}/references/input-patterns.md
有关基础指针事件和点击处理器,请参阅 skill。
add-interactivity