Custom Game Engine Development
As part of my passion for game development, I have been building a custom game engine from scratch in C++. This project demonstrates my deep understanding of low-level programming, architecture design, and the foundational systems required to create and run games effectively.
Role
Lead Developer
team size
1 person
development time
1 months
Key Features
1. Platform-Specific Configuration
-
Windows-only Support:
The engine is designed to run exclusively on Windows (#ifdef MGE_PLATFORM_WINDOWS), ensuring platform-specific behaviour. -
Dynamic Linking:
Uses __declspec(dllexport) and __declspec(dllimport) to manage symbols for building and consuming a shared library (My_Game_Engine.dll).
2. Modular Game Engine Architecture​
-
Application Class:
Provides a base class (Application) for client applications, encapsulating the main game loop (Run() method). -
Client-Defined Entry Point:
Clients implement their own CreateApplication() function to initialise their specific application logic (e.g., Sandbox).
3. Logging System​
-
Core and Client Loggers:
Implements two separate loggers:-
Core Logger for engine logs.
-
Client Logger for application-specific logs.
-
-
spdlog Integration:
Uses the spdlog library for flexible, high-performance logging with features like coloured output and formatted messages. -
Macros for Logging:
Simplifies logging with macros such as:-
MGE_CORE_Warn(...) for engine warnings.
-
MGE_INFO(...) for client information messages.
-
4. Utility Features
-
Bitmask Macro:
BIT(x) simplifies creating bitmasks for flags or states by shifting bits (1 << x).
5. Build System Configuration​
-
Multi-Project Setup:
Two projects:-
My_Game_Engine: A shared library (the engine itself).
-
Sandbox: A client application that uses the engine.
-
-
Build Configurations:
Configures different build modes:-
Debug (symbol generation for debugging).
-
Release (optimised build).
-
Dist (distribution build).
-
-
Post-Build Commands:
Automates DLL copying from the engine project to the client project directory.
6. Simplified Application Loop​
-
Infinite Loop (Run):
Demonstrates the concept of a game loop. Currently a placeholder (while (true)), but sets the foundation for further game logic integration.
7. Entry Point​
-
Centralised main():
Handles:-
Initialisation of the logging system.
-
Creation and execution of the client application.
-
Clean-up via memory management (delete app).
-
8. Vendor Library Integration
-
Third-Party Dependency Management:
Includes and integrates the spdlog library for logging, with clear project paths for source and header files.
9. Cross-Module Communication
-
Shared Functionality Export:
The engine exposes its core functionality to the client application through exported symbols (e.g., MY_GAME_ENINGE_API).
Technologies Used
Programming Language
-
C++:
-
The primary language for implementing the game engine and client application.
-
Features like object-oriented programming, manual memory management, and preprocessor directives are used extensively.
-
Logging Library
-
spdlog (take from a github repository):
-
A fast, header-only logging library integrated into the engine for high-performance logging.
-
Supports coloured console output, formatted messages, and adjustable log levels.
-
Build System
-
Premake (or similar build configuration tool):
-
Likely used to generate project files and manage builds.
-
Handles build configurations (Debug, Release, Dist) and platform-specific settings.
-
Object-Oriented Design
-
Inheritance and Polymorphism:
-
The Sandbox class inherits from My_Game_Engine::Application, allowing customisation of application behaviour.
-
Code Example
1. Preprocessor Directives and Platform Configuration
-
#pragma once: Ensures the header file is only included once during compilation.
-
__declspec(dllexport) and __declspec(dllimport):
-
Used to define whether symbols are exported (for DLL creation) or imported (for client-side usage).
-
The MY_GAME_ENINGE_API macro toggles between these based on whether MGE_BUILD_DLL is defined.
-
-
#ifdef MGE_PLATFORM_WINDOWS: Ensures the engine only builds on Windows. If another platform is detected, compilation stops with an error.
2. Utility Macros
-
BIT(x): Creates a bitmask by shifting 1 left by x bits. For example, BIT(3) results in 00001000 in binary.
3. Sandbox Class
-
Sandbox: A client application that inherits from the engine's Application class.
-
CreateApplication(): A factory function defined by the engine but implemented in the client. It creates and returns an instance of Sandbox.
4. Logging System
-
Uses the spdlog library for logging.
-
Two loggers:
-
s_CoreLogger: For engine-specific logs.
-
s_ClientLogger: For client application logs.
-
-
Log::Init(): Initialises both loggers with a custom log pattern.
-
Macros simplify log calls:
5. Core Engine Functionality
-
Application: Base class for client applications.
-
Run(): Contains the main application loop (while (true) for now).
Implementation
-
Run(): Simplified for now but serves as the core game/application loop.
6. Entry Point
-
Defines main() as the entry point for applications.
-
Steps:
-
Initialise the logging system.
-
Create the client application via CreateApplication().
-
Run the application loop.
-
Clean up memory.
-
7. Build System Configuration
-
Defines My_Game_Engine as a shared library (DLL).
-
Adds necessary headers and source files.
-
Specifies build settings for different configurations (Debug, Release, Dist).
Sandbox Project
-
Defines Sandbox as a console application.
-
Links against the My_Game_Engine library.
Technical Challenges Overcome
-
Modular API Design:
Ensured the engine's API supports future scalability, allowing developers to easily extend or modify its core functionalities. -
Cross-Module Logging:
Developed a shared logging infrastructure that distinguishes between engine-level and application-level messages, enhancing clarity during debugging. -
Dynamic Linking:
Addressed the complexities of building and managing DLLs for Windows, ensuring seamless export/import of symbols across modules.
Future Goals
-
Introduce a robust event system for handling user input and internal messaging.
-
Develop a graphics rendering pipeline leveraging modern APIs like DirectX or Vulkan.
-
Build a basic physics engine to handle collision detection and response.
-
Add support for cross-platform deployment by abstracting platform-specific dependencies.