Skip to content
This repository has been archived by the owner on Apr 15, 2022. It is now read-only.

All about collision

Derek Detweiler edited this page Aug 16, 2017 · 4 revisions

Getting Started

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.

Colliding Entities

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.

Solid Collisions

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

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.

Handling the Tile Map

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.

Efficient Collision Processing

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.