The main purpose of this project was to take the first few steps of making our own 3d engine, so the game itself needed to be simple (the theme was an on-rail shooter of some kind) and most of the programming effort lied under the hood so we could build the foundation of the engine.
My main task for this project was to create a collision system, and build it in such a way that it would hold up for the next three projects as well. I wanted an easy interface that was intuitive for the other programmers to use without having to be bothered with what happens under the hood, and also keep expandability in mind. Because another programmer was building a component system in parallell at the time, I decided to integrate the collision system with that. All you had to do was to add a Collider-component to a gameobject and then register some other component that would react to the collision, and then things would just work automatically.
One easy line, and then the component system will take care of the rest in its Update loop.
Now let's say we want some component to react to the collision. Easy! First we create the component header file and add all virtual functions that can be overridden:
Notice the "ICollision" interface. This gives access to the "OnCollisionEnter" and "OnCollisionLeave" functions that the collision system will call when collisions happen. Now all we need to do is inform the collision system that this particular component wants to know when something collides with its OBB collider:
And that's it! All you have to do now is put code into the "OnCollisionEnter" and "OnCollisionLeave" functions and that code will happen whenever the gameobject collides with something.
The Collision Manager
Underneath the Collider components lies the Collision Manager. The only thing you really have to do with it to make the system work is to run its Update function:
There's a bit of code going on underneath to make it all work, but to summarize it the system essentially just keep one big list of all registered components:
When the system detects a collision between two colliders it checks whether or not it's in the list of things that are currently colliding with each other:
If it's not in the list, a new collision has occured and the collider is added to the list and the "OnCollisionEnter" function is run. If it is already in the list and a collision has occured nothing more needs to be done, but if it's in the list and no collision is occuring then the "OnCollisionLeave" function is run and the collider is removed from the list.
(I'd like to add here that I was initially a bit worried about performance, considering all the unordered_maps I was using. But it never became an issue since our bottlenecks mostly lied on the render thread, and no need to optimize something that is not a bottleneck.)
One neat thing about the way I put this together is that I could also keep all culling code in the Collision Manager, which is then also out of sight and not something the other programmers needed to think about. No need to handle individual components through a separate culling class, just attach any number of colliders as usual and the game will handle it all automatically under the hood. Just the way I like it!
As a final note I would like to showcase the system that handles the actual math behind the collisions. The Collider components don't do any math themselves, this is handled by the Collision namespace and each collider component has one of these as a member variable:
Since we were going to use all manner of collisions in our projects I decided to make a separate system for all the collision calculations. Again my mantra was "Easy to use and expandable". If you want to check a ray against a plane or an OBB against a sphere, they should all use the same CheckCollision function and you shouldn't have to do anything other than that. For this purpose I devised a system with polymorphism and Double Dispatch at its core.
What I wanted was this:
Here's the header and some code from the AABB class:
The Visit function finally calls a CheckCollision function in a separate header (where all the actual math was gathered in one spot):
Now this whole Double Dispatch thing might seem awfully convoluted and slow at a first glance, it takes a lot of function calls to get from the collider component down to the function that actually calculates the collision (there's a double dispatch system both in the collider components and in the collision classes!), but remember the goal we set: Expandability and ease of use. It was trivial to expand with new collision types over the four games we made with this engine, and the speed was also never an issue.