Lumos Docs
Cross-platform C++ game engine with Forward+ rendering, Lua scripting, and a built-in editor.
Getting Started
Clone the repo:
git clone https://github.com/jmorton06/Lumos.git
cd Lumos
Requirements
- C++17 compiler (Clang, MSVC, or GCC 11+)
- Vulkan SDK (recommended) or OpenGL
- Premake5 (bundled in
Tools/)
Linux Dependencies
sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libopenal-dev mesa-common-dev
Building
Lumos uses Premake5 to generate platform-native build files. The premake binary is included in Tools/.
macOS
# Generate Xcode project
Tools/premake5 xcode4
# Apple Silicon
Tools/premake5 xcode4 --arch=arm64 --os=macosx
# Or use the build script
./Scripts/MacOS/BuildMacOS.sh
Windows
# Generate Visual Studio solution
Scripts\GenerateVS.bat
# Build from command line
msbuild /p:Platform=x64 /p:Configuration=Release Lumos.sln
Linux
Tools/linux/premake5 gmake2
make -j$(nproc)
iOS
Tools/premake5 xcode4 --os=ios
Build Configurations
| Config | Use |
|---|---|
| Debug | Full symbols, no optimisation, validation layers on |
| Release | Optimised, symbols stripped |
| Production | Release + asset packing, no editor |
Output goes to bin/{config}-{system}-{arch}/.
New Project
A project is a folder with a .lmproj file and an Assets/ directory.
Project Structure
MyGame/
MyGame.lmproj
Assets/
Scripts/ # Lua scripts
Scenes/ # .lscene files
Meshes/ # .glb, .obj, .gltf
Textures/ # .png, .jpg
Sounds/ # .wav, .ogg
Fonts/
Resources/
Icons/ # App icons per platform
Project File
The .lmproj is JSON:
{
"value0": {
"Project Version": 12,
"Title": "MyGame",
"RenderAPI": 1,
"Width": 1200,
"Height": 800,
"Fullscreen": false,
"VSync": true,
"Scenes": ["//Assets/Scenes/Main"],
"SceneIndex": 0,
"BundleIdentifier": "com.you.mygame",
"Version": "1.0.0",
"AppScript": "Assets/Scripts/main.lua"
}
}
| Field | Description |
|---|---|
RenderAPI | 1 = Vulkan, 2 = OpenGL |
Scenes | Array of scene paths (use //Assets/ prefix) |
AppScript | Entry point Lua script |
Running
# Launch in editor
./Editor MyGame/
# Launch runtime only
./Runtime MyGame/
Editor
The built-in editor is ImGui-based with play-in-editor support. Launch with ./Editor MyGame/.
Panels
| Panel | Purpose |
|---|---|
| Scene View | 3D/2D viewport with transform gizmos |
| Game View | Runtime preview, play-in-editor |
| Hierarchy | Entity tree with parent/child structure |
| Inspector | Edit components on selected entity |
| Asset Manager | Browse and import project assets |
| Console | Log output, Lua errors, warnings |
| Resource | Loaded resource management |
| Project Settings | Project configuration |
Keyboard Shortcuts
| Key | Action |
|---|---|
| W | Translate gizmo |
| E | Rotate gizmo |
| R | Scale gizmo |
| T | Universal gizmo |
| Q | Bounds gizmo |
| Y | Toggle snap |
| F | Focus selected |
| O | Focus origin |
| Escape | Exit play mode |
| Ctrl+S | Save scene |
| Ctrl+O | Open scene |
| Ctrl+Z | Undo |
| Ctrl+D | Duplicate entity |
| Ctrl+X/C/V | Cut / Copy / Paste entity |
Gizmos support both local and world space modes. Snap amounts are configurable per-axis.
Scenes
Scenes are saved as .lscene files using Cereal serialisation. They contain all entities, components, and hierarchy data.
Creating Scenes
- Editor: File → New Scene, then save with Ctrl+S
- Code: Use
scene:CreateEntity("name")to add entities
Scene Paths
Use the virtual file system prefix //Assets/ when referencing scenes in project files:
"Scenes": ["//Assets/Scenes/Main", "//Assets/Scenes/Level2"]
Switching Scenes
-- From Lua
SwitchSceneByName("Level2")
SwitchSceneByIndex(1)
SwitchScene() -- next scene
Scenes support both JSON and binary serialisation. JSON is default and human-readable.
Prefabs
Prefabs are reusable entity templates saved as .lprefab files. They store the full component state of an entity and its children.
Creating Prefabs
- Editor: Right-click an entity in the Hierarchy → Save as Prefab
- Code:
scene:SavePrefab(entity, "Assets/Prefabs/Enemy.lprefab")
Instantiating
local enemy = scene:InstantiatePrefab("//Assets/Prefabs/Enemy.lprefab")
Prefabs use the same serialisation format as scenes. Changes to the prefab file update all future instances.
Lua Scripting
Scripts use Lua 5.3 via Sol2 bindings. Attach a LuaScriptComponent to any entity. Scripts hot-reload on file save.
Lifecycle
function OnInit()
-- called once on load
end
function OnUpdate(dt)
-- called every frame
end
function OnRelease()
-- called on cleanup
end
function OnCollision2DBegin()
end
function OnCollision2DEnd()
end
function OnCollision3DBegin(info)
end
function OnCollision3DEnd(info)
end
Entity & Components
-- 'this' is the current entity
local t = this:GetTransform()
t.position = Vec3(0, 5, 0)
t.rotation = Quaternion()
t.scale = Vec3(1, 1, 1)
-- Add / query components
this:AddRigidBody2D()
this:GetRigidBody2D()
this:HasRigidBody2D()
-- Find other entities
local e = GetEntityByName("Player")
EachEntity(function(entity) end)
Math
local v = Vec3(1, 2, 3)
local len = v:Length()
local n = v:Normalize()
local d = Vec3.Distance(a, b)
-- Operators work: v + v, v - v, v * scalar
Input
-- Keyboard
Input.GetKeyPressed(Key.Space) -- true on frame pressed
Input.GetKeyHeld(Key.W) -- true while held
-- Mouse
Input.GetMouseClicked(MouseButton.Left)
Input.GetMouseHeld(MouseButton.Right)
Input.GetMousePosition() -- Vec2
Input.GetScrollOffset() -- float
-- Gamepad
Input.GetControllerAxis(0, axis)
Input.GetControllerName(0)
Input.IsControllerButtonPressed(0, button)
UI
UIBeginPanel("HUD")
UILabel("score", "Score: " .. score)
if UIButton("restart") then
SwitchSceneByName("Main")
end
UISlider("volume", vol, 0, 100)
UIEndPanel()
Scene Control
SwitchScene(0)
SwitchSceneByName("Level2")
ExitApp()
Assets
LoadTexture("//Assets/Textures/sprite.png")
LoadMesh("//Assets/Meshes/character.glb")
Animation
-- Tween helpers with easing
AnimateToTarget(target, 0, 1, 2.0, ElasticOut)
.lua file and the engine picks up changes automatically. Errors are shown in the editor console without crashing.Materials
Lumos uses a PBR material system with configurable texture slots and properties.
Texture Slots
| Slot | Description |
|---|---|
| Albedo | Base colour / diffuse map |
| Normal | Surface normal map |
| Metallic | Metallic properties |
| Roughness | Surface roughness |
| AO | Ambient occlusion |
| Emissive | Self-illumination |
Properties
| Property | Default | Description |
|---|---|---|
albedoColour | Vec4(1) | Base colour when no albedo texture |
roughness | 0.7 | Surface roughness factor |
metallic | 0.7 | Metallic factor |
reflectance | 0.3 | Reflectance at normal incidence |
emissive | 0.0 | Emission strength |
alphaCutoff | 0.4 | Alpha test threshold |
PBR Workflows
0.0— Separate textures (one per slot)1.0— Metallic/roughness combined2.0— Specular/glossiness
Render Flags
Materials support flags: DEPTHTEST, WIREFRAME, NOSHADOW, TWOSIDED, ALPHABLEND.
Lua Usage
material:set_albedo_texture("//Assets/Textures/brick.png")
material:set_normal_texture("//Assets/Textures/brick_n.png")
material:set_roughness_texture("//Assets/Textures/brick_r.png")
material:set_metallic_texture("//Assets/Textures/brick_m.png")
material:set_ao_texture("//Assets/Textures/brick_ao.png")
material:set_emissive_texture("//Assets/Textures/brick_e.png")
-- Load a full PBR material set from a folder
material:load_pbr_material("brick", "//Assets/Textures/", ".png")
Features
Rendering
- Forward+ renderer with PBR shading
- Vulkan primary, OpenGL fallback
- Shadow mapping, FXAA, bloom, depth of field, SSAO
- Skeletal animation (ozz-animation)
- MSDF text rendering
- Particle system
- Sprite and 2D rendering pipeline
- glTF 2.0 and OBJ model loading
- Mesh optimisation via meshoptimizer
Physics
- 2D: Box2D integration — full rigid body, collision callbacks
- 3D: Built-in engine — sphere, cuboid, pyramid, capsule shapes
- Collision callbacks exposed to Lua
Audio
- OpenAL 3D spatial audio
- WAV and OGG support
- SoundComponent for entity-attached audio
ECS
- EnTT-based entity-component-system
- Scene graph with parent/child hierarchy
- Prefab system for reusable templates
- Cereal serialisation for scenes and entities
Editor
- ImGui-based with hierarchy, inspector, and scene panels
- Asset browser and console
- ImGuizmo transform gizmos
- Play-in-editor
AI
- A* pathfinding
- AIComponent for navigation
Asset Pipeline
- Virtual file system —
//Assets/paths - Custom formats:
.lmesh,.limg,.lanim - Asset packing to
.lpakfor distribution - Import caching for fast iteration
Platforms
| Platform | Graphics | Status |
|---|---|---|
| macOS | Vulkan (MoltenVK) | Supported |
| Windows | Vulkan / OpenGL | Supported |
| Linux | Vulkan / OpenGL | Supported |
| iOS | Vulkan (MoltenVK) | Supported |
| Android | Vulkan | WIP |
Platform-specific code lives in Lumos/Source/Lumos/Platform/ with subdirectories per OS.
Debugging
Logging
-- From Lua
Log.Trace("detailed info")
Log.Info("general info")
Log.Warn("warning")
Log.Error("something broke")
All log output appears in the editor Console panel with file and line info.
Lua Errors
Runtime Lua errors are caught and displayed in the console without crashing the engine. Query errors on a script component with LuaScriptComponent:GetErrors().
Editor Debug Overlays
| Flag | Shows |
|---|---|
| Grid | World grid |
| Gizmo | Transform gizmos |
| ViewSelected | Selection highlight |
| CameraFrustum | Camera frustum wireframe |
| MeshBoundingBoxes | AABB debug draw |
| SpriteBoxes | Sprite bounds |
| EntityNames | Name labels in viewport |
Physics Debug
-- Enable physics debug drawing from Lua
SetPhysicsDebugFlags(flags)
Available flags: CONSTRAINT, MANIFOLD, COLLISIONVOLUMES, COLLISIONNORMALS, AABB, LINEARVELOCITY, LINEARFORCE, BROADPHASE.
Vulkan Validation
Validation layers are enabled automatically in Debug builds. Common issues:
- Missing Vulkan SDK — install from LunarG
- MoltenVK on macOS/iOS — disable Metal API Validation in Xcode scheme
- Shader compilation errors appear in console with line numbers
API Reference
Complete list of Lua-exposed APIs. All bindings use Sol2.
Input
Input.GetKeyPressed(key) -> bool
Input.GetKeyHeld(key) -> bool
Input.GetMouseClicked(button) -> bool
Input.GetMouseHeld(button) -> bool
Input.GetMousePosition() -> Vec2
Input.GetScrollOffset() -> float
Input.GetControllerAxis(id, axis) -> float
Input.GetControllerName(id) -> string
Input.IsControllerButtonPressed(id, button) -> bool
Math
-- Types: Vec2, Vec3, Vec4, Quat, Matrix3, Matrix4, Transform
-- All support +, -, *, / operators
v:Length() -> float
v:Normalise() -> vec
Maths.Distance(a, b) -> float
-- Easing
Maths.SineIn(t) / SineOut(t) / SineInOut(t)
Maths.ExponentialIn(t) / ExponentialOut(t) / ExponentialInOut(t)
Maths.ElasticIn(t) / ElasticOut(t) / ElasticInOut(t)
-- Transform
transform:GetLocalPosition() -> Vec3
transform:GetLocalScale() -> Vec3
transform:GetLocalOrientation() -> Quat
transform:SetLocalPosition(vec)
transform:SetLocalScale(vec)
transform:SetLocalOrientation(quat)
transform:GetWorldPosition() -> Vec3
transform:GetForwardDirection() -> Vec3
transform:GetRightDirection() -> Vec3
Entity & Scene
GetCurrentScene() -> Scene
GetEntityByName(name) -> Entity
GetAllEntities() -> table
EachEntity(callback)
scene:CreateEntity() -> Entity
scene:CreateEntity(name) -> Entity
scene:InstantiatePrefab(path) -> Entity
scene:SavePrefab(entity, path)
scene:GetRegistry()
entity:Valid() -> bool
entity:Destroy()
entity:SetParent(entity)
entity:GetParent() -> Entity
entity:GetChildren() -> table
entity:SetActive(bool)
entity:Active() -> bool
SwitchScene()
SwitchSceneByIndex(index)
SwitchSceneByName(name)
ExitApp()
Graphics
-- Textures
LoadTexture(name, path) -> Texture2D
LoadTexture2D(path) -> Texture2D
texture:GetWidth() -> int
texture:GetHeight() -> int
-- TextureFilter: None, Linear, Nearest
-- TextureWrap: Repeat, Clamp, MirroredRepeat, ClampToEdge, ClampToBorder
-- Model
model = Model(path)
model:GetMeshCount() -> int
model:GetTotalStats() -> {vertices, triangles, indices, meshes}
model:GetBounds() -> {min, max, center, extents, radius}
-- Light
light:Intensity() -> float
light:Radius() -> float
light:Colour() -> Vec3
light:Direction() -> Vec3
light:Type() -> LightType
light:Angle() -> float
-- Camera
camera = Camera(fov, aspect, near, far)
camera:SetIsOrthographic(bool)
camera:SetFOV(fov)
camera:SetNearPlane(near)
camera:SetFarPlane(far)
-- Sprite
sprite = Sprite(scale, size, color)
sprite:SetTexture(texture)
sprite:SetSpriteSheet(texture, cols, rows)
sprite:SetSpriteSheetIndex(index)
-- ParticleEmitter
emitter = ParticleEmitter(count)
emitter:SetParticleLife(seconds)
emitter:SetParticleSize(size)
emitter:SetInitialVelocity(vec)
emitter:SetInitialColour(color)
emitter:SetSpread(spread)
Physics
-- 3D
body:SetForce(vec) / SetPosition(vec) / SetLinearVelocity(vec)
body:GetLinearVelocity() -> Vec3
body:SetOrientation(quat) / SetAngularVelocity(vec)
body:SetFriction(f) / GetFriction() -> float
body:SetIsStatic(bool) / GetIsStatic() -> bool
body:SetIsTrigger(bool) / GetIsTrigger() -> bool
body:SetElasticity(e)
body:SetCollisionShape(type)
-- Shapes: Cuboid, Sphere, Pyramid, Capsule, Hull, Terrain
-- 2D (Box2D)
body2d:SetForce(vec) / SetPosition(vec) / SetLinearVelocity(vec)
body2d:GetLinearVelocity() -> Vec2
body2d:GetAngle() -> float / GetPosition() -> Vec2
body2d:SetLinearDamping(d) / SetIsStatic(bool)
-- Physics materials
PhysicsMaterial.Default() / Bouncy() / Ice()
PhysicsMaterial.Rubber() / Metal() / Wood() / Concrete()
Sound
sound = Sound.Create(path, extension)
sound:GetLength() -> float
node = SoundNode.Create(sound)
node:Play() / Pause() / Resume() / Stop()
node:SetVolume(v) / GetVolume() -> float
node:SetLooping(bool) / SetPitch(p)
UI
UIBeginFrame() / UIEndFrame()
UIBeginPanel(name) / UIBeginPanel(name, flags) / UIEndPanel()
UILabel(text)
UIButton(name) -> bool
UIImage(texture)
UISlider(label, value) -> float
UIToggle(label, value) -> bool
UIPushStyle(var, value) / UIPopStyle()
UILayoutRoot()
GetStringSize(text) -> Vec2
-- WidgetFlags: Clickable, DrawText, DrawBorder, DrawBackground,
-- Draggable, StackVertically, StackHorizontally, Floating_X,
-- Floating_Y, CentreX, CentreY
-- StyleVar: Padding, Border, BorderColor, BackgroundColor,
-- TextColor, FontSize
Utility
Rand(a, b) -> float
SaveScreenshot(path)
ReadFile(path) -> string
WriteFile(path, content) -> bool
GetAssetPath() -> string
FileDialog.OpenFile(filter, path) -> string
FileDialog.SaveFile(filter, path, name) -> string
FileDialog.PickFolder(path) -> string
Releasing
1. Pack Assets
Create a .lpak bundle from your project assets. This compresses and packages everything needed at runtime.
Via Editor: File → Build Asset Pack
Via CLI:
./Runtime --project=MyGame --pack-assets=MyGame/Assets.lpak
Cache and import directories are excluded automatically.
2. Prepare Icons
Place your icon at Assets/Textures/icon.png and run:
./Scripts/MacOS/PrepareIcon.sh
This generates all required sizes for macOS, iOS, Windows, and Linux.
3. Package
macOS
./Scripts/MacOS/PackageGame.sh MyGame
Packs assets, generates Xcode project, and opens it for archive and export.
iOS
./Scripts/MacOS/PackageGame.sh MyGame --ios
Same flow targeting iOS. Set your team and bundle ID in Xcode before archiving.
Launch Screen (iOS)
./Scripts/MacOS/GenerateLaunchScreen.sh
Windows / Linux
Build with Production config and distribute the binary alongside the .lpak file. The runtime auto-mounts the pack from the project folder.
Naming
| Thing | Convention |
|---|---|
| Project folder | Match project name |
| Executable | Set via targetname in premake |
| Scenes | Assets/Scenes/Name.lscene |
| Scripts | Assets/Scripts/name.lua |
| Asset pack | Assets.lpak in project root |
App Store
For macOS/iOS App Store submission:
- Set
BundleIdentifierin.lmprojand Xcode - Set version and build number
- Run icon prep script for all sizes
- Generate launch screen (iOS)
- Archive in Xcode → Validate → Distribute
ITSAppUsesNonExemptEncryption = NO) is handled automatically by the premake template.