Project Information
- Language: C++
- Graphics API: DirectX 11
- Creation: Spring 2021
- Source URL: GitHub Page
Group Project: Molecularity
Created as part of a group, Molecularity is a puzzle game, similar in nature to Portal, that was developed entirely with C++ and DirectX 11, for a module at university in the Spring of my second year. The goal of the game is to utilize the provided multi-tool and all of its in-built functions to manipulate and modify the physical properties of objects within each room.
The player is given the ability to change the material, weight, size, bounciness, conductivity etc. of select objects. In doing this, they are tasked with activating a pressure plate in each level which serves as the win condition. The underlying engine utilizes a variety of systems that were developed across the members of the group while adhering to standard programming practices.
Roles & Responsibilities
Within the group, I took on the role of lead programmer and was in charge of overseeing the development of all of the systems within the game engine, and ensuring that they adhered to standard programming practices and patterns. Further, I outlaid the design of the system architecture according to a series of deliverables that would be distributed to each group member, myself included, according to what best suited our interests and expertise.
As my area of expertise lies with graphics programming, I opted to develop all of the graphics systems that are used throughout the game. For assigning tasks, Trello was used throughout the game's development. To see how these tasks were distributed, our personal Trello board can be found here.
For managing the source code, GitHub's source control was the primary tool that we used. This enabled us to create local branches of the main codebase, without affecting the work of other group members. I initially developed a bare-bones system using Win32 for message processing which allowed everyone to start developing their systems after creating a local branch of the source code.
GitHub proved extremely useful in the development of these new systems as it allowed each team member to view what everyone else was working on. This was especially true when it came to debugging. Once a team member was satisfied with their work, they would submit a pull request which I would always personally verify, to ensure that there were no conflicts with the existing code, before committing it to the master branch. All the pull requests made throughout the development of the game can be found here.
Idea Generation
To begin laying the groundwork, we began with a team meeting to decide the type and style of game that we wanted to create: a futuristic puzzle game seemed to be unanimous. To further develop this idea, I assigned weekly research tasks to each team member concerning different aspects of the game; art, sound, mechanics, models and UI. Each week, we would share our findings and commit this research to a design document. We would draw influence from existing game implementations as we saw fit, where the art style and the multi-tool were derived from Portal. We then created an initial design presentation that shared our thoughts and ideas of the game to be developed.
The Development Process
The development process began with a bare-bones Win32 system that created a simple window with minimal graphics. After developing this system for the first two weeks, and using team meetings solely to discuss the game idea, I then assigned individual roles to develop specific parts of the game engine. Initially, these systems were coupled with the graphics engine, and after some time a better solution was implemented: an event system, to better decouple the various systems and to allow for easier branching and development as outlined here. The general layout for the event system that was implemented is as shown below.
class EventSystem
{
public:
void AddClient( EVENTID eventid, Listener* client ); // Registers client to specific event
bool IsRegistered( EVENTID eventid, Listener* client ); // Checks if the client in question is already in the system
void RemoveClient( EVENTID eventid, Listener* client ); // Unregisters client from a specific event
void RemoveAll( Listener* client ); // Unregisters client from all events
void SendEvents( Event* event ); // Sends the event to anyone registered
void AddEvent( EVENTID eventid, void* data = 0 ); // Add event to the buffer
void ProcessEvents(); // Process All events
void ClearBuffer(); // Clears all events from the buffer
void ClearClients(); // Clears all clients from the client list
void Shutdown(); // Shutdown event system
static EventSystem* Instance(); // Returns the event system
private:
EventSystem() {};
~EventSystem() { this->Shutdown(); }
std::multimap
The event system enabled us to better focus on the development of our systems without the worry of how they would integrate into the rest of the game engine. Instead, we were able to modify code and submit pull requests to the master branch with minimal conflicts due to the now decoupled nature of the codebase. Using this, I was able to better implement the lighting, fog, stencil, post-processing and multi-viewport systems.
One final problem remained with the way that levels were being handled. To better manage switching between and loading levels, I opted to integrate a level state machine, according to the following article which can be seen below.
class LevelStateMachine { public: LevelStateMachine(); void Render(); void Update( const float dt ); std::shared_ptrMulti-threading was then implemented to improve the transition times between levels, as the loading of new levels would be offloaded to a separate thread, and only for the level that is to be switched to. This addition drastically helped in the creation of new levels, as we were able to quickly switch between the levels on the fly in-engine, and check for any inconsistencies, bugs or exploits. A development diary was updated throughout the development process to document the weekly additions to the game.GetCurrentLevel() const noexcept { return currentLevel; } // Get a handle to the current active level uint32_t Add( std::shared_ptr level ); // Add another level to the state machine void Remove( uint32_t id ); // Removes a level from the state machine void SwitchTo( uint32_t id ); // Switch to an already existing level private: uint32_t insertedLevelID; std::shared_ptr currentLevel; std::unordered_map > levels; // Stores all of the registered levels };
Final Thoughts
I believe my team and I worked well together. We were able to cooperate on the game's development and focus on our parts of the engine when required. Group discussions were also really involved and often resulted in a lot of new ideas for potential mechanics and systems to be added into the game. the team respected my decisions on the game's direction, the engine architecture, the responsibilities that I would assign them and the deadlines that I would set for any given task. They also trusted me to verify each pull request and would often ask for advice on parts of the engine that they were unsure of.
Given that our team was able to complete the majority of the set deliverables, we completed the project with enough time before the final deadline to complete further bug tests. A final presentation with a complimentary video was created and feedback provided by industry professionals. Overall, it was a really enjoyable experience and I learned a lot, not just about programming, but also project management and working as part of a team.