Project
My Game Engine
C++23 engine with SDL3, Vulkan 1.3, and a modular gameplay library.
Game Engine, Tools, Runtime
28-02-2026
Project
C++23 engine with SDL3, Vulkan 1.3, and a modular gameplay library.
Game Engine, Tools, Runtime
28-02-2026
Highlights
This project is my "engine-first" way of building a top-down action game: a small C++23 runtime that hosts a game-agnostic engine library, plus a hot-reloadable gameplay module. The goal isn't to compete with existing engines — it's to have a codebase where I can change gameplay, rendering, UI, and tooling in the same place, and keep iteration tight.
The stack is intentionally boring in the best way:
At a high level, everything is organized around a clean boundary between “host”, “engine”, and “game”:
This split is the main reason the project stays pleasant to work on. I can rebuild just the gameplay module, have the runtime detect the updated .so/.dll, and swap it without restarting the whole program.

The hot-reload path is deliberately explicit: the runtime host doesn't try to be “magical” — it just asks the module to serialize and deserialize its own state.
The loop looks like this:
Game instance.Game, and restore the saved buffer.Two details that matter in practice:
reloadRequested flag. The game can poll that via a callback and decide when to request reload (e.g. between frames).That combination gives you the “edit → compile → alt-tab → continue playing” loop, while still being honest about what can and can't be preserved.
The engine is intentionally “library-shaped”. Instead of a giant base class you inherit from, you wire it together through a small API:
Engine::pollEvents() and Engine::update(dt) advance input/window/audio state.Engine::render() executes the render path.I like this shape because it forces a contract:
On top of that sits a minimal scene framework (enter/exit/update) that the gameplay module uses for main menu vs gameplay, transitions, and so on.
The renderer is built around a sprite-batching model, but it's not “just draw sprites”. The batching data includes enough information to support lighting and post-processing without turning everything into bespoke draw calls.
Under the hood, Vulkan setup is kept pragmatic: instance/device/swapchain selection is handled via vk-bootstrap, and GPU memory allocations go through VMA so resource creation stays predictable as the renderer grows.
The key idea is: gameplay pushes a stream of sprite vertices plus a list of draw batches. Each batch describes:
From there, Vulkan becomes a predictable pipeline:
Lighting is represented as a compact GPU-friendly struct (ambient + point lights, capped to a fixed maximum), which keeps “lighting feels good” from exploding into a complex deferred system for a 2D game.
Post-processing is also first-class. The engine keeps a structured “post-process state” (vignette, LUT grading, fog, god rays, CRT, transitions), and the game can treat these like gameplay-controlled knobs rather than hard-coded effects.
One of the easiest ways to kill motivation in a hobby engine is to make assets annoying.
In dev builds, the game reads from the assets/ folder so iteration is instant. In release builds, the pipeline prefers an assets.pak next to the executable so the bundle is relocatable and self-contained.
The runtime host does the boring work:
The AssetManager sits underneath that and exposes a simple “virtual path → bytes” API. It supports disabling filesystem fallbacks entirely in pack mode, which is a small but powerful way to detect missing assets early.
The build is Meson/Ninja, and the project is structured so clangd can pick up exact compilation flags from compile_commands.json.
For shipping builds, the tooling does three things:
I also keep a Doxygen config in-repo so the engine/game boundary stays documented as it evolves.
The engine is “small” in the sense that it avoids sprawling abstractions, but it's not a toy. The project has enough structure to support real features (lighting, particles, UI, persistence, packaging) while still being easy to change.
The big wins so far:
If you're watching the demo videos and wondering “why build all this instead of using X?”, the honest answer is: because the engine is the project. The fun part is making the architecture real.