MEngine
This is a project that I have been working on for some time now (constantly in the cycle of starting and restarting the project, trying to maintain the components that I actually liked each time), it is a game engine that I am writing in C and is still very much a work in progress. As I continue to write about it, I will continuously link all subpages that I have created.
All code for this project can be found on my GitHub page: https://github.com/Mgrimaldi20/MEngine
- Part 2: System Layer and Main Loop
Overview
There are a few things to know if you are starting with this repository and wish to experiment with it, and even when cloning it.
- This project uses git submodules for the few external libraries it makes use of.
- This project makes use of CMake version 3.25 and as such needs to be installed. The projects few build and install scripts will call CMake and have some configuration options that I will touch upon later.
- There are a few extra packages for linux and MacOS builds that you will need to install from either apt/yum/dnf/brew or whichever your preference is (these libraries are mostly for GL and X/Wayland windowing).
- There are a few steps involved in creating a game, but it is very simple to do for the most part, there is an aditional step required for Windows builds (exporting module definitions).
Submodules
As mentioned above, this project uses submodules, so when cloning the project:
- Clone with:
--recurse-submodules
or--recursive
flags to clone the submodules as well. - Alternatively, you can run
git submodule update --init --recursive
after cloning the project. - Or
git submodule init
andgit submodule update
The simplest way to get started is with this typical clone command (clone main repository, clone and update all submodules):
git clone --recurse-submodules https://github.com/Mgrimaldi20/MEngine.git
Building The Project
Also as mentioned before, this project requires CMake 3.25, and building on Linux, a few extra packages to get started. I will start with the steps for building on Windows, since it is the simplest.
- The windows build script is the
build_win.bat
batch file, and it can be run with some configuration options. - You can either run this script to produce a Debug or Release build with all optimizations turned on.
- Run
build_win.bat debug
to generate a Debug mode binary with all debug symbols and sanitizers, and all optimizations turned off. - Run
build_win.bat release
to generate a Release mode binary with no debug symbols or sanitizers, and all optimizations turned on.
Building on Unix based systems is a little different, a few packages may need to be installed before the build will succeed (mainly on Linux based systems, MacOS shouldnt need to do this):
sudo apt-get install libxkbcommon-dev
sudo apt-get install libxrandr-dev
sudo apt install xorg-dev
sudo apt install libglu1-mesa-dev
sudo apt install libasan6
The *nix build scripts actually take in an aditional parameter to the Windows one which will specify the operating system for which it is being compiled:
- For all types of *nix OS, built from the
build.sh
script. - Takes in an aditional parameter to specify the OS:
linux
andmacos
for the respective Operating System. - Takes in the usual parameter
debug
orrelease
for build type.
For example, if you wish to build a Release version for MacOS, you would run ./build.sh macos release
The semantic is as follows: ./build [macos|linux] [debug|release]
The script will pop up a usage message if you use the script incorrectly.
Installing The Engine
The installation scripts are more or less semantically the same as the build scripts, however, these scripts are used when distributing the engine, while the build scripts are mainly only used for testing and debugging. These install scripts also need CMake and the resulting directory structure is how the end user is going to be using the engine to make games, or how the engin will be distributed with games.
- To install on Windows:
install_win.bat [debug|release]
- To install on *nix systems:
./install [macos|linux] [debug|release]
Developing A Game
In MEngine, much like old style FPS shooter games such as Quake and Doom3, the engine itself is actually the executable program, and games are dynamic libraries that the engine will load at runtime.
This approach offers a great deal of control to the engine, yet is still highly flexible yet simple to use when making a game. Using this method, the engine is able to abstract away the difficult operations such as window creation and management, and other various services that a game developer should not need to worry about... All the game developer has to worry about is their own game code itself.
To develop a game, a few steps need to be taken, including the following additional step for Windows:
LIBRARY <LIB NAME HERE>
EXPORTS
GetMServices @1
For an example, the demo game has the following:
LIBRARY DemoGame
EXPORTS
GetMServices @1
*nix systems do not require this step, as all functions in a shared object file are marked as exported and visible by default. On Windows however, this is not the case, all functions by default are not exported, the user must manually do this.
Once this step is complete (only if it was required), the next step to developing a game lies in the SDK, there is only one header file for the engine and its purpose is to define to various interfaces that the engine provides to the game developer,
and that the game developer must supply to the engine. This file is the mservices.h
file.
If you look closely in this file, you will see a few interfaces, for a game developer, the main ones to worry about are as follows:
gameservices_t
This interface defines a game, and it is the interface that the game DLL/SO/DYLIB binary must implement.mservices_t
This interface defines the engine, and it is the interface that the engine provides to the game, which contains its core functionality, along with some useful routines for game development.
The mservices_t
interface contains various sub-interfaces representing the engines sub-systems. This will all be touched upon later in much greater depth, as the way the engine and game code communicates its quite interesting.
In short, upon engine startup, it will implement the mservices_t
interface, it will then call the GetMServices(); function, which will do a "handshake" between the game and the engine, providing the engine implementation to
the game, and the game will provide its interface implementation to the engine.
The signature for that function looks like this:
gameservices_t *GetMServices(mservices_t *services)
It's defined by the game code and exported by the game, it will return its implementation, to the engine, and receive the engines implementation as an output parameter.