Loading...
Loading...
Event-driven interactivity for Decentraland entities. Covers pointerEventsSystem, proximity events, trigger areas (enter/exit zones), raycasting, and one-shot key presses on entities. Use when the user wants clickable objects, hover highlights, proximity-based interactions, detecting when a player enters a zone, E/F key actions on an entity, or ray-hit detection. For system-level polling (held keys, WASD movement, cursor lock, InputModifier, action bar) see advanced-input. For screen-space UI buttons see build-ui.
npx skill4agent add decentraland/sdk-skills add-interactivityassets/scene/main.compositeindex.tsengine.addEntity()import { engine, pointerEventsSystem, InputAction } from "@dcl/sdk/ecs";
import { EntityNames } from "../assets/scene/entity-names";
export function main() {
// By name (type-safe via auto-generated EntityNames enum)
const door = engine.getEntityOrNullByName(EntityNames.Door_1);
if (door) {
pointerEventsSystem.onPointerDown(
{
entity: door,
opts: { button: InputAction.IA_PRIMARY, hoverText: "Open" },
},
() => {
/* open door */
}
);
}
// By tag (batch operations on groups of composite entities)
const crystals = engine.getEntitiesByTag("Crystal");
for (const crystal of crystals) {
pointerEventsSystem.onPointerDown(
{
entity: crystal,
opts: { button: InputAction.IA_PRIMARY, hoverText: "Collect" },
},
() => {
/* collect crystal */
}
);
}
}main()main()| Need | Approach | API |
|---|---|---|
| Click/hover on a specific entity | Pointer events | |
| Button press when player is nearby (no aiming needed) | Proximity events | |
| Detect player entering an area | Trigger area | |
| Poll key state every frame | Global input | |
| Detect objects in a direction | Raycasting | |
| Read cursor position / lock state | Cursor state | |
pointerEventsSystem.onPointerDown().onPointerUp().onPointerHoverEnter().onPointerHoverLeave()maxDistancehoverText.removeOnPointerDown(entity)showFeedbackhoverText30,20-pointer-events-feedbackshowFeedbacktruehoverTextshowFeedbackshowFeedback: falsehoverTextshowHighlighttrueshowFeedbackshowFeedback: truehoverTextonPointerDownremoveOnPointerDown@dcl/ecs/dist/systems/events.jsEventSystemMaponPointerDownremoveEvent(entity, EventType.Down)getEvent(entity).set(EventType.Down, …)DownMapMapfor…ofinputSystem.getInputCommand(...)PointerEventsimport { PointerEvents } from '@dcl/sdk/ecs'
function setHoverText(entity: Entity, hoverText: string) {
const pe = PointerEvents.getMutableOrNull(entity)
if (!pe) return
for (const entry of pe.pointerEvents) {
if (entry.eventInfo) entry.eventInfo.hoverText = hoverText
}
}ColliderLayer.CL_POINTERMeshCollider.setBox(entity)visibleMeshesCollisionMask: ColliderLayer.CL_POINTERGltfContainerGltfContainer@dcl/ecsgltf_container.gen.d.tsvisibleMeshesCollisionMask0invisibleMeshesCollisionMaskCL_POINTER | CL_PHYSICS*_collider| Model | To make it clickable |
|---|---|
Has a | Already on |
Static mesh, NO | Set |
Skinned / armature-rigged (NPCs, characters, ghosts), NO | |
visibleMeshesCollisionMask_collidervisibleMeshesCollisionMask: ColliderLayer.CL_POINTERGLTFShapeCL_POINTERimport { engine, MeshCollider, ColliderLayer, pointerEventsSystem, InputAction, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
// `npc` is the rigged GLB entity (with GltfContainer + Animator).
const clicker = engine.addEntity()
// scale to the avatar bounds; parent so it follows the NPC as it moves
Transform.create(clicker, { position: Vector3.create(0, 0.45, 0), scale: Vector3.create(2, 2.9, 2), parent: npc })
MeshCollider.setBox(clicker, ColliderLayer.CL_POINTER)
pointerEventsSystem.onPointerDown(
{ entity: clicker, opts: { button: InputAction.IA_PRIMARY, hoverText: 'Talk', showFeedback: true } },
() => { /* handle click */ }
)GltfContainervisibleMeshesCollisionMask: 0InputAction.IA_POINTER; // Left mouse button
InputAction.IA_PRIMARY; // E key
InputAction.IA_SECONDARY; // F key
InputAction.IA_ACTION_3; // 1 key
InputAction.IA_ACTION_4; // 2 key
InputAction.IA_ACTION_5; // 3 key
InputAction.IA_ACTION_6; // 4 key
InputAction.IA_JUMP; // Space key
InputAction.IA_FORWARD; // W key
InputAction.IA_BACKWARD; // S key
InputAction.IA_LEFT; // A key
InputAction.IA_RIGHT; // D key
InputAction.IA_WALK; // Control key
InputAction.IA_MODIFIER; // Shift keyPointerEventType.PET_DOWN; // Button pressed
PointerEventType.PET_UP; // Button released
PointerEventType.PET_HOVER_ENTER; // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE; // Cursor leaves entity
PointerEventType.PET_PROXIMITY_ENTER; // Player walks within entity's proximity range
PointerEventType.PET_PROXIMITY_LEAVE; // Player moves out of entity's proximity rangepointerEventsSystem.onProximityDown().onProximityUp()maxPlayerDistance.onProximityEnter().onProximityLeave()priority.removeOnProximityDown(entity)buttonmaxDistancemaxPlayerDistancehoverTextshowHighlighttrueshowFeedbacktruepriorityInteractionType.PROXIMITYPointerEventsinputSystem.isTriggered()@dcl-sdk/utilstriggers.addTrigger()TriggerAreaTransformTransform.scalescale.ximport {
engine,
Transform,
TriggerArea,
triggerAreaEventsSystem,
ColliderLayer
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const zone = engine.addEntity()
Transform.create(zone, {
position: Vector3.create(8, 1, 8),
scale: Vector3.create(4, 2, 4) // 4m × 2m × 4m box
})
TriggerArea.setBox(zone, ColliderLayer.CL_PLAYER)
triggerAreaEventsSystem.onTriggerEnter(zone, (result) => {
if (result.trigger?.entity !== engine.PlayerEntity) return // local player only
console.log('player entered')
})
triggerAreaEventsSystem.onTriggerExit(zone, () => {
console.log('player left')
})TriggerArea.setSphere(entity, ColliderLayer.CL_PLAYER)Transform.scalescale.xCL_PLAYERColliderLayerCL_PLAYER | CL_CUSTOM4TriggerAreaMeshColliderGltfContainer75,-9-trigger-areasGltfContainervisibleMeshesCollisionMask: ColliderLayer.CL_CUSTOM4TriggerArea.setBox(entity, ColliderLayer.CL_PLAYER | ColliderLayer.CL_CUSTOM4)TriggerAreatriggerAreaEventsSystem.onTriggerEnter(entity, cb)triggerAreaEventsSystem.onTriggerStay(entity, cb)triggerAreaEventsSystem.onTriggerExit(entity, cb)removeOnTriggerEnter/Stay/Exit(entity)PBTriggerAreaResultresult.trigger?.entityengine.PlayerEntityresult.triggeredEntitytriggeredEntity{baseDir}/references/input-reference.md#trigger-area-callback-fieldsCL_PLAYERif (result.trigger?.entity !== engine.PlayerEntity) returnTriggerAreaTriggerAreaResultTriggerAreaResultraycastSystem.registerLocalDirectionRaycast().registerGlobalDirectionRaycast().registerGlobalTargetRaycast().registerTargetEntityRaycast().removeRaycasterEntity()RaycastRaycastResultcontinuous: falsetrue77,-1-raycast-unit-testsdirection: { $case: 'localDirection'|'globalDirection'|'globalTarget'|'targetEntity', ... }originOffsetmaxDistancequeryTypecontinuoustimestampRaycastResultglobalOrigindirectionhits[]timestamptickNumberoriginOffsetlocalDirectionglobalDirectionRaycastQueryType.RQT_HIT_FIRSTRQT_QUERY_ALLRQT_NONEengine.CameraEntityPrimaryPointerInfo.worldRayDirectioninputSystem.isTriggered()inputSystem.isPressed()engine.addSystem()inputSystem.getInputCommand()PointerLock.get(engine.CameraEntity).isPointerLockedPrimaryPointerInfo.get(engine.RootEntity)onProximityDown/Enter/LeavePointerEventsshowFeedbackshowHighlighthoverTextshowFeedback: trueTriggerArea.setBox/setSphereonTriggerEnter/Stay/Exitresult.trigger?.entityPointerEventsinputSystem.isTriggered(IA_POINTER, PET_DOWN, entity)RaycastRaycastResult{baseDir}/references/interactivity-patterns.md{baseDir}/references/input-reference.md