-
Notifications
You must be signed in to change notification settings - Fork 30
All about collision
These are the key components that work together to cause collisions in a game:
- HandlerCollision typically resides at the topmost entity layer and manages detecting collisions for all entities containing any of the following components.
- CollisionGroup is connected to an entity and specifies that this entity has children who are also collidable entities.
- CollisionBasic is the key component to add to colliding entities and will re-position the entity during solid collisions and trigger events on the entity when a collision takes place.
- CollisionTiles handles a grid of tiles representing the level map. There is typically only a single entity in a collision group that has this component.
To get everything set up, start by adding a HandlerCollision
component to the topmost entity layer. This component listens for messages from HandlerLogic
, so make sure that this component is added as well. With this in mind, the topmost entity may take the following form:
{
"id": "action-layer",
"components": [
{"type": "HandlerController"},
{"type": "HandlerLogic"},
{"type": "HandlerCollision"},
{"type": "HandlerRender"},
{"type": "EntityContainer",
"entities": [{"type": "hero"}, {"type": "block"}, {"type": "exit-door"}]
}
]
}
Note that in addition to the components described above, the topmost layer also includes a HandlerController
so the "hero" can respond to input, HandlerRender
to render the entities, and an EntityContainer
with a few entities that will soon become collidable.
Now that the HandlerCollision
component is prepared, we can attach a collision component to our entities. Adding a CollisionBasic
component to our hero may look something like this:
{
"id": "hero",
"components":[{
"type": "EntityController"
},{
"type": "LogicDirectionalMovement"
},{
"type": "CollisionBasic",
"collisionType": "hero"
},{
"type": "RenderDebug"
}]
}
At a minimum, define the collisionType
of the entity as shown above. Defining this causes the entity to collide with other entities that are concerned about this collision type. We may define the collision type of the other two entities, "block" and "exit-door", as "block" and "door" respectively. The collision type does not need to match the id of the entity, but in some cases this may make it easier to keep track of collision types. It is also perfectly okay for multiple entities to share the same collision type if their collision behavior is likely to remain identical.
Now that collision types have been defined on the entities, we can define their behavior when they collide. There are two types of collisions: solid collisions which prevent the entities from occupying the same world space and soft collisions which allow the entities to overlap.
To set up a solid collision, we need to specify the solid collision on either of the two entities involved. In this example, we want the hero to collide with block entities. Additionally, we do not want block entities to overlap each other, so they should collide against other entities of their type as well. To do this, we list the solid collision types on the block entity. Note that this list is a list of collision types not a list of entity types. Adding this information will give us a block entity that looks something like this:
{
"id": "block",
"components":[{
"type": "LogicPushable"
},{
"type": "CollisionBasic",
"collisionType": "block",
"solidCollisions": {
"block": "hit-solid",
"hero": ["push-entity", "hit-solid"]
}
},{
"type": "RenderDebug"
}]
}
Noting the above syntax, the solidCollisions
list uses keys matching the various collision types as well as a string value or an Array of strings. The values indicate the messages that should be triggered on this entity when a collision has taken place. When a block hits another block a default "hit-by-block" message is triggered on both entities as well as the explicitly set "hit-solid" message. If an empty string is specified, only the default "hit-by-[collision type]' message is triggered. In this case, colliding with a hero entity causes a "push-entity" message to trigger on the block - a message that is received by the attached LogicPushable component to update the block's location.
Note that the default 'hit-by' collision messages are triggered on both entities regardless of where the collision is listed. If, for example, neither entity listed the other's collision type in its list of solid collisions, no events are triggered. On the other hand, if either of the objects list the other, messages are triggered on both.
In addition to messages being triggered, both entities will not overlap one another. If their logic components request that their new positions overlap, the CollisionBasic
component will move them into adjacent locations instead.
Soft collisions are defined in a similar way to solid collisions. Unlike solid collisions, soft collisions are one-way, so if both entities need to be aware of colliding with the other, the soft collision must be listed in both entities' definitions. In this example, we want the exit-door entity to be aware of the presence of the hero, so its definition may look like this:
{
"id": "exit-door",
"components":[{
"type": "LogicPortal"
},{
"type": "CollisionBasic",
"collisionType": "portal",
"immobile": true,
"softCollisions": {
"hero": "occupied-portal"
}
},{
"type": "SceneChanger"
}]
}
In this case the door list a single collision type "hero" and will trigger "occupied-portal" on the "exit-door" entity in addition to the default "hit-by-hero" message.
Also of note in this example is the "immobile" property being set to true. Setting this property to true for a CollisionBasic
component can greatly increase the performance of collision checks if the entity will never change locations.
The above covers all entity-on-entity collisions, but another key solid collision is the map itself. Fortunately most of this is handled by the TiledLoader
component when maps are created in Tiled. For an overview of creating Tiled maps, including collision layers (that become entities with a CollisionTiles
component), check out the Use-Tiled guide.
In order to keep collision processing quick, the HandlerCollision
only runs tests on the visible view port as determined by a Camera
component (see Setting-Up-The-Camera). If an entity should run collision tests even when outside the viewable area of the world, the entity should have an "alwaysOn" property set to true
as shown below:
{
"id": "hero",
"components":[{
"type": "EntityController"
},{
"type": "LogicDirectionalMovement"
},{
"type": "CollisionBasic",
"collisionType": "hero"
},{
"type": "RenderDebug"
}],
"properties":{
"alwaysOn": true,
}
}
This camera view port optimization is enabled by default, but another setting that will speed up collision is not: the CollisionBasic
"immobile" property. For example, using our example above with the block
entities, let's say that they're now immobile. To set this up, their definition will look something like this:
{
"id": "block",
"components":[{
"type": "CollisionBasic",
"collisionType": "block",
"immobile": true,
"solidCollisions": {
"block": "hit-solid",
"hero": "hit-solid"
}
},{
"type": "RenderDebug"
}]
}
This causes the collision test for this particular entity to run twice as fast, since it's stationary and doesn't need to check against other entities - it just needs to be checked against by moving entities.