About input actions
A Decentraland scene can detect input actions from all of the buttons that are used to control the player’s avatar. These include pointer clicks, several action buttons, and the keys that are used to move the avatar around. Button events can come from a mouse, a touch screen, a VR controller or some other device, these are all interpreted the same by the SDK.
You can detect input actions against an entity. This involves pressing a button while the player’s cursor is pointing at that entity’s collider. You can also detect global input event, that involve pressing activating the input at any time, without consideration for where the pointer is aiming.
📔 Note: Entities must have a collider to respond to input actions.MeshRenderer
models must also be given aMeshCollider
component. Models from aGLTFContainer
may have their own embedded collision geometry, or they can be configured to use their visible geometry, they can also be given aMeshCollider
component.
There are several different ways to handle input actions, depending on the use case.
- Register a callback : The easiest way to add interaction to a single entity. Write a single statement to set up a callback function and hover feedback.
- System-based : Ideal for handling multiple entities with similar behavior. Use a system to iterate over similar entities and query for input actions on each, handling them all with the same logic. Hover feedback needs to be set up separately. This approach is also required for handling global input actions.
- Advanced : Read the raw response data on each entity, including time-stamps and an event history of input events. This can be useful for defining custom interaction patterns.
Use the Scene Editor #
The easiest way to handle click events on an entity is to use the Scene Editor . Use the no-code On Click or On Input Action Triggers on an item to call actions when clicking on it. Or use On Global Click, On Global Primary or On Global Secondary Triggers to react to global button events. See Make any item smart .
Simple example #
To detect clicks on an entity, use pointerEventsSystem.onPointerDown
.
pointerEventsSystem.onPointerDown(
{
entity: myEntity,
opts: { button: InputAction.IA_PRIMARY, hoverText: 'Click' },
},
function () {
console.log('clicked entity')
}
)
See Register a callback for more information.
Hover Feedback #
It’s important to make players aware that an entity is interactive. Otherwise, they might completely miss out on the experience you built. It’s not a good experience to be clicking on every object hoping for one to respond.
When you use the Register a callback method, two kinds of feedback are displayed whenever the player passes their cursor over the object:
- The entity’s edge is highlighted. The highlight is green if the entity is close enough to click, red if the entity is too far away.
- A hover hint appears near the cursor with UI text, signalling what will happen if they click.
When using the
System-based
method, you can achieve the same results by adding a PointerEvents
component to the clickable entities.
Both the entity highlight and the hover hint can be disabled via properties in these methods and components.
You could also implement advanced custom hints , for example you could play a sound, making the entity change color, spin or enlarge while being pointed at, etc. Whatever you do, make sure that it’s a clear signifier.
Obstacles #
Button events cast rays that only interact with the first entity on their path that is subscribed to the pointer events collision layer, as long as the entity is closer than its distance limit.
For an entity to be intercepted by the ray of a pointer event, either:
- The model must contain collider meshes .
- The
GLTFContainer
must be configured to use the visible geometry with collision masks . - The entity must have a MeshCollider component .
If another entity’s collider is standing on the way of the entity that the player wants to interact with it, the player won’t be able to click the entity that’s behind, unless the entity has no collider, or this collider is configured to not respond to the pointer events collision layer.
// clickable entity
const clickableEntity = engine.addEntity()
MeshRenderer.setBox(clickableEntity)
MeshCollider.setBox(clickableEntity)
Transform.create(clickableEntity, { position: Vector3.create(8, 1, 8) })
pointerEventsSystem.onPointerDown(
{
entity: clickableEntity,
opts: {
button: InputAction.IA_POINTER,
hoverText: 'Click',
},
},
function () {
console.log('clicked entity')
const t = Transform.getMutable(clickableEntity)
t.scale.y += 0.2
}
)
// non-blocker for clicks
const nonBlocker = engine.addEntity()
MeshRenderer.setBox(nonBlocker)
MeshCollider.setBox(nonBlocker, ColliderLayer.CL_PHYSICS)
Transform.create(nonBlocker, { position: Vector3.create(10, 1, 8) })
// blocker for clicks
const blocker = engine.addEntity()
MeshRenderer.setBox(blocker)
MeshCollider.setBox(blocker, ColliderLayer.CL_POINTER)
Transform.create(blocker, { position: Vector3.create(8, 1, 10) })
📔 Note: For an entity to not only intercept a pointer event, but also to return data, the entity also needs to have aPointerEvents
component. ThepointerEventsSystem
helpers also take care of this requirment.
Pointer buttons #
The following inputs can be handled by any of the approaches to detect input events.
InputAction.IA_POINTER
: left-mouse button on a computer.InputAction.IA_PRIMARY
: E key on a computer.InputAction.IA_SECONDARY
: F key on a computer.InputAction.IA_ACTION_3
: 1 key on a computer.InputAction.IA_ACTION_4
: 2 key on a computer.InputAction.IA_ACTION_5
: 3 key on a computer.InputAction.IA_ACTION_6
: 4 key on a computer.InputAction.IA_JUMP
: Space key on a computer.InputAction.IA_FORWARD
: W key on a computer.InputAction.IA_LEFT
: A key on a computer.InputAction.IA_RIGHT
: D key on a computer.InputAction.IA_BACKWARD
: S key on a computer.InputAction.IA_WALK
: Shift key on a computer.
Each InputAction
is abstracted away from the literal input in the keyboard so that it can be mapped to different inputs depending on the device. For this same reason, not all buttons on the keyboard can be tracked for button events, only the buttons that are used for movement and interaction. This intentional limitation is to ensure that all content is compatible in the future with VR controllers, other kinds of game controllers, and mobile devices.
Types of pointer events #
Each input can produce the following types of pointer events. Each of the following is a value in the PointerEventType
enum.
DOWN
: Player pushes down a specific button while having the cursor pointing at the entity’s collider.UP
: Player releases a specific button while having the cursor pointing at the entity’s collider.HOVER_ENTER
: Player’s cursor starts pointing at the entity’s collider.HOVER_LEAVE
: Player’s cursor stops pointing at the entity’s collider.
Data from an input action #
All input actions include data about the event, including things like the button that was activated, and where the pointer was pointing at at the time.
The following information can be obtained from any input event:
-
analog
: Flag to mark if the event is from an analog or a digital input. Digital inputs have a value of 1, analog inputs (like a joy stick) have a value of 0. -
button
: Which button id was pressed. The number corresponds to theInputAction
enum, that lists all of the available buttons. -
state
: Type of pointer event, from the enumPointerEventType
. 0 refers toPointerEventType.PET_DOWN
, 1 toPointerEventType.PET_UP
, 2 toPointerEventType.PET_HOVER_ENTER
, 3 toPointerEventType.PET_HOVER_LEAVE
-
timestamp
: A lamport timestamp to identify each button event.Note: This timestamp is not numbered based on the current time. Think of it as a counter that starts at 0 and is incremented by 1 for each event.
-
hit
: An object that contains the following data about the hit event:entityId
: Id number of the entity that was hit by the ray.meshName
: String with the internal name of the specific mesh in the 3D model that was hit. This is useful when a 3D model is composed of multiple meshes.origin
: Vector3 for the position where the ray originates (relative to the scene)position
: Vector3 for the position where the ray intersected with the hit entity (relative to the scene)length
: Length of the ray from its origin to the position where the hit against the entity occurred.normalHit
: Quaternion for the angle of the normal of the hit in world space.
This data is accessed in different ways depending on what approach you’re using to handle input actions.
Using the Register a callback approach, the first parameter passed to the callback function contains this entire data structure.
pointerEventsSystem.onPointerDown({ entity: myEntity }, function (cmd) {
console.log(cmd.hit.entityId)
})
Using the
System-based
approach, use inputSystem.getInputCommand()
to fetch this data.
engine.addSystem(() => {
const cmd = inputSystem.getInputCommand(
InputAction.IA_POINTER,
PointerEventType.PET_DOWN,
myEntity
)
if (cmd) {
console.log(cmd.hit.entityId)
}
})
📔 Note: For an entity to not only intercept a pointer event, but also to return data, the entity also needs to have aPointerEvents
component. ThepointerEventsSystem
helpers also take care of this requirment.
Using the
Advanced
approach, the PointerEventsResults
contains an array with a recent history of all pointer events against that entity.
engine.addSystem(() => {
const pointerEvents = engine.getEntitiesWith(PointerEventsResult)
for (const [entity] of pointerEvents) {
const poninterEvents = PointerEventsResult.get(entity)
if (poninterEvents.commands.length > 0) {
console.log(poninterEvents.commands[0].hit.entityId)
}
}
})