| name | advanced-input |
| description | Advanced input handling in Decentraland. PointerLock (cursor capture state), InputModifier (freeze/restrict player movement), PrimaryPointerInfo (cursor position and world ray), WASD keyboard patterns, and action bar slots. Use when the user wants movement restriction, cursor control, FPS controls, input polling, or cutscene freezing. Do NOT use for basic click/hover events on entities (see add-interactivity). |
Advanced Input Handling in Decentraland
For basic click/hover events, see the add-interactivity skill. This skill covers advanced input patterns.
Pointer Lock State
Detect whether the cursor is captured (first-person mode) or free:
import { engine, PointerLock } from '@dcl/sdk/ecs'
function checkPointerLock() {
const isLocked = PointerLock.get(engine.CameraEntity).isPointerLocked
if (isLocked) {
} else {
}
}
engine.addSystem(checkPointerLock)
Pointer Lock Change Detection
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:
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'
function readPointer() {
const pointerInfo = PrimaryPointerInfo.get(engine.RootEntity)
console.log('Cursor position:', pointerInfo.screenCoordinates)
console.log('Cursor delta:', pointerInfo.screenDelta)
console.log('World ray direction:', pointerInfo.worldRayDirection)
}
engine.addSystem(readPointer)
Input Polling with inputSystem
Per-Entity Input Commands
Check if a specific input action occurred on a specific entity:
import { engine, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
function myInputSystem() {
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)
Global Input Checks
Fires regardless of whether the player's cursor was over an entity.
function globalInputSystem() {
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
console.log('E key pressed!')
}
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
console.log('F key is held!')
}
}
engine.addSystem(globalInputSystem)
Tag-Based Input Batching
If you have many similar entities that all respond to the same input (e.g., every barrel responds to E to break), tag them via the Tag component and iterate the tag query each frame:
import { engine, Tag, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
for (const barrel of [b1, b2, b3]) {
Tag.createOrReplace(barrel, { value: 'breakable' })
}
engine.addSystem(() => {
for (const [entity] of engine.getEntitiesByTag('breakable')) {
const cmd = inputSystem.getInputCommand(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN, entity)
if (cmd) {
engine.removeEntity(entity)
}
}
})
Cleaner than registering N individual pointerEventsSystem.onPointerDown handlers when the behavior is uniform.
All InputAction Values
| InputAction | Key/Button |
|---|
IA_POINTER | Left mouse button |
IA_PRIMARY | E key |
IA_SECONDARY | F key |
IA_ACTION_3 | 1 key |
IA_ACTION_4 | 2 key |
IA_ACTION_5 | 3 key |
IA_ACTION_6 | 4 key |
IA_JUMP | Space key |
IA_FORWARD | W key |
IA_BACKWARD | S key |
IA_LEFT | A key |
IA_RIGHT | D key |
IA_WALK | Shift key |
Event Types
PointerEventType.PET_DOWN
PointerEventType.PET_UP
PointerEventType.PET_HOVER_ENTER
PointerEventType.PET_HOVER_LEAVE
InputModifier (Movement Restriction)
Restrict or freeze the player's movement:
import { engine, InputModifier } from '@dcl/sdk/ecs'
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true })
})
InputModifier.createOrReplace(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({
disableWalk: false,
disableJog: false,
disableRun: true,
disableJump: true,
disableDoubleJump: true,
disableGliding: true,
disableEmote: true
})
})
InputModifier.deleteFrom(engine.PlayerEntity)
While restricted: gravity still applies, the camera still rotates, and pointer / proximity events still fire. All restrictions auto-lift when the player leaves the scene.
Important: InputModifier only works in the DCL 2.0 desktop client. It has no effect in the web browser explorer.
Cutscene Pattern
Freeze the player during a cinematic sequence:
function startCutscene() {
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true })
})
}
WASD Movement Pattern
Poll movement keys to control custom entities:
import { engine, inputSystem, InputAction, PointerEventType, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const MOVE_SPEED = 5
function customMovementSystem(dt: number) {
const transform = Transform.getMutable(controllableEntity)
let moveX = 0
let moveZ = 0
if (inputSystem.isPressed(InputAction.IA_FORWARD)) moveZ += 1
if (inputSystem.isPressed(InputAction.IA_BACKWARD)) moveZ -= 1
if (inputSystem.isPressed(InputAction.IA_LEFT)) moveX -= 1
if (inputSystem.isPressed(InputAction.IA_RIGHT)) moveX += 1
transform.position.x += moveX * MOVE_SPEED * dt
transform.position.z += moveZ * MOVE_SPEED * dt
}
engine.addSystem(customMovementSystem)
Combining Input Patterns
Action Bar with Number Keys
function actionBarSystem() {
if (inputSystem.isTriggered(InputAction.IA_ACTION_3, PointerEventType.PET_DOWN)) {
console.log('Slot 1 activated')
useAbility(1)
}
if (inputSystem.isTriggered(InputAction.IA_ACTION_4, PointerEventType.PET_DOWN)) {
console.log('Slot 2 activated')
useAbility(2)
}
if (inputSystem.isTriggered(InputAction.IA_ACTION_5, PointerEventType.PET_DOWN)) {
console.log('Slot 3 activated')
useAbility(3)
}
if (inputSystem.isTriggered(InputAction.IA_ACTION_6, PointerEventType.PET_DOWN)) {
console.log('Slot 4 activated')
useAbility(4)
}
}
engine.addSystem(actionBarSystem)
Best Practices
- Use
isTriggered() for one-shot actions (fire weapon, open door) — it returns true only on the frame the key is first pressed
- Use
isPressed() for continuous actions (movement, holding a shield) — it returns true every frame while held
getInputCommand() gives hit data (position, entity) — use it when you need to know what was clicked
- Prefer
pointerEventsSystem.onPointerDown() for simple entity clicks — use inputSystem for complex multi-key or polling patterns
- InputModifier only works in the DCL 2.0 desktop client — test with the desktop client if your scene relies on it
- WASD keys (
IA_FORWARD, etc.) also control player movement — polling them reads the movement state but doesn't override it
For basic pointer events and click handlers, see the add-interactivity skill.