- Created a collider component. This component holds a rectangular area that we will use to check for collisions. We need to attach this component to every object that we want to collide.
- Created the quadtree. This is a method of efficiently storing collidables to minimise the number of collision checks we need to perform.
With that done it’s on to the main event: the collision system. This will follow a similar structure to that of the drawing system. Before we write anything, let’s define what we want from our collision system:
- To be able to pass the collision system a vector of newly added objects and for the collision system to work out which objects have collidables and only include those in its system.
- When an object is removed from the game we need the collision system to remove it from its collection as well.
- Every frame we need to check if objects are colliding and if they are we want the objects collider components to resolve the collision.
For each collision layer we:
- Create a bitmask to store the collision data.
- We set the bit for each layer that this layer will collide with. Remember that we specified integer values for our CollisionLayer enum. We use those values to set that position in the bitmask if we want collisions with that layer. For example, we only want the default layer colliding with itself so we only set the bit at position 1 (the integer value we gave to the default layer in the CollisionLayer enum). We don’t want the tile layer colliding with any other layer so we set its bit mask to 0. And we want the player layer colliding with the default and tile layer so we set both of those bits.
- We then add this value to our collisionLayer map. We’ll read from this map when we are resolving collisions.
Before the system can call the Resolve function it needs to clear the quadtree and re-insert all the collidables. We have to do this because if the objects move around the game and transition from one quadtree node to another, we currently have no way of dynamically updating its node in the quadtree. In future we can look at writing dynamic node updates but for now this will do. Because the quadtree prevents many unnecessary collision checks I am not too concerned about the performance hit we take here. For more information on quadtrees see my previous tutorials.
- We loop over every collidable stored within this system.
- We pass the collidables area to the quadtree and retrieve a vector of objects that intersect with that area.
- For every object we are colliding with:
a. We check if the two layers collide. If not then we can skip resolution.
b. The collision is passed to the collidable component of the initial object. This performs an additional collision check and returns a manifold that contains the collision data. We need to perform another check here because as we resolve collisions and move objects around we may no longer be colliding with some objects in the vector.
c. If they are still colliding we pass the manifold to the collidable component of the initial object, which will resolve the collision by moving the object so it is no longer intersecting.
It’s also worth mentioning how we are reducing the number of collision checks we need to perform each frame:
- If a collision layer does not collide with any other layer (such as the tile layer) we do not perform checks on that layer.
- If an object is static we do not need to check if it is colliding with other objects. We’ll still check if other objects collide with the static object. For example, if the player walks up to a chest that is marked static, they can still collide/interact with the chest. We just don’t need to check every frame if the chest is colliding with anything else.
- If a layer does not collide with the layer of the other object based on its entry in collisionLayers then we do not perform any collision checks/resolutions.
- And of course, by using the quadtree we only retrieve a small subset of objects so we do not need to check if every object is colliding with every other object.
You may have noticed the ‘TODO’ near the end of the function. We currently have no satisfactory way of resolving collisions between two non-static objects. This is definitely something we want to change sooner rather than later but as our player will only be colliding with static objects (tiles) for the moment we can leave this as it is.