Let's Make an Engine: Project

2022-12-21

Series Introduction

Welcome to the first entry in a series where we will be creating a modern game engine from scratch using C++. It is the game industry standard and the language I am most comfortable with having used it for over 20 years now.

As a professional working on AAA games, it can often feel like the fable of the blind men touching an elephant, where you are only able to see a small part of the project at a time and never really grasp the totality of it. Adoption of new technology is also frequently slow, as companies are understandably risk-averse in the latter stages of a project’s life cycle. This series gives me the opportunity to revisit topics I haven’t had to seriously consider in many years, reevaluate my priors, and stay up-to-date with the state-of-the-art. I hope that others reading these posts will also find value in following my journey.

We will utilize SDKs and libraries where it makes sense. While there is value in learning about concepts such as the inner workings of the zlib compression algorithms, that is a separate journey. Our goal is to start simple and gradually build up to a game engine that can be used to create a variety of simple games.

Setting Up the Project

There are an infinite number of choices to make with a blank canvas so we just need to start. Our dev environment of choice is a Windows 10 host running VS 2022 with CMake using the latest C++ version.

We need 3 things to get started:

  1. Engine: A place to code the engine.
  2. Game: A place to code an app that uses the engine.
  3. Build: A build script to make the builds.

Engine

Engines are frequently coded as a library. If done well this can allow you to reuse the engine across multiple projects. Even if you are only interested in a single project it can be advantageous as game-only changes do not require rebuilding the engine which can typically take a while.

We’re going to use a C++ Module for our engine. It will start here as an empty placeholder we can fill in with features later.

engine/engine.ixx
module;

export module LetsMakeEngine;

export namespace LetsMakeEngine {
    // engine public interface
}

module :private;

namespace LetsMakeEngine {
    // engine private implementation details
}

This creates an empty module named LetsMakeEngine and exports a namespace also named LetsMakeEngine. An explanation of modules is beyond the scope of this post but I encourage you to watch this excellent presentation A (Short) Tour of C++ Modules - Daniela Engert.

Game

Now we need a game or app that uses our engine to build something cool. Well we can worry about the cool part later. For now let’s just stub out a dirt simple empty game loop.

main.cpp
import LetsMakeEngine;

using namespace LetsMakeEngine;

int main() {
    // Initialize systems

    for (;; ) {
        // Update game state
        // Render frame
    }

    // Shutdown systems
}

This is as simple as it gets. In fact it does literally nothing but spin in an infinite loop. I promise things will get more interesting quickly. Nevertheless let’s make sure this works before we move on.

Build

We need to define the build script for our project.

CMakeLists.txt
cmake_minimum_required(VERSION 3.24)

# Enable more warnings and treat warnings as errors
add_compile_options(/W4 /WX)

# C++ modules are a little new and wonky in the tooling
# These are just the necessary incantations to enable
# the latest C++ version and std library modules
function (target_enable_cxx_modules TARGET)
    target_compile_features(${TARGET} PRIVATE cxx_std_23)
    if( MSVC )
        target_compile_options(${TARGET} PRIVATE /experimental:module)
        set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 23)
    endif()
endfunction ()

# Define a C++ project
project(LetsMakeEngine VERSION 1.0
            DESCRIPTION "Let's Make an Engine"
            LANGUAGES CXX)

# Include all of the source files
file(GLOB SOURCES CONFIGURE_DEPENDS *.[hic]pp)

# Build the engine module
add_subdirectory(engine)

# Build an executable for the game
add_executable(game ${SOURCES})
target_enable_cxx_modules(game)
# Add engine to the game
target_link_libraries(game PRIVATE engine)
engine/CMakeLists.txt
file(GLOB SOURCES CONFIGURE_DEPENDS "*.ixx")

add_library(engine STATIC ${SOURCES})
target_enable_cxx_modules(engine)

A couple of things to note here.

We use 2 CMakeLists. One for the game and another for the engine. This isn’t strictly necessary but will make it easier to separate concerns later.

We’re using file glob with CONFIGURE_DEPENDS this is fairly new. The standard best practice is to list out all of the files manually but this is safe and plenty fast for now and makes it easier to add and remove files as we rapidly develop.

And finally the helper function that enables C++23 and standard library module support.1 Tooling support for modules is fairly new, these are the incantations that work for now but it will change and become easier before long.

Going forward we’ll only show changes to the CMake configuration as necessary.

One final thing related to the project setup is setting up version control including a suitable ignore file to exclude intermediate and generated files from source control. Setting up a .gitignore or .p4ignore is beyond the scope of this article but there are plenty of examples available online.

Wrapping Up

With the skeleton in place, we are now ready to begin writing the interesting code for our game engine. While there isn’t much to see in this current setup, it is simple and flexible, providing a solid foundation for our future development.

In the next entry, we will transition from a terminal application to a proper GUI window.


Series: Lets Make an Engine