simple-3d

A clean, minimalistic 3D/2D game framework for C++23 and Lua.

Write game logic against a stable, engine-agnostic API. No engine internals leak into your code. Subclass Game, override Start() and Update(), ship.

C++23 CMake 3.21+ Lua / LuaJIT 3D + 2D Bullet Physics Box2D Recast Navigation ENet Networking Tiled Maps Cross-platform

What is simple-3d?

simple-3d is a high-level C++ game framework built as a clean wrapper around an Urho3D engine fork. It exposes a minimal, game-first API without requiring you to understand any of the underlying engine's internal types, subsystems, or component model.

Urho3D types (Urho3D::Node, Urho3D::Scene, Urho3D::Application, etc.) are never visible in the public headers. They are hidden behind PIMPL classes. The only exception is the math library — Vector2, Vector3, Color, Quaternion and friends are re-exported as Simple3D:: type aliases because they are ABI-identical and there is no benefit to wrapping them further.

The result: you write entity->AddRigidBody(1.0f) and simple-3d handles the Urho3D::RigidBody component, GetOrCreateComponent, collision mask setup, and physics world registration behind the scenes.

Why simple-3d?

Zero engine coupling

Your game code never includes a single Urho3D header. Swap the backend from U3D to NOVA3D with one CMake flag — zero source changes.

Designed for game code

API methods match what you think about while writing a game: "give this entity a capsule collider", "make the camera follow the player". Not engine primitives.

Lua for gameplay logic

Per-entity Lua scripts with Start(), Update(dt), and Stop(). Reloadable at runtime without recompiling the engine.

Full 3D + 2D in one

3D physics (Bullet), 2D physics (Box2D), sprites, tile maps, orthographic camera, and nav-mesh — all in the same library with one unified API surface.

No boilerplate

10 lines for a complete game loop, physics entity, animated character, and follow camera. Everything a game needs, nothing an engine demo needs.

Battle-tested backend

Built on top of an Urho3D fork — a mature engine with Bullet, Recast/Detour, ENet, LuaJIT, and bgfx rendering support.

Key Features

Entity system

Create scene objects with CreateEntity(name). Attach models, colliders, lights, animation controllers, and Lua scripts in any order.

Physics — 3D (Bullet)

Dynamic and static rigid bodies, box/capsule/sphere/mesh colliders, trigger volumes with enter/exit callbacks, impulse/force/velocity control, collision layers.

Physics — 2D (Box2D)

2D rigid bodies, box and circle colliders, linear velocity, impulse, ground detection. Runs alongside the 3D physics world in the same scene.

Camera modes

Third-person follow, orbit (RMB drag), first-person FPS (mouse captured), orthographic 2D, split-screen, smooth lerp, collision avoidance.

Navigation

BuildNavMesh() bakes from static geometry. entity->MoveToward(target, speed, dt) auto-navigates. FindPath() returns waypoints.

Lua scripting

Per-entity .lua scripts via Urho3D's LuaScriptInstance. Simple3D adds custom bindings: MoveRelative, IsOnGround, ApplyImpulse, input helpers.

Animation

Skeletal animation via AnimationController. Named clips, per-layer blending, fade in/out, weight control, speed multiplier.

Networking

ENet-based client/server. Scene entities auto-replicate. Custom messages (IDs 100–32767) with raw byte payloads. Typed connection callbacks.

Audio

Stream OGG/WAV music with loop control. One-shot SFX with per-call volume. Master volume. Spatial 3D audio planned.

2D rendering

Static sprites, Spriter animated sprites (.scml), flip control, Tiled tile map loading (.tmx).

Input

Keyboard (held / first-frame), mouse position / delta / buttons, multi-touch with pressure. Gamepad planned.

UI labels

On-screen text (HUD): screen position, font size, RGBA color, visibility toggle. Full UI panel/button system planned.

Architecture at a glance

Game Code (C++ / Lua) ↓ simple-3d API ← stable public boundary; no engine types visible here ↓ Urho3D fork (U3D) ← scene graph, physics, navigation, networking, Lua ↓ XNA C++ runtime ← rendering, audio, input (NOVA3D backend only) ↓ Vulkan / GLES / bgfx ← GPU backends ↓ SDL3 / OS ← windowing, events

Every public class in simple-3d uses the PIMPL idiom: the .h file declares struct Impl; and stores it in a unique_ptr<Impl>. The .cpp file defines Impl and can include any Urho3D header it needs. This guarantees that rebuilding the engine never forces recompilation of game code.

Full architecture documentation →

Quick Start (C++)

One header, one subclass, four methods — that is everything required for a complete interactive game.

#include <Simple3D/Simple3D.h>
using namespace Simple3D;

class MyGame : public Game {
public:
    Entity* player;
    Camera* camera;

    void Start() override {
        // ── Scene ────────────────────────────────────────────
        auto ground = CreateEntity("Ground");
        ground->AddModel("Models/Ground.mdl");
        ground->AddRigidBody(0.0f);                      // 0 = static
        ground->AddBoxCollider(Vector3(100, 1, 100));

        // ── Player ───────────────────────────────────────────
        player = CreateEntity("Player");
        player->AddModel("Models/Character.mdl");
        player->AddAnimationController();
        player->AddRigidBody(70.0f);
        player->AddCapsuleCollider(0.4f, 1.75f);
        player->SetPosition(0, 2, 0);
        player->AddScript("Scripts/player.lua");

        // ── Lighting ─────────────────────────────────────────
        auto sun = CreateEntity("Sun");
        sun->AddDirectionalLight(Color(1.0f, 0.95f, 0.85f), 1.0f);
        sun->SetRotation(Quaternion(50, -30, 0));

        // ── Camera ───────────────────────────────────────────
        camera = CreateCamera();
        camera->Follow(player, 6.0f, 2.5f);
        camera->SetSmoothFollow(true, 7.0f);
        camera->SetCollisionEnabled(true);

        // ── Audio ────────────────────────────────────────────
        PlayMusic("Music/theme.ogg");
    }

