Skip to main content

Engine API

The Engine API provides access to core engine functionality including input, networking, entity manipulation, UI, and audio.

Overview

The Engine table is available in all scripts and provides functions for:

  • Input state queries
  • Entity manipulation (position, scale, rotation, visibility)
  • Light control (color, intensity, enabled, range) for session-spawned lights
  • Camera get/set
  • HTTP requests
  • UI (textboxes, modals)
  • Audio playback

Script origin trust

Remote .luau execution is gated by an origin allowlist (not scene signatures):

  • Scripts whose host matches the active scene URL host are allowed.
  • Scripts from other hosts are denied unless the operator adds that host under Settings → Script origins (scriptOriginsAllowlist in settings.json).
  • Inline data: script URIs are allowed (they come from the scene document).
  • This is separate from Network Policy (which only controls Engine.httpRequest third-party hosts).
  • Pair with per-asset sha256 pins (scene format) and cache revalidation for integrity.

Input Functions

Engine.getActionState(action)

Get the current state of an input action.

local isMoving = Engine.getActionState("move_forward")
if isMoving then
print("Player is moving forward")
end

Parameters:

  • action (string): Action name (lowercase)
    • "move_forward" / "move_backward" / "move_left" / "move_right"
    • "interact" - Interact button held
    • "jump" - Jump press pending this frame
    • "sprint" / "crouch" - Modifier keys held

Returns: boolean - true if action is active, false otherwise

Example:

function MyScript:on_update(dt)
if Engine and Engine.getActionState then
local moving = Engine.getActionState("move_forward")
if moving then
-- Player is moving forward
end
end
end

Engine.consumeAction(action)

Consume an interact action to prevent multiple triggers.

local consumed = Engine.consumeAction("interact")
if consumed then
-- Handle interaction (only triggers once)
end

Parameters:

  • action (string): Must be "interact"

Returns: boolean - true if action was consumed, false otherwise

Use case: Prevent multiple triggers from a single interaction press

Example:

function MyScript:on_interact(actorId)
local consumed = Engine.consumeAction("interact")
if consumed then
-- This will only run once per interaction
print("Interaction consumed")
end
end

Entity Manipulation

Engine.setEntityPosition(entityIdOrInstanceId, x, y, z)

Set the position of an entity.

Engine.setEntityPosition(self.entity, 0, 5, 0)
-- or, for scene / spawnEntity instance ids:
Engine.setEntityPosition("lua_spawn_1", 0, 5, 0)

Parameters:

  • entityIdOrInstanceId (number|string): Script entity id (self.entity) or scene instance id string
  • x (number): X coordinate
  • y (number): Y coordinate
  • z (number): Z coordinate

Returns: None

ID model: Script components use numeric self.entity. Scene instances and Engine.spawnEntity use string instance ids. getEntity* / destroyEntity take instance ids; setEntity* accepts either form.

Ownership: String instance ids use per-caller spawn ownership. With a script caller, the id must be the caller's own assetId or a spawn created by that same script entity. Without a script caller (console), only console-owned spawns are accepted. Cross-script mutate/destroy of another script's spawnEntity ids is rejected. Rejected updates log a warning and do nothing (they do not throw).

Example:

function MyScript:on_update(dt)
self.time = self.time + dt
local y = math.sin(self.time) * 5
Engine.setEntityPosition(self.entity, 0, y, 0)
end

Engine.setEntityScale(entityIdOrInstanceId, x, y, z)

Set the scale of an entity. Accepts the same id forms as setEntityPosition (script entity id or scene/spawn instance id string). Scripts may transform instance ids returned from Engine.spawnEntity in the current session.

Engine.setEntityScale(self.entity, 2.0, 2.0, 2.0)
Engine.setEntityScale("lua_spawn_1", 2.0, 2.0, 2.0)

Parameters:

  • entityIdOrInstanceId (number|string): Script entity id or instance id
  • x (number): X scale (must be > 0)
  • y (number): Y scale (must be > 0)
  • z (number): Z scale (must be > 0)

Returns: None

Example:

function MyScript:on_start()
self.baseScale = 1.0
self.pulseSpeed = 2.0
end

