Colliders
Entities that have colliders occupy space and block a player’s path, entities without colliders can be walked through by a player`s avatar.
Colliders are also needed to make an entity clickable. Button events are based on the collider shape of an entity, not on its visible shape.
There are separate collision layers for interacting with either the player’s physics, or with pointer events, colliders can be configured to only interact with one or the other. They can also be configured to interact with custom layers, that can be used with raycasts to handle whatever makes sense to the scene.
π Note: Colliders don’t affect how other entities interact with each other, entities can always overlap. Collision settings only affect how the entity interacts with the player’s avatar and button events. Decentraland doesn’t have a native physics engine, so if you want entities to fall, crash or bounce, you must code this behavior into the scene, or import a library to handle that.
Use the Scene Editor #
The easiest way to manage an entity’s colliders is to use the Scene Editor .
You can add a Mesh Collider component to your entity to assign a primitive shape (cube, plain, sphere, cylinder, or cone) to your entity. You can then pick Collision layers from a dropdown.
You can also configure the collision layers on a GLTF component to change the default Collision layers used on either the collider geometry or the visible geometry of the model. See Add Components .
Colliders on primitive shapes #
The MeshCollider
component gives an entity a simple collider based on a primitive shape (boxes, spheres, planes, cylinders, or cones).
Entities that have a MeshRenderer
component to give them a
primitive shape
don’t have colliders by default. You must also give the entity a MeshCollider
component.
The following collider shapes are available on MeshCollider
. Several shapes include optional additional fields, specific to that shape.
-
box:
Use
MeshRenderer.setBox()
, passing the entity. -
plane:
Use
MeshRenderer.setPlane()
, passing the entity. -
sphere:
Use
MeshRenderer.setSphere()
, passing the entity. -
cylinder:
Use
MeshRenderer.setCylinder()
, passing the entity. PassradiusTop
andradiusBottom
as additional optional fields, to modify the cylinder.π‘ Tip:: Set either
radiusTop
orradiusBottom
to 0 to make a cone.
This example defines a box entity that can’t be walked through.
// create entity
const myCollider = engine.addEntity()
// visible shape
MeshRenderer.setBox(myCollider)
// collider
MeshCollider.setBox(myCollider)
The shape used by the MeshCollider
doesn’t need to necessarily match the one used by the MeshRenderer
. You can also add a MeshCollider
to an entity that has a 3D model from a GLTFContainer
component, or to an entity that has no visible shape at all.
π Note: The
MeshCollider
component andColliderLayer
must be imported via
import { MeshCollider, ColliderLayer } from "@dcl/sdk/ecs"
See Imports for how to handle these easily.
Colliders on 3D models #
3D models can be assigned colliders on two different geometry levels:
visibleMeshesCollisionMask
: Refers to the visible geometry of the model. By default this geometry has no colliders.invisibleMeshesCollisionMask
: refers to the collider meshes, whose name end in_collider
. By default, this geometry is treated as a collider for both physics and pointer events.
Any mesh embedded as part of a 3D model who’s name ends in _collider
is treated as part of the invisibleMeshesCollisionMask
layer, and interpreted as a collider by default.
Defining collider geometry as a separate invisible layer allows for much greater control and is a lot less demanding on the system than using the visible geometry, as the collision object is usually a lot simpler (with less vertices) than the original model.
If a model doesn’t have any collider geometry, and you want to make it affect the physics or the pointer events systems, you can either:
- Assign collision layers directly to the visible geometry, via the
visibleMeshesCollisionMask
.π Note: If the visible geometry of the object has many vertices, note that this may have more of a performance cost.
- Give the entity a
MeshCollider
component, to give it a primitive shape collider. - Overlay an invisible entity that has a
MeshCollider
component. - Edit the model in an external tool like Blender to include a collider mesh. The collider must be named x_collider, where x is the name of the model. So for a model named house, the collider must be named house_collider.
You might also want to assign the pointer events collision layer to the visibleMeshesCollisionMask
in case you want the hover hints and pointer events to respond more accurately to the contour of the entity. Note that this is more demanding on performance.
π Note: Make sure you don’t have the same layer (physics, pointer events or custom layers) assigned to bothvisibleMeshesCollisionMask
andinvisibleMeshesCollisionMask
, as that would be a very inefficient use of resources. You can have different layers on each, such as physics on the invisible layer and pointer events on the visible layer.
// create entity
const myEntity = engine.addEntity()
// assign GLTF shape
GltfContainer.create(myEntity, {
src: '/models/myModel.gltf',
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS,
visibleMeshesCollisionMask: ColliderLayer.CL_POINTER,
})
See 3D models for more details on how to add collider invisible geometry to a 3D model.
π Note: The
GltfContainer
component andColliderLayer
must be imported via
import { GltfContainer, ColliderLayer } from "@dcl/sdk/ecs"
See Imports for how to handle these easily.
Animated models #
When setting colliders to use the visible geometry on a model that includes armature-based animations , the animations aren’t followed by colliders. The collider meshes keep their original shape. If an animation involves deforming a meshe’s geometry, the collider meshes retain the un-animated shape while the animation plays.
When playing animations that involve moving full meshes without changing their shape, these changes are accurately reflected by colliders. For example if a platform moves as part of an animation, the platformΒ΄s collider does move with the animation.
Collision layers #
The scene can handle separate collision layers, that have different behaviors.
You can configure a MeshCollider
component or the GltfContainer
component to only respond to one kind of interaction, or to several of them, or none. To do this, on the MeshCollider
set the collisionMask
property, and on GltfContainer
set the visibleMeshesCollisionMask
or invisibleMeshesCollisionMask
properties to one or several of the following values:
ColliderLayer.CL_PHYSICS
: Only blocks player movement (and doesn’t affect pointer events)ColliderLayer.CL_POINTER
: Responds only to pointer events (and doesn’t block the player movement)ColliderLayer.CL_CUSTOM1
through toCL_CUSTOM8
: Can be used together with raycasts, so that a ray only detects collisions with one specific layer.ColliderLayer.CL_NONE
: Doesn’t respond to collisions of any kind.
π Note: To disable collisions form aMeshCollider
component, delete the component. Do not set the collision layer toColliderLayer.CL_NONE
. There’s a known issue with theMeshCollider
component. Instead of disabling all collisions, it makes this value equivalent to the default (ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER
).
// create entity
const myEntity = engine.addEntity()
// visible shape
MeshRenderer.setBox(myEntity)
// create a MeshCollider component that only responds to player physics
MeshCollider.setBox(myEntity, ColliderLayer.CL_PHYSICS)
A single collision mask can respond to multiple collision layers. Use the |
character as an or, to include as many layers as you need. The default value on a MeshCollider is ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER
.
MeshCollider.setBox(
myEntity,
ColliderLayer.CL_CUSTOM1 |
ColliderLayer.CL_CUSTOM3 |
ColliderLayer.CL_PHYSICS |
ColliderLayer.CL_POINTER
)
You can use the 8 different custom layers for whatever suits your scene best, for example one could be used for NPC line-of-sight calculations, whilst another for estimating trajectories of falling objects. Using different layers for different systems allows you to use less resources, as in each case you’ll only be checking collisions with the relevant entities.
See Raycasting for more on how to use custom collision layers.
Pointer blocking #
Only shapes that have colliders can be activated with
pointer events
. An entity also needs to have a collider to block pointer events from going through it and prevent hitting entities behind it. So for example, a player can’t pick something up that is locked inside a chest, if the chest has colliders around it. The player’s pointer events are only affected by meshes that are active in the ColliderLayer.CL_POINTER
layer.
By default, a MeshCollider affects both the Physics and the Pointer layers, but you can change this value to only affect one, or neither, and to affect custom layers instead.
π Note: Besides colliders, an entity also needs to have aPointerEvents
component to respond to pointer events. ThepointerEventsSystem
helpers also take care of this requirment.
// only responds to player physics
// for example for an invisible wall that you can't walk through but you can click through
MeshCollider.setBox(myEntity, ColliderLayer.CL_PHYSICS)
// only responds to the player's pointer
// for example for example for an item you can click to pick up, but can walk right through
MeshCollider.setBox(myEntity2, ColliderLayer.CL_POINTER)
By default, the visible geometry of a GLTFContainer
isn’t mapped to any collision layers, but the invisible geometry affects both the Physics and the Pointer layers. You can change this value to only affect one, or neither, and to affect custom layers instead. You can also configure the visible geometry layer in the same way.
// default: both player physics and pointer events use the simpler invisible geometry
GLTFContainer.create(myEntity, {
src: '/models/myModel.gltf',
})
// player physics uses the simpler invisible geometry
// pointer events use the full detailed contour of the visible geometry
GltfContainer.create(myEntity2, {
src: '/models/myModel.gltf',
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS,
visibleMeshesCollisionMask: ColliderLayer.CL_POINTER,
})
// both player physics and pointer events use the full detailed contour of the visible geometry
// the simpler invisible geometry is mapped to ColliderLayer.CL_NONE to avoid calculating both
GltfContainer.create(myEntity, {
src: '/models/myModel.gltf',
invisibleMeshesCollisionMask: ColliderLayer.CL_NONE,
visibleMeshesCollisionMask:
ColliderLayer.CL_POINTER | ColliderLayer.CL_PHYSICS,
})
// don't respond to collisions of any kind, with either the visible or the invisible geometry:
GltfContainer.create(myEntity, {
src: '/models/myModel.gltf',
invisibleMeshesCollisionMask: ColliderLayer.CL_NONE,
})
Advanced MeshCollider Syntax #
The complete syntax for creating a MeshCollider
component, without any helpers to simplify it, looks like this:
MeshCollider.create(myBox, {
mesh: {
$case: 'box',
box: {},
},
})
MeshCollider.create(myPlane, {
mesh: {
$case: 'plane',
plane: {},
},
})
MeshCollider.create(myShpere, {
mesh: {
$case: 'sphere',
sphere: {},
},
})
MeshCollider.create(myCylinder, {
mesh: {
$case: 'cylinder',
cylinder: {},
},
})
This is how the base protocol interprets MeshCollider components. The helper functions abstract away from this and expose a friendlier syntax, but behind the scenes they output this syntax.
The $case
field allows you to specify one of the allowed types. Each type supports a different set of parameters.
The supported values for $case
are the following:
box
plane
sphere
cylinder
Depending on the value of $case
, it’s valid to define the object for the corresponding shape, passing any relevant properties.