Skip to content

2D Game Engine

A C++ engine where games are Lua components and JSON scenes, with Box2D physics and a real-time fluid sim.

A component-based 2D engine in C++ on SDL2. The engine binary never changes between games: actors, scenes and prefab templates are JSON files, behaviour is Lua components with Unity-style lifecycle hooks, and Box2D, particles, audio, text and a grid-based fluid simulation are all exposed to scripts through one API.

Games as data

A game is a resources/ folder. game.config names the initial scene and window title, rendering.config sets resolution, clear colour, zoom and camera easing, and each .scene file lists its actors: a name, an optional template from resources/actor_templates, and a map of components with their property overrides. RapidJSON parses all of it at load time, so a new level or a new kind of enemy costs a file, not a compile.

Scenes can be switched from script, and actors marked DontDestroy survive the transition. Templates double as prefabs: Actor.Instantiate copies one at runtime, components included.

Lua components

Every .lua file in resources/component_types defines a component type. Each instance is a fresh Lua table whose metatable __index points at the type table, so instances share behaviour while JSON overrides land on the instance alone.

The engine calls OnStart once, then OnUpdate and OnLateUpdate every frame, and OnDestroy on removal. Whether a component defines those hooks is cached on load, so the frame loop does not look up functions that are not there. A Lua error is reported against the actor’s name instead of taking the engine down.

Changes made during a frame are deferred: added or removed components, instantiated or destroyed actors and event subscriptions all apply at the end of the frame, so a script can never pull the actor list out from under the loop that is iterating it.

LuaBridge binds the rest of the engine into the same namespace: Actor lookup and instantiation, Input for keys and the mouse, Camera, Scene, Audio, Text, Image drawing, Physics raycasts, and a publish/subscribe Event bus.

Physics

Rigidbody is a native C++ component wrapping a Box2D body, exposed to Lua with the same property-and-method surface as a scripted one: body type, box or circle collider, a separate trigger shape, friction, bounciness, gravity scale, plus forces, velocity and rotation. The Box2D world is only created once the first rigidbody exists.

A contact listener routes Box2D contacts back into Lua as OnCollisionEnter/Exit or OnTriggerEnter/Exit, with the other actor, the contact point, normal and relative velocity. Raycast and RaycastAll return the closest hit or every hit along a ray.

Fluid simulation

FluidSystem is a grid-based incompressible fluid after Jos Stam’s stable fluids: add sources, advect velocity, project it to be divergence-free with a 20-iteration Gauss–Seidel pressure solve, then advect a dye density through the result. The grid is sized from the window, one cell per four pixels, and the density is shaded into a pixel buffer that streams into an SDL texture every frame.

It is coupled to Box2D both ways. A moving rigidbody injects thrust and dye from three emitters behind it, leaving a wake, and the fluid velocity under it applies a capped drag force back onto the body. ApplyWhirlpool adds a vortex with a tangential swirl and inward suction that falls off with distance.

Rendering and particles

Drawing is request-based. Scripts queue world-space images, screen-space UI and text through the Image and Text APIs; images carry a sorting order, and the engine stable-sorts and draws everything once per frame relative to a scriptable, zoomable camera.

ParticleSystem stores particles as a structure of arrays in a ring buffer sized up front for the most particles the emitter can have alive, so a running emitter retires and reuses slots without allocating. Emission angle, radius, speed, initial rotation, spin and scale are randomised within configurable ranges. Particles respond to gravity and drag, and can scale and blend toward end colours over their lifetime.

Build

premake generates Visual Studio, Xcode and Makefile projects from one script, with Box2D built as a static library alongside the engine. It finds the vendored SDL frameworks and DLLs automatically, setting rpaths on macOS and copying DLLs next to the binary on Windows.

facts
StatusLive
Year2026
RoleSolo

Stack

  • C++17
  • SDL2
  • Lua 5.4
  • LuaBridge
  • Box2D
  • RapidJSON
  • premake
highlights
  • Games are data: JSON scenes and actor templates, Lua components, no recompile.
  • Lua lifecycle hooks (OnStart, OnUpdate, OnLateUpdate, OnDestroy) plus Box2D collision and trigger callbacks.
  • A Stam-style stable-fluids solver that rigidbodies push through and are dragged by.
  • Native C++ components for the hot paths: rigidbodies, a particle system, the fluid grid.