function MyScript:on_update(dt)
self.time = self.time + dt
local scale = self.baseScale + math.sin(self.time * self.pulseSpeed) * 0.2
Engine.setEntityScale(self.entity, scale, scale, scale)
end

Engine.setEntityRotation(entityIdOrInstanceId, x, y, z)

Set the rotation of an entity (Euler angles in degrees). Same ownership rules as setEntityPosition / setEntityScale.

Engine.setEntityRotation(self.entity, 0, 45, 0)
Engine.setEntityRotation("lua_spawn_1", 0, 45, 0)

Parameters:

  • entityIdOrInstanceId (number|string): Script entity id or instance id
  • x (number): X rotation in degrees (pitch)
  • y (number): Y rotation in degrees (yaw)
  • z (number): Z rotation in degrees (roll)

Returns: None

Example:

function MyScript:on_update(dt)
self.rotation = (self.rotation or 0) + dt * 90 -- 90 degrees per second
Engine.setEntityRotation(self.entity, 0, self.rotation, 0)
end

Engine.setEntityVisible(entityIdOrInstanceId, visible)

Show or hide a renderable entity. Same ownership rules as setEntityPosition (script entity id, or session-spawned / owned instance id).

Engine.setEntityVisible(self.entity, false)
Engine.setEntityVisible("lua_spawn_1", true)

Parameters:

  • entityIdOrInstanceId (number|string): Script entity id or instance id
  • visible (boolean): Whether the entity should render

Returns: boolean - true if the visibility change was applied

Engine.getEntityVisible(entityIdOrInstanceId)

Query whether a renderable entity is visible.

local visible = Engine.getEntityVisible("lua_spawn_1")

Parameters:

  • entityIdOrInstanceId (number|string): Script entity id or instance id

Returns: boolean or nil if the entity is missing / not renderable

Engine.setLightColor(instanceId, r, g, b)

Set RGB color on a light entity. Intended for lights created via Engine.spawnEntity by the calling script (per-caller ownership).

Engine.setLightColor(lightId, 1.0, 0.5, 0.2)

Returns: boolean - true if applied

Engine.setLightIntensity(instanceId, intensity)

Set light intensity multiplier.

Engine.setLightIntensity(lightId, 2.5)

Returns: boolean - true if applied

Engine.setLightEnabled(instanceId, enabled)

Enable or disable a light without destroying it.

Engine.setLightEnabled(lightId, false)

Returns: boolean - true if applied

Engine.setLightRange(instanceId, range)

Set range for point/spot lights (no-op for directional lights).

Engine.setLightRange(lightId, 20.0)

Returns: boolean - true if applied

Open-read / reconnaissance surface

Mutate/destroy APIs are per-caller ownership-gated (see Ownership under setters / destroyEntity). Getters and queries are not: any script in the scene can typically read transforms, light properties, overlap/raycast results, and camera state for ids it can name. Do not assume sandbox isolation between untrusted multi-script scenes for observation—only for mutate/destroy and network/filesystem limits.

Engine.getLightColor(instanceId) / getLightIntensity / getLightEnabled / getLightRange

Read light properties. Unlike setters, getters are not ownership-gated (same open-read model as getEntityPosition). Returns nil / falsey when the id is missing or not a light.

local color = Engine.getLightColor(lightId)       -- {x,y,z} or nil
local intensity = Engine.getLightIntensity(lightId)
local enabled = Engine.getLightEnabled(lightId) -- boolean or nil
local range = Engine.getLightRange(lightId) -- number or nil (point/spot)

Engine.getCamera()

Read the current camera position and forward vector from InputState.

local cam = Engine.getCamera()
-- cam.position = { x, y, z }
-- cam.forward = { x, y, z }

Returns: table with position and forward vec3 tables

Engine.setCamera(...)

Set camera position and forward. Accepts either six numbers or a table.

Engine.setCamera(0, 1.6, 5, 0, 0, -1)
Engine.setCamera({
position = { x = 0, y = 1.6, z = 5 },
forward = { x = 0, y = 0, z = -1 },
})

Forces a one-shot camera sync on the render/player side (forceCameraSync), updates the player physics body, and resets FPS velocity. Forward is normalized; a zero-length forward is an error.

