Cross-object interactions
Find nearby objects, destroy or knock them back, inspect their transforms, handle triggers, and coordinate state between scripts.
Find another object at runtime
Object scripts can look up scene objects by UUID, exact name, name prefix, or object type. Spatial queries return UUIDs for overlapping physics bodies; resolve each UUID with gameObject.getObject(id) before reading its transform or calling methods on it.
| Method | Result |
|---|---|
getObject(uuid) | One object API or nil |
getObjectByName(name) | The first exact-name match or nil |
getObjectsByNamePrefix(prefix) | An array of object APIs |
getObjectsByType(type) | An array of object APIs |
sphereQuery(radius) | UUIDs of physics bodies overlapping a sphere centered on this object |
boxQuery(halfExtents) | UUIDs of physics bodies overlapping a box centered on this object |
Filter spatial results
Spatial queries return physics-body UUIDs, not names, types, or tags. Build a set from a name-prefix lookup when only a particular group should react.
Characters count as physics bodies here. Raycasts and sphere and box queries all find a character through a hitbox matching its standing or crouched capsule, reported under the character's own UUID, so a player's hitscan can hit an NPC character directly. A query a character casts from its own position starts inside its own hitbox, so skip results equal to gameObject.id.
Destroy an object
Every resolved object API — including gameObject itself — exposes destroy(). Calling it on an object you looked up (for example gameObject.getObject(id).destroy()) removes that object, not the one the current script is attached to. From a scene script, scene.destroyObject(id) and scene.destroyObjectByRef(object) do the same thing by id or by reference.
All three remove any scene object, authored or runtime-created, together with every object parented under it. Removal is deferred until the current callback (update, onTriggerEnter, etc.) finishes, so it is safe to destroy an object from inside its own collision or trigger handler. Each returns true when the object was queued for removal and false when there was nothing to do — the id does not resolve, the object is already on its way out, or the simulation is not running.
Authored objects come back on stop
An authored object removed at runtime returns automatically when simulation stops, so the scene file itself is never modified. An object created with scene.createObject() is gone for good once destroyed.
Destroying objects in a multiplayer room
Only scripts that run on the room server — the scene script, characters' and vehicles' control scripts, and objects declared "authority" — may destroy an object, and players see it disappear only if it is shared with them: a slotted character or vehicle, a scripted object declared "authority", an unscripted object with Replicate turned on, or a copy made with multiplayer.spawn. To make breakable crates work in a room, turn on each one's Replicate setting, or give it a script and declare it "authority" (at most 32 such props per scene). See Build and test a multiplayer game.
Example: destroy nearby crates
This is the simplest pattern for a Ground Pound ability. Name the breakable rigid bodies with a shared prefix such as Ground Pound Crate, keep them dynamic, and call destroyNearbyCrates() once when the character lands. If the controller already defines init(), merge the crate lookup into that function instead of defining a second one.
local GROUND_POUND_RADIUS = 1.5
local CRATE_NAME_PREFIX = "Ground Pound Crate"
local crateIds = {}
function init()
local crates = gameObject.getObjectsByNamePrefix(CRATE_NAME_PREFIX)
for _, crate in ipairs(crates) do
crateIds[crate.id] = true
end
end
function destroyNearbyCrates()
local nearbyIds = gameObject.sphereQuery(GROUND_POUND_RADIUS)
for _, id in ipairs(nearbyIds) do
if crateIds[id] then
local crate = gameObject.getObject(id)
if crate then
crate.destroy()
crateIds[id] = nil
end
end
end
end
Clearing the id from crateIds after destroying it isn't strictly required — a destroyed object's id simply won't resolve again — but it keeps the table from growing stale across many stomps.
Alternative: knock crates away with an impulse
Use an impulse instead of destroy() when the crate should go flying rather than disappear — for example if it should still be collectible or collidable afterward.
function knockBackNearbyCrates()
local playerPosition = gameObject.getPosition()
local nearbyIds = gameObject.sphereQuery(GROUND_POUND_RADIUS)
local hitIds = {}
for _, id in ipairs(nearbyIds) do
if crateIds[id] and not hitIds[id] then
hitIds[id] = true
local crate = gameObject.getObject(id)
if crate then
local cratePosition = crate.getPosition()
local dx = cratePosition.x - playerPosition.x
local dz = cratePosition.z - playerPosition.z
local distance = math.sqrt(dx * dx + dz * dz)
if distance < 0.001 then
dx, dz, distance = 1, 0, 1
end
crate.addImpulse(
dx / distance * 20,
8,
dz / distance * 20
)
end
end
end
end
The impulse values are a starting point for a mass-5 crate. Increase or decrease them to match the desired launch speed. addForce and addTorque are also available on the resolved object API.
The same three calls work if the thing you resolved is a soft body, but the numbers above will not visibly move one. A soft body's mass is its vertex count rather than its Mass field, so the default soft sphere weighs 162 kg against this crate's 5 — an impulse of 20 becomes about 0.12 m/s. Scale by the vertex count. See Physics, collisions, and triggers.
Share state between object scripts
A resolved object API exposes getVar(name) and setVar(name, value). Define the variable on the target object in the inspector first, then another script can update it:
local crate = gameObject.getObject(crateId)
if crate then
crate.setVar("broken", true)
end
The crate's own script can read gameObject.getVar("broken") during update(deltaTime). Primitive values are numbers, strings, and booleans. An editor-configured object_ref variable is returned as another object API.
In a multiplayer room only scripts on the room server may call setVar, and object variables are not replicated: each browser keeps its own copy, so a value set on the server never reaches players. Use multiplayer state for anything players need to see; see Build and test a multiplayer game.
Trigger callback payloads
Call gameObject.registerForCollisions() in init(), then define onTriggerEnter(other) and onTriggerExit(other). Trigger callbacks can run on scripts attached to either side of the overlap. A script that defines no onTriggerEnter or onTriggerExit receives the overlap in onCollisionEnter or onCollisionExit instead, so an existing collision script keeps working when its object becomes a trigger.
function init()
gameObject.registerForCollisions()
end
function onTriggerEnter(other)
print("Entered trigger: " .. other.id)
local object = gameObject.getObject(other.id)
if object then
object.setVar("insideHazard", true)
end
end
function onTriggerExit(other)
print("Left trigger: " .. other.id)
end
other is a callback snapshot, not an object API. On entry it contains id, position, rotation, normal, contactPoint, contactDepth, and isTrigger. On exit, rely on id, position, and rotation. Resolve other.id when you need current state or object methods.
Calling a method on the snapshot directly — other.setPosition(...), other.setVar(...) — raises, because those fields are nil on a plain table. The error stops the callback where it stands, so the effect you expected never happens and the overlap looks as though it was never detected. See Troubleshooting for how to confirm which of the two it is.
Combining a trigger volume with destroy() is the other common way to break a crate: give the crate an isSensor child (or make the crate itself a trigger the player's stomp overlaps), then call gameObject.destroy() from that object's own onTriggerEnter(other) when other.id is the player.