A hobby 2D game engine
written in C++
A free and open-source engine for building 2D games — with a C++ core and gameplay scripted in Lua.
Features
A focused toolkit for building 2D games — fast rendering, real physics, and a scripting workflow that keeps you in the flow.
Rendering
A modern, GPU-accelerated 2D pipeline.
SDL3 + SDL_GPU
Sprites render at a fixed logical resolution into an offscreen target, then upscale cleanly to any window size.
Materials & HLSL shaders
Per-material custom sprite shaders, authored in HLSL and cross-compiled at runtime — no offline build step.
Sprite animation
Define animation clips and play them per entity with a simple sprite animator.
World-space camera
Pan and zoom a world camera with optional physics interpolation for smooth motion.
Physics
Real 2D physics built on Box2D.
Box2D simulation
Rigid bodies and colliders driven by a fixed-timestep loop with configurable sub-steps.
Raycasting & queries
Cast rays into the world for line-of-sight, ground checks, and gameplay queries.
Collision & trigger events
Components receive on_collision_enter/exit and
on_trigger_enter/exit callbacks automatically.
Entity & Components
A straightforward composition model: each entity owns a list of components.
Lifecycle hooks
Components hook into init, enter_play,
exit_play, tick, physics_tick, and more.
Composition over inheritance
Build behaviour by attaching components — mix and match without deep class trees.
Priority ordering
Control the order components tick so input runs before gameplay logic.
Lua Scripting
All gameplay code is written in Lua, with hot reload while the game runs.
Component inheritance
Share behaviour across components with single inheritance.
Data-driven prefabs
Compose entities from component blocks as declarative data — no boilerplate constructor code.
Hot reload
Edit scripts, assets, and config and see changes without restarting the engine.
DefineComponent.Player = { speed = 7.0, } ---@class Player : LuaComponent local Player = Player function Player:init() end function Player:enter_play() end function Player:exit_play() end function Player:tick(delta_time) -- per-frame game logic end function Player:physics_tick(fixed_delta_time) -- fixed-timestep movement end
DefineEntity.Player = { ticking = true, input = {}, character_body = { collision_layer = Collision.Kinematic, collision_mask = Collision.Static | Collision.Dynamic, solver_ignore_mask = Collision.Trigger, capsule = Capsule(Vector2.zero(), Vector2.zero(), 1.2), }, sprite = { texture = Textures.PlayerIdle01, material = Materials.WhiteOutline, z_index = 1, }, -- attach the Player component lua_components = { Components.Player }, }
-- spawn the prefab at a world position EntitySpawner.spawn_entity(Entities.Player, Vector2(0.0, 0.0))
Input Mapping
Bind keys to named actions and axes in JSON — no rebuild to remap controls.
Named actions
Map multiple keys to a single action so gameplay code never references raw key codes.
Smoothed axes
Axis mappings blend opposing keys with configurable acceleration and deceleration.
{
"action_mappings": {
"jump": ["Space", "W"],
"interact": ["E"],
"cancel": ["Escape"]
},
"axis_mappings": {
"horizontal": {
"acceleration": 10,
"deceleration": 10,
"positive": ["Right", "D"],
"negative": ["Left", "A"]
}
}
}
function Player:enter_play() local input = self.entity:get_input() -- react to a smoothed axis input:bind_axis("horizontal", function(axis) self.movement.x = axis end) -- react to a named action input:bind_action("jump", InputEventType.Pressed, function() -- ... end) end
Dev Tooling
A debug-first workflow baked into the engine.
ImGui dev console
A drop-down developer console, toggled with the backtick key, for commands and live tweaking.
Typed CVars
Bool / Int / Float / String console variables with Archive, ReadOnly, and Cheat flags.
Debug drawing
Draw world-space lines, circles, and text that stay crisp across DPI and resolution.
Cross-Platform
One codebase, three desktop platforms.