Scene scripts, variables, and storage
Coordinate a level with scene APIs, runtime objects, scene variables, audio, and persistent storage.
Scene scripts
Assign one project script in Scene Settings. It receives the same init, update, and destroy lifecycle but runs once for the scene rather than once per object.
Every object and scene script can call scene.loadScene(idOrName), scene.restart(), scene.pauseGame(), and scene.resumeGame(). Restart reloads the current authored scene in Preview or published play and returns false in editor Play, where no scene loader exists. Pause freezes physics, per-frame updates, and gameplay audio while leaving GUI rendering and onClick active; resume continues the same run.
In a multiplayer room the scene script runs on the room server, not in any player's browser. Only the scene script may call scene.loadScene and scene.restart there (publication refuses either call in an object's script), and every player moves with the room. Nothing carries over unless the optional second table names it: scene.loadScene("Arena", { keep = { game = { "round" } } }) keeps the round key of game state, and keep may also list participant, avatar and private keys. scene.restart({ keep = ... }) takes the same table. Only the scene script and other authority scripts may call pauseGame and resumeGame; a GUI button cannot. See Build and test a multiplayer game.
Lookup and physics
Scene scripts can call scene.getObjectByName(name), scene.getObjectsByNamePrefix(prefix), scene.getObject(id), scene.getObjectsByType(type), scene.getGravity(), scene.setGravity(x,y,z), and scene.log(message). These are not on an object script's scene — there scene.getObjectByName is nil. Object scripts make the same four lookups through gameObject instead (gameObject.getObjectByName(name) and so on); gravity is scene-script-only. In a multiplayer room only the scene script, on the room server, may call setGravity.
scene.findPath(from, to) and scene.getNearestNavPoint(point) query the navigation mesh from arbitrary world-space points. They are available to object scripts too and return nil until the bake completes or when no reachable answer exists.
Cameras
scene.setActiveCamera(id) selects which authored camera renders the running scene and returns true. Pass the camera object's UUID, not its name; it returns false if the ID does not resolve to a camera in the running scene. The change is runtime-only — it does not alter the authored isActive setting you see when the scene is next opened in the editor. scene.getActiveCamera() returns the object API for whichever camera is currently active, or nil if none is. Both are available to object scripts as well as scene scripts. In a multiplayer room the camera belongs to each player's browser, so call them from a camera or GUI script there; the scene script and characters' control scripts cannot.
-- Hand control to a second character's camera mid-game.
scene.setActiveCamera("b4c74f8a-c2c9-4e03-9a83-6ea8036bba7a")
-- Read the live transform of whatever is rendering now.
local camera = scene.getActiveCamera()
if camera then
local forward = camera.getForward()
end
gameObject.setVisible() does not affect a camera's render state — it only controls the visibility of GUI objects. Use scene.setActiveCamera() instead.
Scene variables
scene.getVar(name) reads number, string, boolean, or object-reference values. scene.setVar(name,value) updates primitive values for the running scene.
Scene variables stay on one browser
They are not replicated. Every browser in a multiplayer room keeps its own copy, so a value the scene script writes with scene.setVar — on the room server — never reaches any player, whatever the variable is named. Share state between players with the multiplayer state partitions instead; see Build and test a multiplayer game.
Runtime objects
scene.createObject(type, options) creates a simulation-only object and returns its object API. type is one of cube, sphere, cylinder, capsule, heightField, water, camera, empty, ambientLight, directionalLight, pointLight, hemisphereLight, guiLabel, guiPanel, guiImage, guiButton, mesh, or character. options is optional and accepts name, position, rotation (a quaternion {x=, y=, z=, w=}), scale, motion ("static"|"dynamic"|"rig"), materialId, plus shape-specific fields: extent (cube and water), radius (sphere, cylinder, capsule), halfHeight (cylinder, capsule), and buoyancy, linearDrag, angularDrag, fluidVelocity (water). Anything you leave out takes the type's default — including motion, which is "static" for the primitive shapes, so pass motion = "dynamic" for something that should fall or be pushed. An unknown type returns nil. createObject is a scene-script call; object scripts do not have it. A runtime object created this way is not saved to the scene file. In a multiplayer room only authority scripts may create or destroy objects, and an object created at runtime is not shared with players; copy a spawn template with multiplayer.spawn for one every player sees.
scene.destroyObject(id) and scene.destroyObjectByRef(object) remove any scene object — authored or runtime-created — for the rest of the current simulation; authored objects return automatically when simulation stops. See Cross-object interactions for the full destroy API, including the per-object destroy() method available on any resolved object API.
Audio and scene transitions
The audio calls on scene are on every script's scene, object scripts included. Use scene.playSound(assetId, options) for one-shot effects that are not tied to a place in the world — UI blips, stingers — and stop or check them with scene.stopSound(soundId, fadeOutSeconds?) and scene.isSoundPlaying(soundId), using the id playSound returned. options accepts volume, loop, playbackRate, and fadeIn. scene.stopAllSounds(fadeOutSeconds?) stops every active effect at once (music is untouched). For background music, use scene.playMusic(assetId, options) (volume, loop, fadeIn, and crossfade seconds of overlap with the current track), scene.stopMusic, scene.setMusicVolume, and scene.setSfxVolume. For a sound that comes from an object, call gameObject.playSound from that object's script instead; it plays positionally at the object, and scene.stopSound can stop it too.
In a multiplayer room the scene script runs on the room server, which has no speakers, so these calls are refused there. Play a room's music and non-positional sound from a camera, GUI, or presentation script's scene instead, or positional sound with its gameObject.playSound; those scripts run in each player's browser. A scene's Music setting plays in a room without any script. scene.loadScene(idOrName) queues a transition to another included scene and returns ok, reason: false, "unknown_scene" when no included scene has that id or name; use scene.restart() when the destination is the current scene so renaming it cannot break the script.
Game storage
local highScore = gameStorage.get("highScore") or 0
gameStorage.set("highScore", math.max(highScore, 250))
gameStorage.remove("temporaryValue")
-- gameStorage.clear() removes every value for this game.
Keys are non-empty strings. Values can be numbers, strings, booleans, or tables of those (lists or keyed tables, nested); a function, math.huge, or a table that contains itself raises an error. gameStorage.get returns a copy, so change a stored table by setting it again. Values are shared by every scene of the game.
gameStorage is stored on the player's own device. In a multiplayer room only GUI, camera, and presentation scripts may use it, and it is never shared between players — keep anything the room must agree on in multiplayer state.