dcl-regenesislabs/opendcl

advanced-input

Advanced input handling in Decentraland.

First seen Feb 25, 2026

Installation

$ npx skills add dcl-regenesislabs/opendcl --skill advanced-input

Summary

  • 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).

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from dcl-regenesislabs/opendcl · top by installs.

npx skills add dcl-regenesislabs/opendcl

Browse all from dcl-regenesislabs/opendcl

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 17
License Apache-2.0
Default branch main
Open issues 2
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,160 B
  • docs SUMMARY.md 435 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 37 installs

SKILL.md

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) {
    // Cursor is captured — player is in first-person control
  } else {
    // Cursor is free — player can click UI elements
  }
}

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() {
  // 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)

Global Input Checks

Fires regardless of whether the player's cursor was over an entity.

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)

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'

// At setup: tag the entities (or declare Tag in main-entities.ts).
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) {
      // entity was the IA_PRIMARY target this frame
      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
IAACTION3 1 key
IAACTION4 2 key
IAACTION5 3 key
IAACTION6 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         // Button/key pressed
PointerEventType.PET_UP           // Button/key released
PointerEventType.PET_HOVER_ENTER  // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE  // Cursor leaves entity

InputModifier (Movement Restriction)

Restrict or freeze the player's movement:

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

// Freeze player completely
InputModifier.create(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({ disableAll: true })
})

// Restrict specific movement (every flag is optional and defaults to false)
InputModifier.createOrReplace(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({
    disableWalk: false,
    disableJog: false,
    disableRun: true,
    disableJump: true,
    disableDoubleJump: true,
    disableGliding: true,
    disableEmote: true
  })
})

// Restore normal movement
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() {
  // Freeze player
  InputModifier.create(engine.PlayerEntity, {
    mode: InputModifier.Mode.Standard({ disableAll: true })
  })

  // ... play cinematic with VirtualCamera ...

  // After cutscene ends, restore movement
  // InputModifier.deleteFrom(engine.PlayerEntity)
}

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.