This week we are continuing where we left off by editing the collision system to call the OnCollisionEnter, OnCollisionStay, and OnCollisionExit functions that we wrote last week. This will lay the groundwork for many future features that require interaction between colliding objects.

This is part of an ongoing series where we write a complete 2D game engine in C++ and SFML. A new tutorial is released every Monday. You can find the complete list of tutorials here and download the source code from the projects GitHub page.

This week we are continuing where we left off by editing the collision system to call the OnCollisionEnter, OnCollisionStay, and OnCollisionExit functions that we wrote last week. This will lay the groundwork for many future features that require interaction between colliding objects. First I want to make a quick change to the C_InstanceID class, whose job it is to provide a unique id number to every object. Currently we are storing the ids as int, however, I want to change this to unsigned int for reasons that will become apparent shortly.

C_InstanceID.hpp
#ifndef C_InstanceID_hpp
#define C_InstanceID_hpp

#include "Component.hpp"

class C_InstanceID : public Component
{
public:
C_InstanceID(Object* owner);
~C_InstanceID();

unsigned int Get() const;

private:
static unsigned int count;
unsigned int id;
};

#endif /* C_InstanceID_hpp */

C_InstanceID.cpp
#include "C_InstanceID.hpp"

unsigned int C_InstanceID::count = 0;

C_InstanceID::C_InstanceID(Object* owner) : Component(owner), id(count++){}

C_InstanceID::~C_InstanceID(){}

unsigned int C_InstanceID::Get() const
{
return id;
}

Ok, with that done we’ll start work on the collision system. First, we need some way of determining when objects start, stop, and continue to collide. To accomplish this, I’ve decided to store colliding objects as a pair in an unordered_set. I’m using an unordered_set as it stores items uniquely, so it will prevent us from adding a pair that is already colliding and re-calling there collision functions. This way when we add a pair successfully we know they’ve started colliding so we can call OnCollisionStay; we can iterate over the pairs each frame and check if they’re still colliding, and if they are we call the OnCollisionStay function; and if they aren’t then we call OnCollisionExit.

The unordered_set stores elements in buckets based on their hash, so we need a quick hash function that can be used to determine if a colliding pair is unique. As an object will only have one C_BoxCollider, we can use the instance ID component of the owning object. I’ve chosen to use an altered version of Szudzik’s elegant pairing function.

“When x and y are non-negative integers, elegantPair(x, y) function outputs single non-negative integer that is uniquely associated with that pair.”

In this case x and y will be the instance ids of the first and second object in the pair. As we’ve changed the instances ids to use unsigned ints and they are unique to each object they’ll do nicely. I’ve changed the function slightly because currently it will provide a different hash depending on whether the object is first or second in the pair. As an example, say we have two objects with the instance ids {120, 240}. We want to be able to insert them into the unordered_set as {120, 240} but then when we attempt to insert {240, 120} (because object 2 is also colliding with object 1) I want it to fail as the pair is already in the unordered_set.It should be noted that this compares if two objects are unique as its based on the objects instance id. If in future we can add multiple colliders to a single object we’ll need some way to determine the uniqueness of a pair based on the components alone. 

ComponentPairHash.hpp
#ifndef ComponentPairHash_hpp
#define ComponentPairHash_hpp

struct ComponentPairHash
{
template <typename T>
std::size_t operator()(T t) const
{
std::size_t x = t.first->owner->instanceID->Get();
std::size_t y = t.second->owner->instanceID->Get();

return (x >= y) ? (x * x + x + y) : (y * y + y + x);
}
};

#endif /* ComponentPairHash_hpp */

With the hash function hashed out (sorry!), we can create the storage for the colliding objects in S_Collidable. As I said earlier this will be an unordered_set of collider pairs.

S_Collidable.hpp
#include "ComponentPairHash.hpp"

class S_Collidable
{

private:

std::unordered_set<std::pair<std::shared_ptr<C_BoxCollider>, std::shared_ptr<C_BoxCollider>>, ComponentPairHash> objectsColliding;

};

Now when two objects collide we attempt to add them to this collection in the Resolve function.

S_Collidable.cpp
void S_Collidable::Resolve()
{
for (auto maps = collidables.begin(); maps != collidables.end(); ++maps)
{


for (auto collidable : maps->second)
{


std::vector<std::shared_ptr<C_BoxCollider>> collisions = collisionTree.Search(collidable->GetCollidable());

for (auto collision : collisions)
{


if(m.colliding)
{
auto collisionPair = objectsColliding.emplace(std::make_pair(collidable, collision));

if(collisionPair.second)
{
collidable->owner->OnCollisionEnter(collision);
collision->owner->OnCollisionEnter(collidable);
}

}
}
}
}
}

The call to emplace returns a pair and the second item in the pair is a bool indicating if the insertion was successful. If it returns true then we know that this is the first time they have collided and can call OnCollisionEnter.

With OnCollisionEnter sorted, create a new function called ProcessCollisingObjects. This will be responsible for calling OnCollisionStay and OnCollisionExit.

S_Collidable.hpp
class S_Collidable
{


private:
// Remove below function definition.
//void ProcessCollisions(std::vector<std::shared_ptr<Object>>& first, std::vector<std::shared_ptr<Object>>& second);

// And add the definition for the new function:
void ProcessCollidingObjects();

};

I’ve also removed the definition for the ProcessCollisions function, which was something we added all the way back in week 18 and never used.We iterate over the colliding objects and check if either of the objects has been queued for removal and if either of them has we then call OnCollisionExit on both objects and remove the pair from the set. If neither has been queued for removal then we need to check if they are both still colliding, if they aren’t we call OnCollisionExit and remove them from the set. Lastly, if neither of them has been queued for removal and they are still colliding then we call OnCollisionStay and keep them in the set to be tested next frame.

S_Collidable.cpp
void S_Collidable::ProcessCollidingObjects()
{
auto itr = objectsColliding.begin();
while (itr != objectsColliding.end())
{
auto pair = *itr;

std::shared_ptr<C_BoxCollider> first = pair.first;
std::shared_ptr<C_BoxCollider> second = pair.second;

if(first->owner->IsQueuedForRemoval() || second->owner->IsQueuedForRemoval())
{
first->owner->OnCollisionExit(second);
second->owner->OnCollisionExit(first);

itr = objectsColliding.erase(itr);

}
else
{
Manifold m = first->Intersects(second);

if (!m.colliding)
{
first->owner->OnCollisionExit(second);
second->owner->OnCollisionExit(first);

itr = objectsColliding.erase(itr);
}
else
{
first->owner->OnCollisionStay(second);
second->owner->OnCollisionStay(first);

++itr;
}
}
}
}

We’ll call this function in Update just before we update the quadtree.

S_Collidable.cpp
void S_Collidable::Update()
{
collisionTree.DrawDebug();

ProcessCollidingObjects();

collisionTree.Clear();
for (auto maps = collidables.begin(); maps != collidables.end(); ++maps)
{
for (auto collidable : maps->second)
{
collisionTree.Insert(collidable);
}
}

Resolve();
}

Now if we’ve done it right we should have enabled communication between colliding objects! Next week we’ll write a component for our projectile that removes it from the game when it collides with the environment. Not super exciting but it will test our communication system.

As always, if you have any suggestions for what you would like covered or are having any trouble implementing a feature, then let me know in the comments and I’ll get back to you as soon as I can. 

Thank you for reading 🙂