Spitoon
Overview
This was a project I worked on for 2 months from February 2023 to March 2023.
For this project, I was working in a team of 8 people (including myself) and we were given the task of creating a game inspired by Splatoon in C++, by building on top of the game engine framework we had from the Goat Simulator project.
The gameplay loop is relatively simple. The player navigates around a map and is able to shoot at and paint the environment. The player can pick up power-ups with varying effects throughout the scene. The game can be played competitively in 2-player split screen or in 2-4 player networked play.
Below is a short video showcasing the project.
Middleware - ReactPhysics
As the physics system that was originally in the project was programmed by us and we weren't very confident in its implementation, we opted to import an external physics library into our project. I was responsible for adding it to the project which meant that I had to become more confident in my use of CMake to add the library to the project and then I had to integrate the ReactPhysics colliders and system managers into our existing system. This was a relatively simple task and it greatly improved the performance of the game from the beginning.
One consequence of using this middleware, however, was that it meant I was going to have to program a custom implementation of ray casts as well as OnCollisionBegin and Exit methods myself for the rest of my team to use. This was because ReactPhysics provides a base callback class for this functionality which the engineer using the middleware is responsible for creating a derivative class for use in the game.
Raycast Code
GameWorld.h
GameWorld.cpp file
Using the ReactPhysics3D documentation I reimplemented raycasts into the project. Raycasts are called with this middleware by passing a ray and a rayCallback class object to the physics engine. The rayCallback class object must implement a function called notifyRaycastHit, this gets called on all objects that the ray intersects with. As the length of a ray may be longer than the distance at which it hits the first object and the order in which objects are hit is not known, all collisions need to be recorded and then sorted through afterwards. Therefore I store all of the collisions in a vector as each notifyRaycastHit is called.
When sorting through the collisions, I considered using a sorting algorithm to order the collisions by hit distance, however, I realised that just looking through them in turn and caching the index and hit distance of the closest collision could be done in O(n) and a sorting algorithm would have a greater big O. From there I iterate through the game objects to determine which object the hit rigidbody belongs to and add this to the collision information before returning it.
If there is no collision I create and return a dummy value which has to be deleted if something is hit. I realise that the better approach to this would be to create a scene contact point outside of this function, pass it through to this function, change it inside of this function and then outside of the function check to see if the struct changed. If the struct changed it would be determined that something was hit otherwise nothing was hit. I learnt this in the process of fixing the memory leak that I caused due to my original implementation of determining the closest collision. In the commented-out getHit() function I would fail to delete a dummy collision if the ray succeeded in hitting something.
I also added an ignore object field to the raycasts. I implemented this as often a raycast would be called from the position of a transform and then the ray would pass through the object calling the raycast, therefore the rays needed to be able to ignore the object calling the function.
OnCollisionBegin/Exit Code
GameObject.h
GameObject.cpp
The information on object-object collisions in ReactPhysics3D is accessed using a listener which the physics engine has access to. The Physics engine will call the onContact function and pass all the information about all collisions in that physics update. In the onContact function, I then cycle through all of the collision pairs, determine which gameObject each collider belongs to, pass the collision point to each game Object and call the appropriate OnCollisionBegin or OnCollisionEnd function.
Similarly to the raycast function, I dislike that I have to cycle through all of the gameObjects to determine which one owns which collider. I think in a future project, if I were to use ReactPhysics3D again I would have to learn how to use its entity component system because then I could assign each collider an ID and have this ID match the game object's. This would drastically improve performance and limit the number of loops in each physics update.
Profiling and Debug Menu
I created a debug menu that could be displayed on screen during gameplay. This allowed for memory usuage to be easily tracked while running the game as well as a method for us to track various other metrics such as the number of gameobjects in the scene or if gravity was on or not. This was a useful feature for when a new feature was implemented to the game and would be a starting point for spotting memory leaks before consulting the visual studio debugger for a more in depth look at the memory profiler.
Profiling Code
TutorialGame.cpp
https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process - This is where I got the code (lines 444-452) to extract memory usage and memory available from the computer on Windows.
The code toggles the debug menu when TAB is pressed. The information in the debug menu is passed as strings to the Debug renderer which renders text passed to it to the screen at the position and in the colour specified.
Other team responsibilities
In this project I had lots of different responsibilities, however, most of them were not glamorous programming tasks such as graphics, AI or UI.
As part of my profiling responsibilities, I became the team's main port of call for troubleshooting bugs. Throughout the course of the project, I helped to eliminate memory leaks in the raycasts (caused by me) and terrain painting systems. I also helped to find and eliminate bugs in the networking and character controller code. I spent a lot of time in this project refactoring code. The people who were responsible for programming character controls had created a lot of duplicate functions: one for player 1 and one for player 2. I went through the project and collapsed most of these functions into a single function to reduce code duplication and redundancy. This was essential for the setup of the 4-player networked mode of the game.
I also agreed to develop the game on PS4, however, as the university failed to give us admin access credentials this became very challenging to complete. I managed to successfully render multiple different models with different textures on screen simultaneously, which was more than most, however, I did not succeed in fully porting the game to PS4.
In non-programmtic roles, I created the Microsoft Teams group: giving the team an easy way to communicate with each other as well as the Trello: giving everyone an easy way to see what tasks we were all doing and what tasks were left to be done. I organised meetings with the team as and when needed, which became roughly twice a week through the 8-week period. In these meetings I would find out what everyone else was doing, I would share what I was doing and I would bring up any major issues I had noticed since the last meeting and endeavour to create a strategy to tackle these issues.