4 years ago, I started working on a custom 2D game engine in C++. At the beginning, I needed a simple game to experiment AI techniques with, but I ended up working on a small engine (I still wanted to train AI to I moved that project to Unity).
The main features I am aiming at are:
- Entity-component system
- Fixed update and input/render update loops
- Game window with OpenGL with GLEW + GLFW
- Keyboard/mouse input
- Primitive 2D shapes rendering
- Basic shaders
- 2D physics (Box2D)
- Scene-Entity system
- Scene editor and basic scene serialization
- AI helpers
- Unit tests (Catch2)
- Sample game
At first I used SDL2 for window management and rendering, before switching to OpenGL with GLFW for more flexibility (e.g. zooming in and out with the camera). Now, I know that SDL2 supports OpenGL rendering too, so looking back at it, SDL2 wasn’t a bad choice at all (plus I’m still only rendering simple shapes like squares right now). At the same time, I like GLFW’s window API better and I need some OpenGL training anyway, so why not start with simple shapes. Next time, I’ll also have a look at SFML, but for now I want to focus on the engine systems (modules).
I have implemented the game application window, basic keyboard input and basic rendering systems, as well as a simple entity-component system with a base Actor class for game objects, a base Component class for components and their respective factories. Input and rendering is done via Components, but the developer can also add custom behavior by subclassing the Actor class for all game, as in Unreal Engine (and Godot with Nodes).
Advantage: you can implement behavior specific to one entity directly inside the Actor subclass, without using components
Disadvantage: all game objects don’t have the same exact type and have different sizes, so you cannot put them in an array/vector of GameObjects (Unity-style) combined with handles for maximum cache efficiency (see Data Locality). If you still want data contiguity, you’ll need a custom container that supports variable object size.
For now, I have something like this:
The last thing I have done is refactoring the engine structure with a lot of interfaces to allow unit testing.
You can check the code on my GitHub repos:
I’m not working on this project right now, but I’ll have to go back to it at some point, especially if I want to make an engine in a different language (e.g. Rust). This would be an opportunity to learn from my past mistakes and clean things up before I step onto a cleaner architecture.