Getting Started
This guide walks you through every step: installing dependencies, building the library, integrating it into your CMake project, and writing your first game.
Requirements
| Dependency | Min version | Notes |
|---|---|---|
| CMake | 3.21 | Modern target-based build. Earlier versions lack target_sources(FILE_SET). |
| C++ compiler | C++23 | GCC 13+, Clang 16+, MSVC 2022 17.5+ (v143). C++23 is required for std::expected and deducing this. |
| U3D (Urho3D fork) | any recent | Must be pre-built. Default search path: /rv/data/library/github.com/u3d-community/U3D/cmake-build-debug. Override with -DU3D_HOME=…. |
| Git | any | For cloning the repository. |
System Packages
Debian / Ubuntu
# C++ toolchain sudo apt install gcc-13 g++-13 cmake ninja-build git # U3D runtime dependencies sudo apt install \ libsdl2-dev libsdl2-image-dev \ libbullet-dev \ liblua5.1-dev libluajit-5.1-dev \ libenet-dev \ libfreetype6-dev \ libassimp-dev # Use gcc-13 as default sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 130
Fedora / RHEL
sudo dnf install gcc13 g++13 cmake ninja-build git \ SDL2-devel bullet-devel lua-devel luajit-devel \ enet-devel freetype-devel assimp-devel
macOS (Homebrew)
brew install cmake ninja gcc@13 sdl2 bullet lua luajit enet freetype assimp
Note — Ninja
All build commands below use the Makefile generator. Replace -j$(nproc) with
-GNinja and drop -j for faster incremental builds:
cmake -S . -B build -GNinja && ninja -C build
Build — U3D Backend (default)
The U3D backend links against the u3d-community/U3D fork and is the recommended target for Linux, Windows, and macOS desktop development.
Clone simple-3d
git clone https://github.com/openeggbert/simple-3d cd simple-3d
Configure (U3D in default location)
cmake -S . -B build-u3d -DCMAKE_BUILD_TYPE=Debug
If U3D is not in the default path, specify it explicitly:
cmake -S . -B build-u3d \ -DCMAKE_BUILD_TYPE=Debug \ -DU3D_HOME=/path/to/U3D/cmake-build-debug
Build
cmake --build build-u3d -j$(nproc)
The static library libsimple3d.a (Linux) or simple3d.lib (Windows) is produced in build-u3d/.
Run sample (optional)
./build-u3d/samples/blupi_proto/blupi_proto
Build — NOVA3D Backend
NOVA3D is Robert Vokac's Urho3D fork with an XNA C++ runtime underneath. The public API of simple-3d is identical between backends.
cmake -S . -B build-nova3d \ -DSIMPLE3D_ENGINE=NOVA3D \ -DNOVA3D_HOME=/path/to/nova3d/build cmake --build build-nova3d -j$(nproc)
Windows — MinGW Cross-Compile
Cross-compile from Linux to Windows using the MinGW-w64 toolchain bundled with simple-3d.
Install mingw-w64 first:
# Debian/Ubuntu sudo apt install mingw-w64 # Configure and build cmake -S . -B build-windows \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \ -DSIMPLE3D_ENGINE=U3D cmake --build build-windows -j$(nproc)
The toolchain file sets up the x86_64-w64-mingw32 cross-compiler and configures
DLL search paths automatically. The resulting .exe and .dll files can
be copied to a Windows machine and run without further setup.
Windows native build
Native Windows builds (MSVC or MinGW on Windows itself) are supported but not regularly tested in CI. The CMake configuration is identical; use the Visual Studio 2022 generator or a Windows MinGW installation.
Web / Emscripten (Planned)
Web support via Emscripten is defined in CMakeLists.txt but not yet tested end-to-end
(Task #9). The build command is:
# Requires Emscripten SDK activated in current shell: # source /path/to/emsdk/emsdk_env.sh emcmake cmake -S . -B build-web -DSIMPLE3D_ENGINE=U3D cmake --build build-web -j$(nproc)
Status
This build path is experimental. Track progress in Task #9 in the project's issue tracker.
CMake Options Reference
| Option | Default | Description |
|---|---|---|
SIMPLE3D_ENGINE |
U3D |
Backend selection. Values: U3D, NOVA3D. |
U3D_HOME |
auto-detect | Path to the U3D build directory (where U3DConfig.cmake lives). |
URHO3D_HOME |
fallback | Alias for U3D_HOME. Checked if U3D_HOME is not set. |
NOVA3D_HOME |
auto-detect | Path to the NOVA3D build directory. Used only when SIMPLE3D_ENGINE=NOVA3D. |
SIMPLE3D_BUILD_TESTS |
OFF |
Build unit tests. Requires Google Test. Tests do not need a display or GPU. |
SIMPLE3D_BUILD_SAMPLES |
ON |
Build sample applications (currently blupi_proto). |
CMAKE_BUILD_TYPE |
Release |
Use Debug during development for full symbols and assertions. |
CMAKE_TOOLCHAIN_FILE |
— | Pass cmake/toolchains/mingw-w64.cmake for Windows cross-compile. |
Integrating into Your Project
The recommended way to consume simple-3d is as a CMake subdirectory:
# Recommended: Git submodule
git submodule add https://github.com/openeggbert/simple-3d ext/simple-3d
# CMakeLists.txt for your game cmake_minimum_required(VERSION 3.21) project(my-awesome-game CXX) # The engine backend is set by the parent project or on the cmake command line # option(SIMPLE3D_ENGINE "U3D or NOVA3D" "U3D") add_subdirectory(ext/simple-3d simple3d) add_executable(my-game src/main.cpp src/GameplayScene.cpp src/EnemyController.cpp ) target_link_libraries(my-game PRIVATE simple3d) target_compile_features(my-game PRIVATE cxx_std_23) # Copy game data next to the executable add_custom_command(TARGET my-game POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/Data $<TARGET_FILE_DIR:my-game>/Data)
Automatic include propagation
The simple3d CMake target propagates include/ as a public include directory.
After target_link_libraries(my-game PRIVATE simple3d) you can use
#include <Simple3D/Simple3D.h> without any additional include path flags.
Recommended Project Structure
my-game/
├── CMakeLists.txt
├── ext/
│ └── simple-3d/ ← git submodule
├── src/
│ ├── main.cpp ← Game subclass + main()
│ ├── GameState.h/.cpp ← scene management
│ └── EnemyAI.h/.cpp ← gameplay logic
└── Data/ ← all runtime assets
├── Models/
│ ├── Character.mdl
│ ├── Ground.mdl
│ └── Enemy.mdl
├── Textures/
│ └── Character_D.png
├── Animations/
│ ├── idle.ani
│ ├── run.ani
│ └── jump.ani
├── Scripts/
│ ├── player.lua
│ └── enemy.lua
├── Sounds/
│ ├── jump.wav
│ └── coin.wav
├── Music/
│ └── theme.ogg
└── Maps/
└── level1.tmx
IDE Setup
VS Code with CMake Tools
- Install extensions: C/C++ (Microsoft), CMake Tools (Microsoft).
- Open the project folder. CMake Tools will detect
CMakeLists.txtautomatically. - Press Ctrl+Shift+P → CMake: Configure. Select your kit (GCC 13 or Clang 16).
- Set CMake arguments in
.vscode/settings.json:
{
"cmake.configureArgs": [
"-DU3D_HOME=/path/to/U3D/cmake-build-debug",
"-DCMAKE_BUILD_TYPE=Debug"
]
}
- Press F7 to build, F5 to debug (requires
launch.json).
CLion
- File → Open → select the project folder.
- In Settings → Build → CMake, add CMake options:
-DU3D_HOME=/path/to/U3D/cmake-build-debug - CLion automatically detects the
CMakeLists.txtand creates run configurations. - Use the built-in debugger with full variable inspection.
Your First Game
Create src/main.cpp. The complete program below creates a spinning box, a directional
light, a camera, and a HUD label — all in under 40 lines.
#include <Simple3D/Simple3D.h> using namespace Simple3D; class HelloGame : public Game { public: Entity* box = nullptr; Label* hud = nullptr; float angle = 0.0f; float elapsed = 0.0f; // Start() is called once after the engine is fully initialised. // Create all scene objects here; never in the constructor. void Start() override { SetWindowTitle("Hello simple-3d"); SetClearColor(Color(0.08f, 0.08f, 0.12f)); // A rotating box at world origin, 5 m in front of the camera. box = CreateEntity("Box"); box->AddModel("Models/Box.mdl"); box->SetPosition(0.0f, 0.0f, 5.0f); // Directional sun light. auto sun = CreateEntity("Sun"); sun->AddDirectionalLight(Color(1.0f, 0.95f, 0.85f), 1.0f); sun->SetRotation(Quaternion(50, -30, 0)); // Fill light from below. auto fill = CreateEntity("Fill"); fill->AddDirectionalLight(Color(0.3f, 0.35f, 0.5f), 0.4f); fill->SetRotation(Quaternion(-50, 30, 0)); // Default camera looks at world origin from Z=- CreateCamera(); // On-screen timer. hud = CreateLabel("Time: 0.0 s | Press ESC to quit"); hud->SetPosition(20, 20); hud->SetFontSize(16); hud->SetColor(Color(0.8f, 0.8f, 0.8f)); } // Update() is called every frame. dt = seconds since last frame. void Update(float dt) override { angle += dt * 60.0f; elapsed += dt; box->SetRotation(Quaternion(angle, angle * 0.4f, 0)); hud->SetText("Time: " + std::to_string((int)elapsed) + " s | ESC to quit"); if (IsKeyDown(Key::Escape)) Quit(); } }; int main() { return CreateGame<HelloGame>()->Run(); }
Your First Lua Script
Any entity can have a Lua script attached. The script receives the same lifecycle callbacks as
C++ code. Scripts live in the Data/Scripts/ folder.
Step 1 — Attach the script in C++:
// In Start(): box->AddScript("Scripts/spin.lua");
Step 2 — Create Data/Scripts/spin.lua:
-- spin.lua: Rotate the entity around Y axis. local angle = 0 function Start() entity = self.node -- simple-3d convention: alias self.node end function Update(dt) angle = angle + dt * 90 -- 90 degrees per second entity:SetRotation(Quaternion(angle, 0, 0)) end
The entity convention
In Urho3D Lua, the owning Node is always accessible as self.node.
Simple3D convention is to copy it to a module-level entity variable in Start()
for cleaner syntax in Update() and event callbacks.
Lua script with animation and physics
-- Scripts/player.lua speed = 6 jumpForce = 8 local anim function Start() entity = self.node anim = entity:GetAnimationController() if anim == nil then print("[player.lua] WARNING: no AnimationController found") end 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 local moving = move:Length() > 0 if moving then entity:MoveRelative(move:Normalized() * speed * dt) end if anim then anim:Play(moving and "run" or "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
Asset Paths
All asset paths are relative to Urho3D's resource search directories. By default, simple-3d adds the following directories to the search path at startup:
- The directory where the game executable resides.
- The current working directory at the time of launch.
- Each directory also has its
Data/sub-folder added automatically.
This means if your executable is at build/my-game and assets are in
build/Data/, the path "Models/hero.fbx" resolves to
build/Data/Models/hero.fbx.
Canonical directory layout
my-game/ ← project root
├── CMakeLists.txt
└── Data/ ← all game assets
├── Models/ ← 3D meshes (OBJ, FBX, MDL, GLTF)
├── Textures/ ← diffuse, normal, specular maps (PNG, JPG, DDS)
├── Animations/ ← skeletal animations (.ani)
├── Materials/ ← Urho3D material definitions (.xml)
├── Techniques/ ← custom render techniques (.xml)
├── Scripts/ ← Lua scripts (.lua)
├── Sounds/ ← sound effects (WAV, OGG — short files)
├── Music/ ← background music (OGG, WAV — streamed)
├── Maps/ ← Tiled tile maps (.tmx)
├── Sprites/ ← 2D sprites (PNG) and Spriter files (.scml)
└── Fonts/ ← fonts for UI labels (.ttf, .otf)
Reference assets with paths relative to Data/:
// Correct: entity->AddModel("Models/Character.mdl"); // → Data/Models/Character.mdl entity->AddScript("Scripts/player.lua"); // → Data/Scripts/player.lua PlaySound("Sounds/jump.wav"); // → Data/Sounds/jump.wav LoadTileMap("Maps/level1.tmx"); // → Data/Maps/level1.tmx
Supported Asset Formats
| Category | Formats | Notes |
|---|---|---|
| 3D models | OBJ, FBX, GLTF, MDL | MDL is Urho3D's native binary format (faster loading). FBX and GLTF are imported via Assimp at build time. |
| Textures | PNG, JPG, DDS, KTX | DDS with mipmaps is preferred for GPU efficiency. Urho3D auto-generates mipmaps from PNG/JPG. |
| Skeletal animation | ANI (Urho3D native) | Export from FBX/GLTF using Urho3D's AssetImporter tool. |
| Sound effects | WAV, OGG | Short sounds. Loaded fully into memory. Keep under ~2 MB. |
| Music | OGG, WAV | Streamed from disk. OGG recommended (smaller files, same quality). |
| 2D sprites | PNG, JPG | Transparent backgrounds require PNG. |
| Animated sprites | SCML (Spriter) | Spriter 2D animation definition format. |
| Tile maps | TMX (Tiled) | Tiled Map Editor format. Orthographic and isometric supported. |
| Materials | XML (Urho3D) | Optional — simple-3d applies default materials when none is specified. |
| Scripts | LUA | Plain UTF-8 Lua 5.1 / LuaJIT compatible scripts. |
Debugging
Debug vs Release build
Always develop with -DCMAKE_BUILD_TYPE=Debug. This enables:
- Full debug symbols (
-g) for GDB/LLDB. - Urho3D assertions on invalid API usage.
- simple-3d log messages at
LOG_DEBUGlevel. - Addresses sanitizer (add
-DCMAKE_CXX_FLAGS="-fsanitize=address"if needed).
Console log output
simple-3d forwards all Urho3D log messages to stdout. Look for lines starting with
[Warning] or [Error] when assets fail to load.
# Run with verbose logging:
./my-game 2>&1 | tee game.log
Common runtime errors
| Message | Cause | Fix |
|---|---|---|
Failed to load resource Models/hero.mdl |
Asset not found in any resource path. | Check that Data/ is next to the executable and the file exists. |
NULL pointer in Entity::AddRigidBody |
Calling physics methods before Start() completes. |
Never call engine API from constructors — use Start(). |
| Blank window, no 3D scene | No camera created, or camera not looking at scene. | Call CreateCamera() in Start(). Default position is Z=0 looking toward +Z. |
| Entity falls through floor | Static rigid body has no AddBoxCollider / AddMeshCollider. |
Always pair AddRigidBody(0.0f) with a collider call. |
| Lua script not running | File path wrong, or script has a syntax error on load. | Check log for Failed to load Script; run luac -p yourscript.lua to validate syntax. |
Build Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Could not find U3DConfig.cmake |
U3D_HOME not set or wrong path. |
Pass -DU3D_HOME=/path/to/U3D/build to CMake. |
error: 'std::expected' was not declared |
Compiler does not support C++23. | Upgrade to GCC 13+ or Clang 16+. |
undefined reference to Urho3D::… |
U3D library not found at link time. | Check U3D_HOME points to a fully built U3D (run ls $U3D_HOME/lib). |
LuaJIT not found |
U3D was built without LuaJIT, or headers missing. | Install libluajit-5.1-dev and rebuild U3D with -DURHO3D_LUA=ON -DURHO3D_LUAJIT=ON. |
linker: multiple definition of Simple3D::… |
simple-3d added twice as a CMake subdirectory. | Use if(NOT TARGET simple3d) guard before add_subdirectory. |