Building a Game Engine From Scratch
How to create the building block for any game.
Stunning games are released every year with ever-evolving graphics. But while games differ drastically from each other, their foundation, the game engine, is what truly makes them stand out. But what exactly is a game engine? Let us find out.
The Heart of Any Game
The game engine is the building block of any game. It drives everything we can hear, see, and interact with in a game. Most game engines use multiple programming languages. At the lowest level, C/C++ is the choice of virtually every engine. On top of that are APIs, often implemented in another language, the scripting language. Scripting is a vital part of games as it breathes life into the engine. Without scripting, engines can’t do much. How scripting is implemented in the engine is one of the first biggest choices that have to be made.
Scripting is the process of implementing logic for a game. Most engines feature a custom or third-party scripting language that interferes with the engine. C++ and C are speedy languages, but they are not easy to pick up and are relatively unforgiving. Memory management and much more is something that game developers don’t want to worry about all the time while creating a game. This is why scripting languages are often higher-level and easier to use languages.
In particular, the are two ways of implementing scripting in a game engine.
The second approach focuses on the scripting language and only implements critical parts, such as the renderer, in C/C++. The idea is that high-performance code runs as fast as possible with the help of C/C++, but anything else is implemented in the scripting language. This way, adding developers get access to almost all engine functionality. This allows for significant modification of game details and makes for a more generic engine. Virtually any game can be made with this approach as the engine itself does not have any limitations on what can be scripted.
Which method is more useful heavily depends on the project and requirements of the engine. For example, a turn-based RTS, like Civilization, would not require turn logics to be very fast, so an interpreted language can be used, which adds overhead and leads to decreased performance. In contrast, a high-end shooter game would need frame time to be very low; thus, an interpreted language would not be advantageous. The first approach works with compiled languages as well; those are just some examples.
The above image shows a simplified example of a game engine structure. A clean design would separate each part of a game engine into modules. Often, these modules are wrappers of platform-specific APIs. The goal for an optimal game engine is to be platform-agnostic. This means that the engine code wraps all platform-specific APIs and provides an API itself, which higher-level code has access to. This way, the engine itself can run on any platform.
Additionally, adding another platform is just a matter of providing the required backend logic for each platform-specific API inside the corresponding modules. For example, DirectX 11/12 are the go-to solution for rendering on Windows and XBox. Likewise, developing for Macs would require the Metal library to be used. Having all the module logic separated into a custom, unified wrapper allows easy porting of the game engine to new platforms.
Essentially, the engine collects all APIs required to run a fully-featured game. APIs for rendering, sound, physics, and much more are often all bundled into one library. In many cases, this library is loaded dynamically at program startup. This has the benefit of keeping the engine and editor code logically decoupled. The engine alone is rather dumb. It does not know anything; every single detail of a game has to be sent to it. No matter if it is drawing a model, playing a sound, or loading into a new scene. The user code has to tell the engine to do that.
The editor is the most essential part of any game project. Asset handling, level design, debugging, and much more is done inside the editor. It is like an IDE, just for anything except code in your project (Unless you roll your own scripting language). Designers use the Editor to build levels in a real-time scenario. Programmers use it to develop and test their scripts. Creating builds, new projects, and a lot more is also the editor’s work. For the editor, there are essentially two ways of design choices.
The first method is more complex to implement but enables hot-reloading, faster test iteration and more. Virtually, the editor is a wrapper of the actual engine. It uses the same library that games based on the engine use. It calls many APIs behind the scenes and provides a default user interface. As stated before, this is more complex but helps keep the editor consistent with the engine code. A significant disadvantage is the requirement of editor UI implementations right into the engine’s renderer. This is hard to avoid because the editor does not have a custom rendering system. To prevent game code from accessing the editor UI functionality, a good idea is to write parts (or even all) of the editor in the scripting language itself. This also allows users to customize the editor via scripts. A simple compiler flag can enable or disable the compilation of such methods. Furthermore, this approach leaves the engine code untouched if changes to the editor are required.
The second approach is an entirely different editor with its own systems. This has the upside of not having to rely on the engine code. The editor can have its own APIs (if any) and interact with its own libraries. Of course, those libraries can be the same ones used by a game. This approach is incredibly faster when customization of the editor via user-defined scripts is not essential. Theoretically, the editor would not require the scripting language at all. It can directly talk to the underlying C/C++ engine code. Furthermore, the renderer does not need editor UI code, as it is handled by the editor itself.
Whatever choice is more suitable for an engine depends on its use case. For example, a primitive editor does not need access to editor scripting; thus, making it a standalone project might be faster. On the contrary, if custom editor code is required, especially if it is done in the scripting language itself, it makes more sense to use the game engine scripting APIs directly.
Thank you for reading this article. In the next part, we will discuss how to lay out engine code and what technologies to choose.