Waterwheel
Waterwheel is a custom game engine and accompanying game I built from the ground up to deepen my understanding of game engine architecture and C++ development. To ensure practical application and avoid development in a vacuum, I simultaneously developed the game alongside the engine, allowing for continuous testing and refinement of its capabilities.
Process
I undertook this project because I felt using commercially available engines such as Unity eventually became unrewarding, and limited the technical freedom and control I had. I originally programmed the engine in C#, as I was most familiar with that language. I had made good headway into the project, even making a small editor as seen on the left. Eventually felt like I was massively over-complicating the endeavor, and overall was not satisfied with the result.
I decided to rewrite the engine in C++, as that would give me more control to use the lessons I had learnt when programming the C# version. In order to embrace this newfound control, I opted to use as few libraries as possible. This resulted in all of the graphics, audio, asset loading, memory management, profiling, entity organization, etc. to be programmed by myself. Even though this was quite a lot of work, the fact that it was shaped only by what was necessary limited the complexity of the work greatly. From my first iteration of the engine, I learnt that following in Unity’s footsteps was a bad idea, as they need some much more complicated and general purpose than what I need.
By simplifying systems and understanding every aspect of the engine, as I had programmed them, I was able to create a 2D action game that runs a consistently high framerate from scratch in C++. I am extremely proud of this project and have learnt so much from developing such a complicated piece of software.
Programming
While I did make a game, and that process comes with art and design, most of the time was spent on programming. As hinted at earlier, there are several systems to the engine, and I don’t want to cover all of them here, and some of them I have already covered in previous blog posts. That being said, here are a couple of the most interesting ones, in my opinion.
The rendering system is, by far, the most computationally expensive part of the game, and so required the most love. The rendering routine to properly texture map sprites and correctly anti-alias the edges is not a light one, especially when done millions of times per frame. An easy optimization is to convert the routine to use SIMD operations, which are able to perform computations on several pieces of data in the same amount of time compared to computation on one piece of data. This led to a roughly 4x performance boost, but that was nowhere near enough. Using multithreading to have different partitions of the screen render at the same time, massive speed increases were achieved, allowing me to hit the 60fps at 4k resolution benchmark I had set.
The asset system was fairly complicated as it, like the renderer, also used multithreading. While the renderer used multithreading to perform work faster, the asset system uses it to perform work asynchronously. When an asset is requested and isn’t already loaded, a thread is sent to fetch the asset and load it into the asset system, which may take a variable amount of time. This allows multiple assets to be loaded simultaneously and guarantees there are no frame-spikes from long I/O operations. The asset system also manages a dynamic memory allocator that I wrote and evicts infrequently used assets when loading new ones if space is limited.
The debug profiler was extremely useful when optimizing any system. To make sure the act of profiling had minimal overhead on the executing procedures, very basic info was collected. Only until after the frame ended was any of that data analyzed and presented back to the user. The profiler UI I created allowed the user to view how each thread was performing and to dive into the performance of each function and its respective calls. I also kept track of many of the previous frames, in case there was a particular frame I wished to investigate.