In this guide
- API conventionsReturn shapes, units, error handling, and require style.
- Entity APIFind, spawn, query, destroy, and component fields.
- Transform API2D/3D position, rotation, scale, directions, look_at.
- Scene & prefab APILoading scenes and instantiating prefabs at runtime.
- HUD APIFind, create, and mutate UI from Lua.
- Data & save APILoading game data and reading/writing saves.
- Assets, time & timersAsync loads, frame time, and scheduling.
- Math & debugVectors, seeded RNG, debug lines, and profiling.
Engine service modules, return shapes, units, and error conventions.
Lua API reference
Import every engine service with require("demi.…"). Each call is a plain Lua function on the imported table — store the module in a local and call through it.
local Transform3D = require("demi.transform3d")
local x, y, z = Transform3D.get_position("player")
How values come back
| Shape | Used by | How to read it |
|---|---|---|
local x, y, z = … |
Transform positions, rotations, scales, velocities | Multiple scalar returns. Assign exactly as many names as the function reports, or the extras are nil. |
{x, y} / {x, y, z} |
Embedded vectors in tables (hits, specs, spawn options) and demi.math.vector2/vector3 results |
Fixed-length numeric arrays. Index with v[1], v[2], v[3] (Lua arrays start at 1). |
value, nil / nil, err |
Data.load, Http.request, path queries |
Check the first result. On failure the second result is a table describing the problem. |
nil |
Missed raycasts, missing entities, unknown nodes | Guard with if hit then … end before reading fields. |
{} |
Overlap queries, event batches, children lists | An empty array means “nothing found”, not an error. Use ipairs. |
Units
Transform2Drotation andTransform3DEuler rotation: radians.- Rigidbody angular velocity: radians per second.
- Camera FOV: degrees.
Time.delta_time,Time.fixed_time: seconds.- HUD positions and sizes: canvas units from the HUD document
canvas_size.
Module index
| Module | Use it for |
|---|---|
demi.application | Window mode, FPS cap, mouse capture, safe area, clipboard, user/cache paths, permissions |
demi.entity | Finding, spawning, cloning, destroying entities; reading and writing component fields |
demi.scene | Loading and switching scenes (prepare → activate) |
demi.prefab | Instantiating and releasing prefab instances |
demi.input | Action queries, contexts, rebinding, touch, gamepads |
demi.transform2d / demi.transform3d | Local position, rotation, scale; 3D directions and look_at |
demi.sprite2d | Sprite color, flip, size, sorting, animation playback |
demi.tilemap2d | Reading and overriding tiles; tile objects; navigation bake |
demi.animation | State machine parameters, clips, layers, root motion, two-bone IK |
demi.camera3d | Screen rays and world/screen conversion |
demi.hud | UI nodes, text, variables, tweens, virtualization, accessibility |
demi.events | Subscribing to engine events and emitting game events |
demi.data | Loading schema-backed game data |
demi.save | Save slots and structured state |
demi.assets | Async asset load/unload and memory reports |
demi.time / demi.timer | Frame time, pause, time scale; delays and repeats |
demi.debug / demi.profile | On-screen debug lines; scoped performance timers |
demi.text / demi.regex | Grapheme-safe text helpers; ECMAScript pattern matching |
demi.grid / demi.navigation | Isometric placement; A* pathfinding |
demi.video / demi.cutscene | Video playback and cutscene sequencing |
demi.test | E2E helpers (see Testing & debugging) |
demi.network | Low-level transport (see Networking & HTTP) |
| Namespaced module | Use it for |
|---|---|
demi.physics.rigidbody2d / demi.physics.rigidbody3d | Velocities, forces, impulses, sleep, kinematic movement |
demi.physics.query2d / demi.physics.query3d | Raycasts, shape casts, overlaps, contact checks |
demi.physics.character_controller3d | Character velocity, jump, grounded state |
demi.physics.destruction3d | Impacts, part damage, checkpoints |
demi.network.session | Host-authoritative multiplayer sessions |
demi.network.http | Async HTTP/HTTPS REST calls |
demi.network.tls.client / .server | Verified TLS sockets |
demi.network.crypto | Tokens, password hashing, constant-time compare |
demi.audio / demi.audio.source | Buses, snapshots, one-shots; entity-attached playback |
demi.math.vector2 / vector3 / scalar / random | Vector math, smoothstep, seeded RNG |
demi.mesh.procedural / demi.mesh.deformation | Building procedural meshes; denting |
demi.voxel.world | Chunked voxel sections and meshes |
Entities
local Entity = require("demi.entity")
local id = Entity.find("player") -- "player", or nil if missing
if id and Entity.exists(id) then
Entity.set_field(id, "Sprite", "color", { 1, 0.4, 0.4, 1 })
end
Entity.find takes the authored entity ID and returns the live handle used by every other call, or nil when nothing matches. Entity.set_field(id, component, field, value) writes one field and leaves the rest of that component unchanged — use it for runtime tweaks instead of rebuilding the component.
local Entity = require("demi.entity")
local ok, err = Entity.spawn("crate", {
position = { 4, 2, 0 }, -- world position, 3 floats
velocity = { 1, 0, 0 }, -- optional initial velocity
})
-- ok: true when creation was queued; err: nil, or a diagnostic string
local enemies = Entity.query({ tags = { "enemy" } })
print(#enemies) -- 0 when none match
Entity.destroy_many(enemies)
Entity.spawn takes an entity ID plus optional position and velocity shorthand (dense 2- or 3-float arrays of finite numbers). It returns true when creation was queued, or false plus an error string; the entity appears at the next creation step, so use Entity.find afterwards for the handle. Prefab and ttl options are rejected — use Prefab.instantiate and Timer.after instead.
Entity.query filters by tags, all, layer, and include_disabled, and always returns an array — empty when nothing matches. Use Entity.get_config(id, component, field) to read one last-configured field value; for live simulation state use the relevant system (Rigidbody3D.state, CharacterController3D.state, and so on).
Other helpers: Entity.create, replace, clone, destroy, set_enabled, is_enabled, add_component, remove_component, has_component, set_parent, parent, children, local_position, world_position.
Transforms
local T = require("demi.transform3d")
local x, y, z = T.get_position("player") -- local space, 3 scalars
T.set_position("player", x + 1, y, z)
T.add_position("player", 0, 0, -2) -- relative move
local rx, ry, rz = T.get_rotation("player")-- Euler radians
T.set_rotation("player", 0, math.pi / 2, 0)
local sx, sy, sz = T.get_scale("player")
local fx, fy, fz = T.forward("player") -- world-space unit vector
T.look_at("player", 10, 0, 3) -- aims at a world point
Each getter splits its value into separate return numbers. If the entity or component is missing, every coordinate comes back nil — check if x then … end. Transform3D.forward/right/up return world-space unit direction vectors. look_at resolves the full parent chain first, so a child aims correctly regardless of nesting.
Transform2D is the same pattern with get/set/add_position returning x, y and get/set_rotation returning a single radian angle.
Prefabs and scenes
local Prefab = require("demi.prefab")
local Scene = require("demi.scene")
local instance = Prefab.instantiate("prefab://enemies/grunt", {
id = "wave_4_grunt_12", -- unique instance ID
position = { 4, 2, 0 },
overrides = {
body = { name = "Elite Grunt" }, -- merge into entity "body"
},
pooled = true, -- reuse on release
})
-- instance is the instance ID; entities are "wave_4_grunt_12/body", …
local placed = Prefab.placements(instance)
print(#placed)
Prefab.release(instance) -- or release any child handle
instantiate requires options.id, expands the prefab, and returns that instance ID or nil on failure. Entity IDs join the instance ID with a prefab-local ID (wave_4_grunt_12/body). overrides merges into expanded entities by local ID: objects merge, arrays replace, and null removes a field. pooled = true keeps entities for reuse on release; Prefab.pooled_count(prefab_uri) reports how many are idle.
Scene.prepare("scene://game/level_two") -- start loading
-- …
if Scene.is_prepared() then
Scene.activate() -- switch at frame boundary
end
print(Scene.progress()) -- 0.0 … 1.0
print(Scene.active()) -- current scene URI
print(Scene.error()) -- empty string when healthy
Loading is two-phase: prepare reads and decodes without changing the live world, then activate swaps atomically at a frame boundary. Poll progress or is_prepared while a load screen is up — both take no arguments and describe the pending prepare. Scene.load is the one-step form; reload refreshes the current scene; cancel aborts a prepare; unload(scene_id) removes an additive scene; set_persistent(entity_id, true) marks one entity to survive full transitions.
Math helpers
local Vector3 = require("demi.math.vector3")
local Random = require("demi.math.random")
local length = Vector3.length({ 1, 0, 0 }) -- 1.0
local n = Vector3.normalized({ 3, 0, 0 }) -- { 1, 0, 0 }
local c = Vector3.cross({ 1, 0, 0 }, { 0, 1, 0 }) -- { 0, 0, 1 }
local l = Vector3.lerp({ 0, 0, 0 }, { 10, 0, 0 }, 0.25) -- { 2.5, 0, 0 }
Random.seed(42) -- deterministic sequence
print(Random.value()) -- 0.0 … 1.0
print(Random.integer(1, 6)) -- inclusive bounds
local checkpoint = Random.state()
Random.restore(checkpoint) -- rewind the sequence
Vector functions take and return Vec2/Vec3 numeric arrays — fixed-length tables indexed from 1 (v[1], v[2], v[3]); length and dot return a single number. Random.state snapshots the generator as an exact state string and restore rewinds it. Save both the seed and a state checkpoint when you need a replay to match.
Events
local Events = require("demi.events")
local id = Events.subscribe("physics_collision_enter", function(e)
-- e.other_id, e.point, e.normal, …
end)
Events.emit("player_died", { x = 1, y = 2 }) -- game-defined payload
Events.unsubscribe(id)
subscribe returns an integer token you pass back to unsubscribe (which returns true when the token was removed). emit returns the number of handlers delivered. Handlers run immediately on emit. Engine event payloads are plain tables — the fields depend on the event (see the system pages).
Editor stubs
Generate LuaLS/EmmyLua stubs for autocomplete in your editor:
demi lua-stubs generate .demi/lua
Add .demi/lua to LuaLS workspace.library so the language server finds the type annotations. The generated files are editor metadata only.