DEMIENGINEDOCUMENTATION Everything is free
Create

In this guide

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

ShapeUsed byHow 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

  • Transform2D rotation and Transform3D Euler 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

ModuleUse it for
demi.applicationWindow mode, FPS cap, mouse capture, safe area, clipboard, user/cache paths, permissions
demi.entityFinding, spawning, cloning, destroying entities; reading and writing component fields
demi.sceneLoading and switching scenes (prepare → activate)
demi.prefabInstantiating and releasing prefab instances
demi.inputAction queries, contexts, rebinding, touch, gamepads
demi.transform2d / demi.transform3dLocal position, rotation, scale; 3D directions and look_at
demi.sprite2dSprite color, flip, size, sorting, animation playback
demi.tilemap2dReading and overriding tiles; tile objects; navigation bake
demi.animationState machine parameters, clips, layers, root motion, two-bone IK
demi.camera3dScreen rays and world/screen conversion
demi.hudUI nodes, text, variables, tweens, virtualization, accessibility
demi.eventsSubscribing to engine events and emitting game events
demi.dataLoading schema-backed game data
demi.saveSave slots and structured state
demi.assetsAsync asset load/unload and memory reports
demi.time / demi.timerFrame time, pause, time scale; delays and repeats
demi.debug / demi.profileOn-screen debug lines; scoped performance timers
demi.text / demi.regexGrapheme-safe text helpers; ECMAScript pattern matching
demi.grid / demi.navigationIsometric placement; A* pathfinding
demi.video / demi.cutsceneVideo playback and cutscene sequencing
demi.testE2E helpers (see Testing & debugging)
demi.networkLow-level transport (see Networking & HTTP)
Namespaced moduleUse it for
demi.physics.rigidbody2d / demi.physics.rigidbody3dVelocities, forces, impulses, sleep, kinematic movement
demi.physics.query2d / demi.physics.query3dRaycasts, shape casts, overlaps, contact checks
demi.physics.character_controller3dCharacter velocity, jump, grounded state
demi.physics.destruction3dImpacts, part damage, checkpoints
demi.network.sessionHost-authoritative multiplayer sessions
demi.network.httpAsync HTTP/HTTPS REST calls
demi.network.tls.client / .serverVerified TLS sockets
demi.network.cryptoTokens, password hashing, constant-time compare
demi.audio / demi.audio.sourceBuses, snapshots, one-shots; entity-attached playback
demi.math.vector2 / vector3 / scalar / randomVector math, smoothstep, seeded RNG
demi.mesh.procedural / demi.mesh.deformationBuilding procedural meshes; denting
demi.voxel.worldChunked 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.