Mingin Game Engine
Game Engine - Programming 4 @ DAE
For my second year at DAE Howest, for the course Programming 4, I had to build a Game Engine and use it to create a game called Qbert.
Check out the RepoDownload the Game
Game Engine Details & Design Choices
Command Pattern
The Input System uses commands to trigger specific events. You can set this up by either overriding the "Command" and "Execute" functions, or by using "CommandFunc" and passing a function pointer (no parameters and void return type). If you want to pass parameters, you can bind them when creating the CommandFunc.
Flyweight Pattern
The flyweight technique is used for managing resources like textures. When you request a Texture2D, you pass in the file path, and if it exists, you get the texture back. Internally, the system stores resources in a map with file paths as the keys. This ensures each resource is loaded only once, saving memory.
Observer Pattern
If you need to listen to events from certain objects without direct calls, you can add a subject to them. You create an Observer class, define your event methods, and connect it with the Subject class. When you want to notify listeners, you call "notify" on the subject and pass in the observer's function. You can also bind additional parameters when needed.
Singleton
I have two types of singletons: a normal one with a static stack allocation and another using a static smart pointer for heap allocation. This makes using singletons easier and prevents the need to manually free the instance.
State Machine
I implemented an object-oriented state machine. You create a class inheriting from "State" and implement functions like "Enter", "Exit", "HandleInput", "Update", and "FixedUpdate". After setting up your state machine, you create instances of your custom state classes and start the machine. In your component’s update methods, you call "HandleInput" and "Update" and in "FixedUpdate", call "FixedUpdate" from the current state.
Game Loop
For the game loop, I used the "Play catch up" pattern, which helps with both physics (using Fixed Update) and regular frame updates (Update).
Component System
Each GameObject can have components attached to it, each with its own
logic. This makes components modular, so you can use them across different
objects while keeping them functional. The parent GameObject manages the
component lifecycle (Initialize, LateInitialize, Start, Update,
FixedUpdate).
"Initialize" and "LateInitialize" are only called once, while "Start" is
called each time the object becomes active (e.g., switching scenes).
Update Method
Each scene holds a collection of GameObjects, which may have child GameObjects and components. Since the game loop calls "Update" and "FixedUpdate" in the SceneManager, it lets each component have its own dedicated "Update" and "FixedUpdate" methods.
Event Queue
Right now, there's no standalone Event Queue. However, I use it in the SoundSystem. Audio processing can be CPU-intensive, so I handle it on a separate thread. When you request to play a sound, it creates a task and adds it to the queue. The audio thread checks for tasks and processes them when the queue has items.
Service Locator
I used a Service Locator for sound system management. Currently, I use SDL for sound, but I wanted flexibility in case I want to switch to a different sound library. The Service Locator makes it easy to swap services without having to rewrite code that uses the sound system.
Dirty Flag
Lastly, I added the Dirty Flag pattern for optimization. Each GameObject only stores its local transform, so to get the world transform, I used to traverse up the hierarchy every time. With the Dirty Flag, I store a cached world transform and only recalculate it when the flag is false, saving a lot of unnecessary recalculations.
Repository
Download Executable