Returns: None

Engine.triggerPortal(instanceId)

Trigger travel for a portal instance by id (uses the portal's destinationUrl through the shared travel/preview path).

function MyPortalScript:on_interact(actorId)
Engine.triggerPortal(self.asset_id) -- portal instance id (script env field)
end

Used with portals that have scriptTrigger: true (exactly one trigger mode). Prefer this when the destination should come from the portal component; use Scene.TravelTo(url) when the script chooses a URL.

Returns: true only if the portal exists, is active, and travel was requested; otherwise false.

Networking

Engine.httpRequest(options)

Make an async HTTPS GET request. Synchronous HTTP is disabled; a callback is required. Script HTTP never inherits the downloader “Allow HTTP” or private-network flags; HTTPS-only and public hosts by default.

Engine.httpRequest({
url = "https://api.example.com/data",
method = "GET",
callback = function(response)
if response.success then
print("Status: " .. tostring(response.status))
print("Body: " .. tostring(response.body))
else
print("Error: " .. tostring(response.error))
end
end
})

Parameters: options (table) with:

  • url (string, required): HTTPS URL
  • method (string, optional): HTTP method (only "GET" is supported)
  • callback (function, required): Invoked with the response table when complete

Returns: Immediately returns a small status table (request accepted / error). The response body is delivered to callback.

Restrictions:

  • Only HTTPS URLs are allowed
  • Only GET method is supported
  • Host allowlist may be enforced (if configured)
  • Response size is limited (default: 1MB)
  • Concurrent in-flight requests are capped

UI Functions

Engine.openTextBox(instanceId, title, text, buttons, checkboxes, callback)

Open a modal textbox overlay. This is unrelated to scene JSON type: "textbox" world billboards (see UI Elements).

local success = Engine.openTextBox(
"my-textbox",
"Welcome",
"Hello, World!",
{"OK", "Cancel"},
{["Remember"] = false},
function(button, checkboxStates)
print("Button: " .. tostring(button))
end
)

Parameters:

  • instanceId (string, required): Unique identifier for the textbox
  • title (string, optional): Textbox title
  • text (string, required): Textbox content
  • buttons (table, optional): Array of button labels
  • checkboxes (table, optional): Table mapping checkbox names to initial states
  • callback (function, optional): Callback function(button, checkboxStates)

Returns: boolean - true if opened successfully

Example:

function MyScript:on_interact(actorId)
Engine.openTextBox(
"quest-" .. tostring(self.entity),
"Quest Available",
"Would you like to accept this quest?",
{"Accept", "Decline"},
{["Show hints"] = true},
function(button, checkboxes)
if button == "Accept" then
print("Quest accepted!")
end
end
)
end

Engine.closeTextBox(instanceId)

Close a textbox modal.

Engine.closeTextBox("my-textbox")

Parameters:

  • instanceId (string, required): Textbox instance ID

Returns: boolean - true if closed successfully

Engine.openImGuiTextBox(title, text, buttons, checkboxes, callback)

Open an ImGui textbox (lightweight overlay).

Engine.openImGuiTextBox(
"Info",
"Quick message",
{"OK"}
)

Parameters: Same as openTextBox except no instanceId

Returns: boolean - true if opened successfully

Engine.closeImGuiTextBox()

Close the ImGui textbox.

Engine.closeImGuiTextBox()

Parameters: None

Returns: boolean - true if closed successfully

Audio Functions

Engine.playAudio(instanceId, assetId, position, ambient, loop, volume)

Play audio.

local success = Engine.playAudio(
"my-audio-instance",
"sound-effect",
{0, 0, 0}, -- position
false, -- not ambient (spatial)
false, -- not looping
0.8 -- volume
)

Parameters:

  • instanceId (string, required): Unique identifier for the audio instance
  • assetId (string, required): Audio asset ID
  • position (table, optional): Position {x, y, z} for spatial audio, or {0,0,0} for ambient
  • ambient (boolean, optional): true for constant volume, false for spatial (default: false)
  • loop (boolean, optional): true to loop (default: false)
  • volume (number, optional): Volume 0.0 to 1.0 (default: 1.0)

Returns: boolean - true if playback started

Spatial Audio: Volume varies with distance from position Ambient Audio: Constant volume regardless of position

Example:

function MyScript:on_interact(actorId)
-- Play spatial audio at entity position
Engine.playAudio(
"interaction-sound-" .. tostring(self.entity),
"click-sound",
{0, 0, 0}, -- Will use entity position
false, -- Spatial
false, -- No loop
1.0 -- Full volume
)

-- Play ambient background music
Engine.playAudio(
"bg-music",
"background-music",
{0, 0, 0},
true, -- Ambient
true, -- Loop
0.5 -- Half volume
)
end

Engine.stopAudio(instanceId)

Stop audio playback.

Engine.stopAudio("my-audio-instance")

Parameters:

  • instanceId (string, required): Audio instance ID

Returns: boolean - true if stopped

Physics / Entity Queries

Engine.raycast(origin, direction, maxDistance)

Cast a physics ray and return the first hit.

Parameters:

  • origin (table): {x, y, z}
  • direction (table): {x, y, z} (normalized preferred)
  • maxDistance (number, optional)

Returns: table with hit, instanceId, distance, position, normal (or nil fields when no hit)

Engine.sphereOverlap(center, radius)

Return instance ids whose physics bodies overlap a sphere.

Engine.getEntityPosition(instanceId) / getEntityRotation / getEntityScale

Read transform for a scene instance id (string). Returns a {x,y,z} table or nil.

Engine.spawnEntity(desc)

Spawn a runtime instance. Returns the new instance id string, or nil.

Supported types only: model, pointlight, spotlight, directionallight, audio.
Not spawnable at runtime: portals, volumes, textbox/picturebox billboards, fonts, etc. Author those in scene JSON. Unsupported type values fail (nil) with a log warning.

local id = Engine.spawnEntity({
type = "model", -- model | pointlight | spotlight | directionallight | audio
asset = "my-model-asset", -- required for model/audio
position = {x = 0, y = 1, z = 0},
rotation = {x = 0, y = 0, z = 0},
scale = {x = 1, y = 1, z = 1},
-- optional light props (lights):
-- color = {x = 1, y = 0.5, z = 0.2}, intensity = 2.5, enabled = true, range = 12,
-- direction = {x = 0, y = -1, z = 0}, cutoff = 12.5, outerCutoff = 17.5,
-- optional audio props:
-- loop = false, volume = 1.0, autoPlay = true, ambient = false
})

Absent optional fields keep the current spawn defaults (white light intensity 1 / range 10; audio loop false, volume 1, autoPlay true).

Engine.destroyEntity(instanceId)

Destroy a non-critical instance by instance id.

Ownership (per-caller): The instance must be the caller's script assetId, or a spawn created by that same script via Engine.spawnEntity. Other scripts cannot destroy or mutate your spawns. Console/eval with no script entity may only affect console-owned spawns. Portals, volumes, and the player are always rejected. Unauthorized calls return false (and log a warning); they do not throw.

External URL Functions

Engine.openExternalUrl(url, callback)

Request opening an external URL in the system browser after user confirmation.

local success = Engine.openExternalUrl(
"https://example.com",
function(accepted)
if accepted then
print("User accepted opening URL")
else
print("User declined")
end
end
)

Parameters:

  • url (string, required): http:// or https:// URL
  • callback (function, optional): Callback function(accepted)

Returns: boolean - true if the confirm modal opened

Security / UX:

  • Confirm dialog emphasizes the host (full URL secondary) and that you are leaving DDDBrowser
  • Private/reserved targets are rejected
  • Default application logs redact URL paths (scheme://host/***)

Example:

function MyScript:on_interact(actorId)
Engine.openExternalUrl(
"https://example.com/help",
function(accepted)
if accepted then
print("Opening help page")
end
end
)
end

API Availability

Always check for API availability before use:

if Engine and Engine.setEntityPosition then
Engine.setEntityPosition(self.entity, x, y, z)
end

This prevents errors if the API is not available.

Error Handling

Engine functions may fail silently or return error indicators:

  • Check return values (booleans for success)
  • Check response tables for error fields
  • Handle nil returns gracefully
  • Log errors for debugging

Next Steps