    void Update(float dt) override {
        if (IsKeyDown(Key::Escape)) Quit();
    }
};

int main() {
    return CreateGame<MyGame>()->Run();
}

Build instructions →  ·  Full C++ API reference →

Lua Script Example

Attach scripts to individual entities. Each script has its own isolated Lua state with access to the owning entity, input, audio, and scene. Scripts are hot-reloadable at runtime.

-- Scripts/player.lua
-- Loaded via entity->AddScript("Scripts/player.lua") in C++

speed     = 6    -- m/s horizontal movement
jumpForce = 8    -- impulse on Space
local anim         -- AnimationController reference

function Start()
    entity = self.node   -- Simple3D convention: alias self.node
    anim   = entity:GetAnimationController()
end

function Update(dt)
    local move = Vector3(0, 0, 0)

    if Input:IsKeyDown("W") then move.z = move.z + 1 end
    if Input:IsKeyDown("S") then move.z = move.z - 1 end
    if Input:IsKeyDown("A") then move.x = move.x - 1 end
    if Input:IsKeyDown("D") then move.x = move.x + 1 end

    if move:Length() > 0 then
        entity:MoveRelative(move:Normalized() * speed * dt)
        anim:Play("run", 0, true, 0.2)
    else
        anim:Play("idle", 0, true, 0.2)
    end

    if Input:IsKeyPress("Space") and entity:IsOnGround() then
        entity:ApplyImpulse(Vector3(0, jumpForce, 0))
        PlaySound("Sounds/jump.wav")
    end
end

Full Lua API reference →  ·  More examples →

Module Status

ModuleStatusC++ APILua bindingNotes
Core ✅ Done Game class Game loop, window, types, time
Entity ✅ Done Entity Via self.node PIMPL over Urho3D::Node, registry
Camera ✅ Done Camera Follow, FPS, orbit, ortho, split-screen
Audio ✅ Done Audio methods 🔄 PlayMusic, PlaySound, master volume
Input ✅ Done Input methods Via Input: Keyboard, mouse, touch
Physics 3D ✅ Done Entity physics Via entity extensions Bullet — rigid bodies, triggers, layers
Physics 2D ✅ Done Entity physics 2D Box2D — rigid body, box/circle
Animation ✅ Done Entity animation Via GetAnimationController() Skeletal, blend layers, weights
Navigation ✅ Done Navigation 📋 Recast/Detour, MoveToward, FindPath
Networking ✅ Done NetworkManager ENet, client/server, auto-replication
2D sprites ✅ Done Entity 2D Static + animated Spriter
Tile maps ✅ Done LoadTileMap Tiled .tmx format
UI — Label ✅ Done Label HUD text, position, color, visibility
Lua scripting 🔄 In progress Lua API Core done; custom bindings Task #3
Full UI system 📋 Planned Panels, buttons, images — Task #17
Scene management 📋 Planned Level transitions — Task #18
Spatial audio 📋 Planned 3D positional sound — Task #23
Android 📋 Planned NOVA3D backend — Task #8
Web / Emscripten 📋 Planned Task #9

Backends

The same simple-3d source works with two different engine backends. Switch with a single CMake variable; no changes to game code are needed.

U3D (default)

Links against the upstream u3d-community/U3D Urho3D community fork. Stable Bullet physics, LuaJIT scripting, Recast/Detour navigation, ENet networking. Supports Linux, Windows, and macOS desktop builds.

cmake -S . -B build \
  -DSIMPLE3D_ENGINE=U3D \
  -DU3D_HOME=/path/to/u3d/build

NOVA3D

Links against Robert Vokac's Urho3D fork which adds an XNA C++ runtime underneath. Same public API surface as U3D. Primary target for Android and experimental hardware. Identical game source builds against either backend.

cmake -S . -B build \
  -DSIMPLE3D_ENGINE=NOVA3D \
  -DNOVA3D_HOME=/path/to/nova3d/build

Comparison with Direct Urho3D Use

AspectDirect Urho3Dsimple-3d
Game entry point Subclass Urho3D::Application, wire subsystems, create context CreateGame<MyGame>()->Run()
Create a physics object Create Node, GetOrCreateComponent<RigidBody>, GetOrCreateComponent<CollisionShape>, set shape type, set mass, add to world entity->AddRigidBody(1.0f); entity->AddCapsuleCollider(0.4f, 1.8f);
Engine types in headers Yes — Urho3D::Node*, Urho3D::Scene*, etc. in game .h files No — only Simple3D:: aliases; no Urho3D includes needed
Camera follow player Write follow logic in Update, handle offset and smoothing manually camera->Follow(player, 6.0f); camera->SetSmoothFollow(true);
Nav-mesh pathfinding Set up NavigationMesh component, NavigationAgent, handle path callbacks, implement steering BuildNavMesh(); enemy->MoveToward(player, 3.0f, dt);
Backend portability Tightly coupled to Urho3D Swap U3D ↔ NOVA3D with one CMake flag

License

simple-3d is open source. See the LICENSE file in the repository for the full text. The underlying Urho3D fork (U3D) is distributed under the MIT license.