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

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
Note: Disable Metal API Validation in Xcode scheme settings when running on device.

Build Configurations

ConfigUse
DebugFull symbols, no optimisation, validation layers on
ReleaseOptimised, symbols stripped
ProductionRelease + 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"
  }
}
FieldDescription
RenderAPI1 = Vulkan, 2 = OpenGL
ScenesArray of scene paths (use //Assets/ prefix)
AppScriptEntry 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

PanelPurpose
Scene View3D/2D viewport with transform gizmos
Game ViewRuntime preview, play-in-editor
HierarchyEntity tree with parent/child structure
InspectorEdit components on selected entity
Asset ManagerBrowse and import project assets
ConsoleLog output, Lua errors, warnings
ResourceLoaded resource management
Project SettingsProject configuration

Keyboard Shortcuts

KeyAction
WTranslate gizmo
ERotate gizmo
RScale gizmo
TUniversal gizmo
QBounds gizmo
YToggle snap
FFocus selected
OFocus origin
EscapeExit play mode
Ctrl+SSave scene
Ctrl+OOpen scene
Ctrl+ZUndo
Ctrl+DDuplicate entity
Ctrl+X/C/VCut / 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

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

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)
Hot-reload: Save a .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

SlotDescription
AlbedoBase colour / diffuse map
NormalSurface normal map
MetallicMetallic properties
RoughnessSurface roughness
AOAmbient occlusion
EmissiveSelf-illumination

Properties

PropertyDefaultDescription
albedoColourVec4(1)Base colour when no albedo texture
roughness0.7Surface roughness factor
metallic0.7Metallic factor
reflectance0.3Reflectance at normal incidence
emissive0.0Emission strength
alphaCutoff0.4Alpha test threshold

PBR Workflows

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

Physics

Audio

ECS

Editor

AI

Asset Pipeline

Platforms

PlatformGraphicsStatus
macOSVulkan (MoltenVK)Supported
WindowsVulkan / OpenGLSupported
LinuxVulkan / OpenGLSupported
iOSVulkan (MoltenVK)Supported
AndroidVulkanWIP

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

FlagShows
GridWorld grid
GizmoTransform gizmos
ViewSelectedSelection highlight
CameraFrustumCamera frustum wireframe
MeshBoundingBoxesAABB debug draw
SpriteBoxesSprite bounds
EntityNamesName 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:

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

ThingConvention
Project folderMatch project name
ExecutableSet via targetname in premake
ScenesAssets/Scenes/Name.lscene
ScriptsAssets/Scripts/name.lua
Asset packAssets.lpak in project root

App Store

For macOS/iOS App Store submission:

  1. Set BundleIdentifier in .lmproj and Xcode
  2. Set version and build number
  3. Run icon prep script for all sizes
  4. Generate launch screen (iOS)
  5. Archive in Xcode → Validate → Distribute
Note: Export compliance (ITSAppUsesNonExemptEncryption = NO) is handled automatically by the premake template.