Feature Reference
Complete breakdown of every feature in simple-3d â current status, configuration options, known limitations, and links to detailed documentation. Tasks #1â#16 are complete and not listed here; see git log for history.
Status Legend
â Done
Implemented, tested, and in the stable public API. Safe to use in production game code.
đ In Progress
Partially implemented. API exists but may have edge-case bugs or missing options. Check task notes before relying on it.
đ Planned
Designed and scheduled but not yet started. API may be drafted in headers (guarded with #if 0) for planning purposes only.
Core & Entity
The foundation of every simple-3d game. All other systems depend on these primitives. See C++ API â Game and C++ API â Entity for full method signatures.
| Feature | Status | Details & Notes |
|---|---|---|
| Game base class | â | Subclass Simple3D::Game and override Start(), Update(float dt), Stop(). The engine calls these on the main thread. dt is clamped to 0.1 s so a debugger breakpoint will not cause a physics explosion. |
| Single-include header | â | #include <Simple3D/Simple3D.h> pulls in every public type. Internal Urho3D headers are never exposed â your project's compile units see no engine symbols beyond Simple3D::*. |
| Entity creation | â | Entity* e = CreateEntity("name") creates a named scene node and registers it. Names do not need to be unique, but FindEntity() returns the first match. |
| Entity registry | â | An internal unordered_map<Node*, Entity*> keeps every entity alive and reachable. Trigger callbacks receive a stable Entity* â never a raw Urho3D pointer. Child entities are registered automatically via CreateChild(). |
| Entity hierarchy | â | Entity* child = parent->CreateChild("wheel"). Children inherit world-space transform from their parent. GetParent() resolves correctly via registry. DestroyEntity(parent) recursively unregisters the entire subtree. |
| Entity find | â | FindEntity("gem_1") searches both root entities and children via the node registry. Returns nullptr if not found. Use a unique name prefix (e.g. "gem_") for bulk lookups combined with GetAllEntities(). |
| Entity destroy | â | DestroyEntity(e) removes the node from the scene and from the entity registry. Calling DestroyEntity on a pointer that has already been destroyed is safe â it no-ops. |
| Transform | â | Position: SetPosition/GetPosition. Rotation: SetRotation/GetRotation (Quaternion). Scale: SetScale. Direction shortcuts: GetForward, GetRight, GetUp. Convenience: MoveRelative(v) translates along local axes, LookAt(target). |
| Math types | â | Full suite: Vector2, Vector3, Vector4, Color, Quaternion, Matrix3, Matrix4, Matrix3x4, BoundingBox, Sphere, Ray, Plane, Rect. All types are exposed in Lua (see Lua API â Math). |
| Window title & clear color | â | SetWindowTitle("My Game") and SetClearColor(Color(0.1f, 0.1f, 0.12f)). Both can be called at any time, not just from Start(). |
| Time access | â | GetTime() returns seconds since engine start. GetDeltaTime() is the same value passed to Update(dt). Both are thread-safe reads. |
| Scene creation helpers | â | CreateScene(), LoadSceneXML(path), CreateSkybox(path), CreateTerrain(heightmap, material), CreateDirectionalLight, CreateFog. All return the entity or void. |
| Quit | â | Quit() shuts down the engine cleanly: calls Stop(), flushes audio, destroys all entities, closes the window. |
3D Rendering
Built on Urho3D's forward renderer. All render features are accessible without writing shader code. See C++ API â Camera for camera details.
| Feature | Status | Details & Notes |
|---|---|---|
| Static model | â | AddModel("Models/box.mdl", "Materials/Stone.xml"). Accepted formats: .mdl (native Urho3D), .obj, .fbx. Convert external formats to .mdl with the Urho3D AssetImporter tool for best runtime performance. |
| Directional light | â | AddDirectionalLight(color, intensity). Simulates sunlight â affects the whole scene. Point the entity's rotation to control light direction. Supports shadow mapping (configure quality in engine XML). |
| Point light | â | AddPointLight(color, range). Range in world units; light falls off to zero at the boundary. Use sparingly on mobile â each per-pixel light has a draw-call cost. |
| Spot light | â | AddSpotLight(color, range, angle). Angle is the full cone angle in degrees (not half-angle). Spot light direction follows the entity's forward vector. |
| Camera â third-person follow | â | camera.SetFollowTarget(player, height, distance). Camera stays behind the player's last movement direction. Immediately snaps on first frame; use SetFollowSpeed() for lerp smoothing. |
| Camera â orbit | â | camera.SetOrbitTarget(entity). Right-mouse-drag orbits; scroll wheel changes distance. Configure pitch limits: SetOrbitPitchLimits(minDeg, maxDeg). Sensitivity: SetOrbitSensitivity(x, y). |
| Camera â FPS | â | camera.SetFPSTarget(entity, headOffset). Captures the mouse (SDL_SetRelativeMouseMode). Look sensitivity: SetFPSSensitivity(x, y). Press Escape to detach: camera.Detach(). |
| Camera â smooth follow | â | SetFollowSpeed(lerpFactor) â 1.0 = instant snap, 0.05 = very slow. Framerate-independent via Lerp(current, target, 1 - pow(1 - speed, dt * 60)). |
| Camera collision avoidance | â | A raycast is fired from the target entity toward the desired camera position. If it hits static geometry (collision layer 2), the camera is pulled in to avoid clipping. Enable: camera.SetCollisionAvoidance(true). |
| Camera â orthographic | â | camera.Setup2D(pixelsPerUnit) configures orthographic projection matched to sprite scale. camera.SetOrthographic(true) + camera.SetOrthoSize(height) for manual control. Used for 2D games and minimaps. |
| Split-screen | â | camera.SetViewport(index, x, y, w, h) â coordinates are 0.0â1.0 normalized. Index 0 and 1 are the two viewport slots. Each viewport has its own Camera object. See Examples â Split-Screen. |
| FOV, near/far clip | â | camera.SetFOV(75.0f) (degrees, vertical). camera.SetNearClip(0.1f). camera.SetFarClip(1000.0f). Setting near clip < 0.01 causes z-fighting artifacts. |
| LookAt | â | camera.LookAt(entity) or camera.LookAt(Vector3). Rotates the camera node to face the target. Useful for cut-scenes or fixed-perspective rooms. |
| Skybox | â | CreateSkybox("Textures/Skybox/day.xml") â loads a Urho3D Skybox XML. The default material uses 6 face textures (cubemap). Only one skybox is active at a time. |
| Fog | â | CreateFog(color, start, end). Linear fog between start and end world-unit distances. Setting start = 0 causes the clear color to merge with the fog color. |
| Particle effects | đ | Task #20. See Roadmap for planned API. Will expose Entity::AddParticleEmitter(xmlPath) + built-in preset XMLs (Sparkle, Smoke, Explosion, Dust, Stars). |
Physics â 3D (Bullet)
Powered by Bullet Physics (via Urho3D). Simulation runs at 60 Hz fixed step on the internal physics thread, independent of render framerate. See C++ API â Entity â 3D Physics.
| Feature | Status | Details & Notes |
|---|---|---|
| 3D rigid body | â | AddRigidBody(mass). mass = 0 â static body (immovable). Mass in kilograms. Uses GetOrCreateComponent internally, so calling multiple times is safe (returns existing body). |
| Box collider (3D) | â | AddBoxCollider(Vector3 size). Size is full extents (not half-extents). Centered on the entity origin unless offset is specified. |
| Capsule collider (3D) | â | AddCapsuleCollider(radius, height). Height is the total capsule height including the two hemisphere caps. The recommended shape for characters â smoother on edges than a box. |
| Sphere collider (3D) | â | AddSphereCollider(radius). Lowest-cost collision shape. Ideal for rolling objects and simple projectiles. |
| Mesh collider (3D) | â | AddMeshCollider("Models/level.mdl"). Triangle mesh â accurate to the model. Static bodies only â Bullet does not support dynamic mesh colliders. For dynamic objects, approximate with primitives. |
| Trigger volumes | â | AddTriggerBox/Sphere/Capsule(size) with SetOnTriggerEnter(cb) / SetOnTriggerExit(cb). Callbacks receive Entity* other resolved via the entity registry. Triggers default to collision layer 4; actors must have mask bit 4 set to receive trigger events. |
| ApplyImpulse / ApplyForce | â | ApplyImpulse(Vector3) â instantaneous velocity change (use for jumps). ApplyForce(Vector3) â continuous force applied each physics step (use for thrusters). Both require an AddRigidBody(mass > 0) first. |
| SetLinearVelocity / GetLinearVelocity | â | Directly sets the velocity vector in world space. Useful for character controllers before Task #21 lands. Note: setting velocity every frame overrides friction â tune as needed. |
| SetAngularVelocity / GetAngularVelocity | â | Controls spin. SetAngularFactor(Vector3(0,1,0)) locks rotation to Y-axis only â commonly used for upright characters. |
| IsOnGround() | â | Fires a short downward raycast (0.7 m) from the entity's position. Returns true if the ray hits anything on collision layer 2 (static geometry). False-negatives can occur on steep slopes (>45°). |
| Collision layers / masks | â | SetCollisionLayer(int) â bit flags for what group this body belongs to. SetCollisionMask(int) â which layers this body collides with. Convention: 1 = actors, 2 = static geometry, 4 = trigger volumes. Player mask = 2 | 4. |
| Kinematic Character Controller | đ | Task #21. Replaces raw RigidBody for player characters. Will support step-up (0.4 m default), slope sliding (>45°), and IsOnGround() from the KCC state rather than a raycast. |
| Physics constraints / joints | đ | Task #25. Planned types: Fixed, Hinge (with limits), Slider (moving platforms), BallSocket (chains). Motor support: SetMotorEnabled, SetMotorSpeed, SetMotorMaxForce. |
Physics â 2D (Box2D)
Powered by Box2D (via Urho3D). The 2D physics world operates on the XY plane. Mix 3D rendering with 2D physics by using orthographic camera + 2D rigid bodies. See C++ API â Entity â 2D Physics.
| Feature | Status | Details & Notes |
|---|---|---|
| 2D rigid body | â | AddRigidBody2D(BodyType2D::Dynamic). Body types: Dynamic (physics-driven), Static (immovable), Kinematic (script-driven, no gravity). Cannot mix 3D and 2D bodies on the same entity. |
| 2D box collider | â | AddCollisionBox2D(width, height). Center offset: optional third parameter Vector2 center. Supports rotation via entity transform. |
| 2D circle collider | â | AddCollisionCircle2D(radius). Lowest cost 2D shape â recommended for balls, characters, coins. Center follows entity position. |
| 2D velocity / impulse | â | SetLinearVelocity2D(Vector2) / ApplyLinearImpulse2D(Vector2). Gravity is applied automatically to Dynamic bodies. Disable gravity per-body: SetGravityScale2D(0.0f). |
| IsOnGround2D() | â | Downward raycast 0.6 m in 2D physics space. Returns true if the ray hits a Static or Kinematic body below. Adjust threshold with the optional rayLength parameter. |
| 2D collision categories | â | Box2D category bits and mask bits via SetCategoryBits2D / SetMaskBits2D. Use 16-bit values (Box2D limitation). Same layer convention as 3D can be used for consistency. |
Animation
Urho3D AnimationController with simple-3d convenience wrappers. Animations are stored as .ani files or embedded in exported .fbx/.mdl. See C++ API â Entity â Animation.
| Feature | Status | Details & Notes |
|---|---|---|
| AnimationController | â | AddAnimationController() attaches the controller component. Call once; subsequent calls return the existing component via GetOrCreateComponent. Returns nullptr before this call â guard access in Lua. |
| Play | â | Play(name, layer, looped, fadeTime). layer â animations on different layers blend additively. fadeTime seconds to cross-fade from any currently playing animation on that layer. 0 = instant cut. |
| PlayBlend (additive) | â | PlayBlend(name, layer, fadeTime) â plays alongside any other animation on the same layer. Use for overlay animations (aiming, hit reactions) on top of a locomotion cycle. |
| Stop / StopAll | â | Stop(name) stops a specific clip. StopAll(fadeTime) fades out every playing animation. Safe to call even if nothing is playing. |
| Blend weight | â | SetWeight(name, weight) â 0.0 = invisible contribution, 1.0 = full. Interpolate manually each frame to build custom cross-fades. GetWeight(name) returns current weight. |
| Playback speed | â | SetSpeed(name, multiplier). 1.0 = normal, 2.0 = double speed, 0.5 = half speed, -1.0 = reverse. Negative speed plays the clip backward. |
| IsPlaying | â | IsPlaying(name) â true if the clip is currently active (weight > 0 and not stopped). Use to gate transitions: only call Play("jump") if !IsPlaying("jump"). |
| GetTime | â | GetTime(name) â playback position in seconds. Use to synchronize sound effects to animation frames (e.g. footstep at 0.25 s and 0.75 s of the walk cycle). |
| Animation state machine | đ | Task #22. Declarative AnimStateMachine with states, transitions, and conditions (SetBool, SetFloat, SetTrigger â Unity Animator style). Will drive AnimationController internally. Planned blend time support per transition. |
Audio
Urho3D audio system wraps OpenAL/SDL_mixer. Streaming for music, buffer-based for SFX. See C++ API â Game â Audio.
| Feature | Status | Details & Notes |
|---|---|---|
| Music streaming | â | PlayMusic("Music/theme.ogg", loop). OGG Vorbis recommended (smaller files); WAV is also accepted. Music streams from disk â not loaded into RAM. Only one music track at a time. |
| Stop music | â | StopMusic(). Immediately halts streaming. To fade out, set SetMasterVolume() to 0 over several frames then call StopMusic(). |
| One-shot SFX | â | PlaySound("Sounds/jump.wav", volume). Volume 0.0â1.0. Sounds are cached after first load. Up to 32 simultaneous sound channels (Urho3D default). |
| Master volume | â | SetMasterVolume(0.0fâ1.0f). Affects all audio channels including music. Persisting the player's volume preference should be saved manually (or via Task #19 SaveData). |
| Missing resource logging | â | PlayMusic() and PlaySound() log an error to the console if the file is not found, instead of crashing. Check the log if audio is silent. |
| Spatial (3D positional) audio | đ | Task #23. Will expose Entity::AddAudioSource() returning AudioSource*. Features: SetGain, SetRange(near, far), SetPitch, PlayAmbient. Requires Game::SetAudioListener(Entity*) to position the listener. Also needed by Speedy Blupi enemy and footstep audio. |
Input
Polling-based input â call from Update(). No event queue to drain. See C++ API â Game â Input and Lua API â Input for the full Key enum.
| Feature | Status | Details & Notes |
|---|---|---|
| Keyboard â held | â | IsKeyDown(Key::W) â true every frame the key is physically held. Use for movement. |
| Keyboard â pressed (single frame) | â | IsKeyPressed(Key::Space) â true only on the frame the key transitioned from up â down. Use for jump and toggle actions. Does not repeat. |
| Mouse position | â | GetMousePosition() â Vector2 in screen pixels (top-left origin). Not available in FPS mode while mouse is captured. |
| Mouse delta | â | GetMouseDelta() â Vector2 pixels moved since last frame. Always valid, including in FPS mouse-capture mode where absolute position is undefined. |
| Mouse buttons | â | IsMouseButtonDown(0) â 0=left, 1=right, 2=middle. IsMouseButtonPressed(0) for single-frame click detection. |
| Touch (multi-touch) | â | GetTouchCount(), GetTouchPosition(i), GetTouchDelta(i), GetTouchPressure(i). Index 0 = first finger. Pressure is 0.0â1.0 (device-dependent; many return 1.0). |
| Gamepad / controller | đ | Task #24. Will expose IsGamepadConnected, GetGamepadAxis(GamepadAxis), IsGamepadButtonDown/Pressed(GamepadButton), rumble (SetGamepadRumble), and hot-plug callbacks. Targeting XInput (Windows) and SDL gamepad (Linux/Mac). |
Lua Scripting
LuaJIT (Lua 5.1 compatible) via Urho3D's LuaScriptInstance. One script per entity. See Lua API for the full reference.
| Feature | Status | Details & Notes |
|---|---|---|
| Script attach per entity | â | entity->AddScript("Scripts/player.lua"). The script file is loaded from the engine's resource search paths. Missing scripts log an error and skip without crashing. |
| Lifecycle callbacks | â | function Start() â called once on attach. function Update(dt) â called every frame. function Stop() â called on detach/destroy. function FixedUpdate(dt) â called at physics step rate (60 Hz). |
| Urho3D Lua globals | â | self.node â the entity's Urho3D Node. input â Urho3D Input (lowercase, not the C++ API). cache â ResourceCache. scene â the active Scene. time â Time subsystem. audio â Audio subsystem. renderer â Renderer. |
| Node extensions (simple-3d additions) | â | Registered on every Urho3D Node via the binding layer: node:MoveRelative(v), node:IsOnGround(), node:IsOnGround2D(), node:ApplyImpulse(v), node:GetForward(), node:GetRight(). These are not standard Urho3D methods. |
| AnimationController from Lua | â | local anim = self.node:GetComponent("AnimationController"). Methods: Play, Stop, StopAll, IsPlaying, SetWeight, SetSpeed, GetTime, SetTime. Guard with if anim then â returns nil before AddAnimationController() is called from C++. |
| RigidBody from Lua | â | local rb = self.node:GetComponent("RigidBody"). Full Urho3D RigidBody API is available: ApplyImpulse, SetLinearVelocity, GetLinearVelocity, GetMass. 2D equivalent: "RigidBody2D". |
| Input from Lua | â | Use the capitalised global: Input:IsKeyDown(KEY_W). Key constants are prefixed with KEY_. Note: Urho3D's lowercase input global uses a different API â prefer Input: (colon syntax) for consistency with simple-3d conventions. |
| PlaySound / PlayMusic from Lua | đ | Part of custom bindings (Task #3, in progress). Currently accessible via Urho3D's audio global with verbose syntax. The simple-3d convenience binding (PlaySound("file.wav")) is not yet exposed. |
| FindEntity from Lua | đ | Part of Task #3. Currently use scene:GetChild("name", true) to search the scene tree from Lua. The simple-3d FindEntity wrapper (which also searches the entity registry) is not yet bound to Lua. |
| MoveToward from Lua | đ | Task #3. Will expose the navigation MoveToward(entity, target, speed) binding so Lua AI scripts can path-find without C++ code. |
| Script communication | â | Scripts communicate via Urho3D's SendEvent / SubscribeToEvent. For simpler cases, read shared state from a global Lua table or use scene:GetChild to reach other entities and read variables from their script component. |
Navigation
Recast (nav-mesh generation) + Detour (pathfinding), both via Urho3D. The nav-mesh is baked at runtime from all static models in the scene. See C++ API â Game â Navigation.
| Feature | Status | Details & Notes |
|---|---|---|
| Nav-mesh bake | â | BuildNavMesh(agentRadius, agentHeight, cellSize). Scans all static geometry in the scene and bakes a walkable surface. Call once after loading the scene â baking is synchronous and may take 0.1â0.5 s for complex scenes. Do not call every frame. |
| FindPath(Entity*) | â | Returns a vector<Vector3> of waypoints from this entity to the target. Waypoints are in world space. The result is also cached internally for use by MoveToward(). |
| FindPath(Vector3) | â | Same as above but the target is a world position rather than an entity. Returns empty vector if either start or end is off the nav-mesh. |
| MoveToward | â | entity->MoveToward(target, speed). Follows the cached path toward the target at the given speed (units/s). Recomputes the path automatically if the entity drifts > 1 m off the expected track. Returns true when the target is reached. |
| StopMoving | â | entity->StopMoving(). Clears the cached path and zeroes the horizontal velocity of the associated rigid body. Call when switching from navigation to manual input. |
| Nav-mesh debug overlay | đ | Planned visual debugging tool â renders the baked nav-mesh polygons as a semi-transparent overlay in debug builds. Will be toggled via a CMake option or runtime key. |
Networking
ENet UDP library via Urho3D. Client/server model with automatic scene entity replication. Custom message IDs 100â32767 are reserved for game code. See C++ API â NetworkManager and Examples â Networking.
| Feature | Status | Details & Notes |
|---|---|---|
| Start server | â | network.StartServer(port, maxClients). Port 27015 is a common choice. maxClients limits simultaneous connections. The server also runs a local game instance â no headless mode currently. |
| Client connect | â | network.Connect("192.168.1.10", 27015). Connection is asynchronous; OnServerConnected() fires on success. If the server is unreachable, OnServerDisconnected() fires after a timeout. |
| Scene entity replication | â | Automatic via Urho3D's built-in network replication. Entities created by the server are automatically cloned on all clients. Mark an entity for replication: entity->SetReplicated(true). |
| Custom messages | â | Send raw byte payloads with a game-defined ID (100â32767). Pack structs into vector<uint8_t> via memcpy. Receive via OnMessage(Connection* sender, int msgId, const vector<uint8_t>& data). IDs 0â99 are reserved by Urho3D. |
| Broadcast to all clients | â | network.Broadcast(msgId, data). Sends to every connected client from the server. Not available on the client side â clients send to the server only. |
| Send to specific client | â | network.SendToClient(connection, msgId, data). Use the Connection* received in OnClientConnected. Store connections in a list to address specific players. |
| Send to server | â | network.SendToServer(msgId, data). Client-side call; routed to the server's OnMessage. The sender in OnMessage identifies which client. |
| Connection callbacks | â | OnClientConnected(Connection*), OnClientDisconnected(Connection*) â server side. OnServerConnected(), OnServerDisconnected() â client side. Override in your Game subclass. |
| Message callback | â | OnMessage(Connection* sender, int msgId, vector<uint8_t> data) â override in Game. Both server and client receive messages here. Dispatch on msgId to handle different message types. |
UI
Currently limited to text labels. Full widget system is planned for Task #17. See C++ API â Label.
| Feature | Status | Details & Notes |
|---|---|---|
| Label (text element) | â | Label* l = CreateLabel(). Methods: SetText(str), SetPosition(x, y) (screen pixels), SetFontSize(px), SetColor(Color), SetVisible(bool). Position (0,0) = top-left. Can be used for HUD counters, debug overlays, subtitles. |
| UI::Button | đ | Task #17. Planned: SetText, SetSize, SetPosition, SetOnClick(callback). Required for start screen and pause menu in Speedy Blupi. |
| UI::Image | đ | Task #17. Screen-space texture; SetTexture(path), SetRect, SetColor. For life icons, health bar backgrounds, etc. |
| UI::Panel | đ | Task #17. Container for grouping UI elements. Transparent or opaque background. Used for pause overlay: show/hide the whole panel group at once. |
| UI::ProgressBar | đ | Task #17. Horizontal/vertical bar. SetValue(0..1), SetColor. For health, mana, loading screens. |
| UI::Slider | đ | Task #17. Draggable float value. SetRange(min, max), SetOnChange(callback). For options menus (volume, sensitivity). |
| Anchor system | đ | Task #17. SetAnchor(Anchor::TopLeft / Center / BottomRight / âŚ) + pixel offset. Allows UI elements to adapt to different window sizes without manual recalculation. |
| Z-order stacking | đ | Task #17. SetZOrder(int) â higher values render on top. Necessary for modal dialogs over HUD. |
| Scene transitions (fade) | đ | Task #18. Game::FadeOut(duration, callback) / FadeIn(duration). Used between level loads. Implemented as a full-screen overlay panel with animated opacity. |
| Save / Load UI state | đ | Task #19. SaveData key-value store for persisting game progress. JSON format. Platform paths: Linux ~/.local/share/<app>/, Windows %APPDATA%\<app>\. |
2D Support
2D rendering via Urho3D's sprite/Spriter2D/TileMap subsystems. Use in combination with the orthographic camera and Box2D physics. See C++ API â Entity â 2D Rendering.
| Feature | Status | Details & Notes |
|---|---|---|
| Static sprite | â | AddSprite2D("Sprites/coin.png"). PNG or JPG. The sprite is rendered at the entity's world position. Scale via SetScale(Vector3). Log-error on missing file, no crash. |
| Animated sprite (Spriter / SCML) | â | AddAnimatedSprite2D("Sprites/player.scml"). Spriter is an external 2D character animation tool that exports .scml files. Spritesheet must be alongside the .scml. |
| PlayAnimation2D | â | PlayAnimation2D("run", loop). Animation name matches the entity name defined in Spriter. Call once to start; the engine loops until a different animation is set. |
| SetFlipX2D / SetFlipY2D | â | SetFlipX2D(true) mirrors the sprite horizontally. Used for left/right character facing without separate left-facing sprite sheets. |
| Tile map (.tmx) | â | LoadTileMap("Maps/world1.tmx"). Tiled map editor TMX format. Supports orthogonal tile layers. Returns an Entity* holding the TileMap2D component. Object layers are not auto-parsed â read them from the Tiled XML manually if needed. |
| Orthographic camera helper | â | camera.Setup2D(pixelsPerUnit). Sets orthographic projection scaled so that 1 world unit = pixelsPerUnit pixels. Commonly 100 PPU for sprite games. Converts pixel sizes to world sizes for physics setup. |
Backend Comparison
simple-3d supports two engine backends selectable at CMake configure time via -DSIMPLE3D_ENGINE=U3D or -DSIMPLE3D_ENGINE=NOVA3D. The public API is identical regardless of backend choice.
What is the difference?
U3D is the community-maintained upstream fork of Urho3D at u3d-community/U3D. NOVA3D is Robert Vokac's fork that adds an XNA-style C++ runtime layer, allowing the same game to target both desktop and (future) Android without code changes.
| Capability | U3D backend | NOVA3D backend |
|---|---|---|
| Linux (x86_64) | â | â |
| Windows (MSVC) | â | đ planned |
| Windows (MinGW cross-compile) | â toolchain provided | đ |
| macOS | đ untested | đ |
| Android | đ | đ primary target |
| Web / Emscripten | đ | đ |
| LuaJIT scripting | â | â |
| Bullet physics | â | â |
| Box2D physics | â | â |
| ENet networking | â | đ |
| Recast/Detour navigation | â | â |
| XNA-style content pipeline | đ | â core feature |
| Toolchain file included | â MinGW | đ |
Platforms
Target platform selection is determined by CMake toolchain files and the active engine backend. Cross-compilation uses MinGW or Android NDK.
| Platform | Status | Backend | Notes |
|---|---|---|---|
| Linux (x86_64) | â | U3D or NOVA3D | Primary development platform. SDL2 window, OpenGL renderer. Install packages: libsdl2-dev libgl1-mesa-dev libopenal-dev. |
| Windows (native MSVC) | â | U3D | Visual Studio 2022 or later. Set U3D_HOME to the built U3D directory. DX11 renderer path available. |
| Windows (MinGW cross-compile) | â | U3D | Build from Linux: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/MinGW.cmake âŚ. Toolchain file provided. Produces a standalone Windows .exe + DLLs. |
| macOS | đ | U3D | Build path exists in CMakeLists.txt but has not been tested end-to-end. Homebrew dependencies: sdl2 openal-soft lua. Community testing welcome. |
| Android | đ | NOVA3D | Task #8. Requires Android NDK + NOVA3D backend. Touch input is already abstracted. The primary motivation for the NOVA3D fork is Android support for Speedy Blupi mobile. |
| Web / Emscripten | đ | TBD | Task #9. Emscripten build. Browser canvas output. LuaJIT requires Emscripten-compatible build (possible via the LLVM backend). No ETA. |
Roadmap
Tasks are ordered by priority for the Speedy Blupi 3D remake prototype. All tasks after Task #17A are blocked until the acceptance test passes (build + manual run of samples/blupi_proto).
Current status: Task #17A â Stabilization (in progress)
All code changes for stabilization are written. The blocker is a manual build-and-run verification of samples/blupi_proto. Once that passes, all other tasks unblock in the order below.
| Task | Title | Priority | Full Description & Planned API |
|---|---|---|---|
| #21 | Character Controller | đĽ High | Bullet KinematicCharacterController replacing raw RigidBody for player movement. Solves slope sliding and step climbing. API: Entity::AddCharacterController(radius, height), CharacterController::Move(vel, dt), Jump(speed), SetMaxSlope(deg), SetStepHeight(m). Entity::IsOnGround() delegates to KCC state when present. |
| #24 | Gamepad Input | đĽ High | SDL gamepad (Linux/Mac) and XInput (Windows). IsGamepadConnected(index), GetGamepadAxis(GamepadAxis, index), IsGamepadButtonDown/Pressed(btn, index). Enums: GamepadAxis::{LeftX,LeftY,RightX,RightY,LTrigger,RTrigger}, GamepadButton::{A,B,X,Y,LB,RB,Start,Select,DPadUp/Down/Left/Right}. Rumble: SetGamepadRumble(lowFreq, highFreq, durationSec). Hot-plug: SetOnGamepadConnected/Disconnected. |
| #17 | Full UI System | đĽ High | Required for start screen, pause menu, lives display, score. New types under Simple3D::UI::: Button, Image, Panel, ProgressBar, Slider. Anchor system: SetAnchor(Anchor::TopLeft / Center / BottomRight âŚ). Z-order stacking. Factory methods on Game: CreateButton, CreateImage, CreatePanel, etc. |
| #18 | Scene Management | đĽ High | Level transitions for Speedy Blupi's ~30 levels. API: Game::LoadScene(path), UnloadScene(), SetPersistentEntity(Entity*), FadeOut(duration, callback), FadeIn(duration), GetCurrentScene(), SetLevelList(âŚ), LoadNextLevel(), LoadLevel(index). Camera and audio survive transitions; all other entities are destroyed. |
| #19 | Save / Load | High | Persistent game state (score, level index, settings). Simple3D::SaveData key-value store; types: int, float, bool, std::string. Set(key, val), Get<T>(key, default), Save(slot), Load(slot), Delete(slot), Exists(slot). JSON format. Platform paths: Linux ~/.local/share/<app>/, Windows %APPDATA%\<app>\. |
| #23 | Spatial Audio | Medium | 3D positional sound for enemy and footstep audio. Entity::AddAudioSource() â AudioSource*. Methods: Play(path), Stop(), SetLooped(bool), IsPlaying(), SetGain(0..1), SetRange(near, far), SetPitch(mult), PlayAmbient(path). Listener: Game::SetAudioListener(entity) (typically camera or player entity). |
| #20 | Particle Effects | Medium | Visual feedback for jumps, collect events, explosions. Entity::AddParticleEmitter(xmlPath) â ParticleEmitter*. Methods: Play(), Stop(), Burst(count), IsEmitting(), SetEmitting(bool). Built-in presets: Particles/Sparkle.xml, Particles/Smoke.xml, Particles/Explosion.xml, Particles/Dust.xml, Particles/Stars.xml. Billboard helper: Entity::AddBillboard(texturePath). |
| #22 | Animation State Machine | Medium | Declarative character animation to replace manual if-else chains. Simple3D::AnimStateMachine: AddState(name, animPath, loop), AddTransition(from, to, cond, blendTime=0.2). Conditions: SetBool(key, val), SetFloat(key, val), SetTrigger(key). Update(dt) from Game::Update. GetCurrentState(). Drives the entity's AnimationController internally. |
| #26 | Enemy AI FSM | Medium | Finite state machine for patrol â alert â chase â attack enemies. Simple3D::BehaviorFSM: AddState(name, onEnter, onUpdate, onExit), AddTransition(from, to, conditionFn), Update(dt), SetState(name). Built-in NavMesh-backed states: AddPatrolState(waypoints, speed), AddChaseState(target, speed, stopDist). Factory: Entity::AddBehaviorFSM(). |
| #25 | Physics Constraints | Low | Moving platforms, hinged doors, levers, chains. Game::CreateConstraint(type, entityA, entityB). Types: ConstraintType::{Fixed, Hinge, Slider, BallSocket}. Hinge: SetHingeAxis(v), SetHingeLimits(minDeg, maxDeg). Slider: SetSliderAxis(v), SetSliderLimits(min, max). Motor: SetMotorEnabled(bool), SetMotorSpeed(f), SetMotorMaxForce(f). |
| #8 | Android | Low (future) | NOVA3D backend + Android NDK. Touch input is already abstracted. The CMake integration will mirror the desktop build. Primary motivation: Speedy Blupi mobile port. No ETA until NOVA3D backend stabilizes on desktop. |
| #9 | Web / Emscripten | Low (future) | Browser build via Emscripten. Output: WebAssembly + canvas. LuaJIT requires the Emscripten LLVM path (supported). ENet networking in a browser context needs WebSocket bridging (non-trivial). No ETA. |