My First Attempt at Building a Proper Game Engine, Mostly From Scratch.

I have uploaded what was the final outcome of my work for a Game Engine Architecture Module in the first term of my final year at university to a repository at: https://gitlab.com/ejt47/cw-2021-blah/.

This was probably my second large undertaking in C++ and cemented my love for the language. This project started with a base provided to us by our lecturer. You can see that the first few commits are made by him.

This module lead onto a Game Engine Development module, where I worked with a team of other students to attempt to expand the toy engines we had created in the Game Engine Architecture module, in order to present a vertical slice of a game. In that module my team chose to work off of my engine as a basis. This post though, will only cover my work up until the end of my Game Engine Architecture Module.

This project does not include much if any game functionality. In fact when the program is started all you would be met with is the below image with rotating 3D shapes in a window.

Whilst not very exciting this shows the main things I needed to demonstrate, 2D rendering, 3D rendering, and text rendering. Alongside various other systems such as input handling we can manipulate the images on a screen to be used in a video game.

The project in this state has the following features:

  • A logging system, utilising the spdlog library through which profiling can and has been performed to test individual aspects of implemented systems. It can be used to provide accurate timings in the console, for outputting messages of varying priority levels ranging from information output, to warnings and errors. With time stamps(accessed through the timing system) and colour sinks. Utilised by many other classes throughout the system to obtain accurate and reliable information on the current state of the system. An invaluable sub-system.
  • A randomisation system, that has been implemented through two primary methods; a uniform distribution method, which utilises continuous box uniform statistical distribution from a pseudo-random seed to provide a pseudo-random generated numbers; and a normal Gaussian distribution that also distributes at similarly random capacity a set from which a pseudo-random number can be selected. Implemented alongside the two distribution methods were two distinct methods of pseudo-random seed generation, where in one a Mersenne twister algorithm is used to select a seed, in the other a Seed Sequence method, both being implemented by the standard template library. In both cases, where it is possible a standard library “random_device”, which can leverage, if available non-deterministic seeding of a generator. However in most cases it would seem that it is mostly non-deterministic, dependent on the C++ compiler implementation, to take account for this a check is made to ensure the device is able to draw upon the entropy it requires to select a seed non-deterministically.
  • A timing system was implemented through the chrono header, which allows for fast and accurate timing implementation across various sub-systems. Utilised by many other classes throughout the system to obtain accurate and reliable information on the current state of the system, proved to be incredibly useful.
  • A windowing system was built upon through a windowing interface, through which API-agnostic programming, utilising different platform-specific libraries can be used to write further windowing programs/systems. In this case only a GLFW implementation was undertaken through the windowing interface, and it is through GLFW that event handling functionality was further developed. Due to the agnostic nature of the interface, it can be reasonably argued that further expansion can be undertaken using technologies such as Win32 or Vulkan.
  • Event handling was also handled for the most part in an attempted platform independent way. Although in the developed system the event handler interface provides a API-agnostic framework through abstract event callback functions, that can be mapped to actual functionality. In this case the functionality is mostly provided by GLFW, where the abstract callbacks are assigned to GLFW callbacks functions, which provides for the most of the heavy lifting in terms of its input polling solutions.
  • Graphically an OpenGL context is implemented as the solution for the graphics capability of the engine, where the foundational aspects include the implementation of shape primitives, shaders, and textures. These foundational aspects are applied at a low-level in terms of hardware through the usage of vertex arrays, vertex buffers, and index/element buffer objects. The foundational graphical systems provide a position for which more impressive tasks can be approached from.
  • The final important piece of functionality that was worked on was the rendering system that is used to draw on these lower-level aspects in a more impactful way. The rendering system operates through a mostly successful API abstraction, that despite only being used to implement graphics through OpenGL, also have the room left available for the implementation of other graphics libraries through a shared interface. The rendering foundations, are further developed into a 2D, a 3D and a very basic text renderer. The 2D system allows for drawing of shapes in a flat plane, where the basic text rendering functionality is also implemented. Whereas the 3D implementation of the renderer provides a sense of depth, giving the appearance of 3 dimensions, despite also being on a flat plane, developed through shader programs and lighting models to provide that degree of emphasis.

I learnt a lot of invaluable information during this project and really would like to revisit it. Although I will never attempt to render text in the way that I did again. There has to be a better so.lution than the janky pointer-walking and hackery involved.

In terms of my future plans, I would like to use this as a starting point to attempt to build a full game from scratch in C++ as I have some problems with the direction of the engine’s development in the subsequent Game Engine Development module. There are some things I would definitely like to add that I did at a later point such as batch rendering and more three-dimensional functionality. I tried to write a lot of the code in a reusable and API-agnostic way, so I’d also like to implement the functions that would allow this engine to render graphics via Vulkan. I think it would be good learning experience in porting a codebase to another operating system such as Linux.

I’d also like to approach things in C++ from a security minded stand point, something that has been lacking in my previous projects. C++ can be very memory unsafe, and the final outcome of most of my projects compile, but with many warnings. I’d like to operate on a basis of warnings as errors that must be resolved.

This undertaking revealed to me the sheer amount of work that goes into building these systems for video games and it is unsurprising that it takes large companies teams of 100s of people to pull off the things that they do.

Thanks for reading! If you fancy attempting to get this to run on your system it works for me with Visual Studio 2019, you willl need to make sure to run the premake script. Take a look in the repository, there is documentation auto-generated via Doxygen in the “doc” folder.

🙂

ejt47 Written by: