objects#
Acting on other objects, the ones you did not spawn and are not standing in.
Use it to
- find other objects by id, tag, name or distance
- read where they are, what they are set to and how much health they have
- change, tag, damage or remove the ones you are allowed to edit
At a glance#
What you want done, and the verb that does it.
Find them
local objects = require("objects")objects also works without the require, as a ready-made global.
Finding one#
me#
objects:me() → object, or nil
This script's own object, as an ordinary reference. This is how a script reads or changes its own tags.
nil on a personal HUD and on a worn item: neither is an object in the world.
get#
objects:get(id) → object, or nil
One object by its id. Only persistent objects. Things world:spawn made are not in here.
tagged#
objects:tagged(tag) → list, or nil, reason
Every object carrying that tag.
local lamps = objects:tagged("streetlight")if lamps then for _, lamp in ipairs(lamps) do lamp:set({ light = true }) endendRefused with a reason when more than 256 objects match. A query that broad is almost always a mistake, and answering it would stall the region.
nearby#
objects:nearby(point, radius) → list, or nil, reason
Everything within radius metres of a point, nearest first. The point may be a vec or three numbers.
local vec = require("vec")for _, crate in ipairs(objects:nearby(self:position(), 8) or {}) do if crate:has_tag("crate") then crate:remove() endendLike every query on this page it refuses rather than truncates: too many matches comes back as nil and a reason, never as a shortened list.
named#
objects:named(name) → list, or nil, reason
Exact display-name lookup, ignoring capitals. Names are not unique, so this always answers a list.
Tip
Prefer tags to names. A name is what a person reads and may rename on a whim; a tag is what a script means. Tagging every lamp "streetlight" survives somebody renaming one to "Lamp (broken)".
What you get back#
Each result carries four readings:
| Field | Meaning |
|---|---|
id | its permanent id in this region |
name | its display name, or nil once it has been removed |
owner | the id of whoever owns it, or nil if unstamped |
tags | its tags, sorted. A snapshot, not a live view |
obj:exists() -- is it still in the world?obj:has_tag("door") -- ignoring capitalsMoving it#
| Verb | Meaning |
|---|---|
obj:move_to(x, y, z) | put it there |
obj:rotate_to(x, y, z) | face that way, degrees |
obj:scale_to(n) | resize. One number is uniform, three are per axis |
obj:move_by(x, y, z) | move by a step, from wherever it actually is |
obj:rotate_by(x, y, z) | turn by an angle, about its own axes |
obj:look_at(target) | turn to face a point or another object, staying upright. A group's root only |
obj:push(x, y, z) | kick a dynamic object: adds to its velocity, like self:push |
obj:set_velocity(x, y, z) | set a dynamic object's velocity outright. (0, 0, 0) stops it dead |
All answer true, or nil plus a reason. Coordinates are local to the parent when the object is inside a group and world coordinates when it stands alone, the same rule as everywhere else.
Requires that you own the object, or have been granted edit rights over it.
Tip
Reaching for move_by before move_to. The relative pair is added to the object's live transform at the moment it is applied, so it is the version that survives somebody else moving the object in between. Prefer it whenever "a bit more" is what you mean.
Reading it#
The same readings self has, for any object you can name.
| Reading | Answer |
|---|---|
obj:position() | a vec, or nil |
obj:rotation() | a vec of Euler degrees, or nil |
obj:scale() | a vec, one entry per axis, or nil |
obj:velocity() | a vec in metres per second, or nil. Zero for anything that is not a dynamic body |
local door = objects:named("Main Door")[1]local p = door:position()if p then print(p.x, p.y, p.z)endNo permission is needed to look. Where something is is already on everyone's screen; it is changing it that asks whether the object is yours.
distance_to#
obj:distance_to(target) → number, or nil, reason
The target may be another object, a vec, or three numbers.
if turret:distance_to(player.pos) < 20 then fire() endif turret:distance_to(door) < 3 then door:set({ light = true }) endproperties#
obj:get(key) → value, or nil, or nil, reason
Any property of another object, by the same names self:get takes: color, light, your own state fields like open. No permission is needed, for the same reason as position: every property is already drawn on every player's screen. What an object keeps to itself belongs in its store, which nobody else can read.
local door = objects:named("Main Door")[1]if door:get("open") then print("the door is open")endThe reading is the tick's. A reference you made in this same event reads nil until the next tick, because that is when the region first copies its properties in. Get your references once, at the top of the script, and read them later from timers and events.
health#
obj:health() → health, max, or nil
What a damageable object has left and what it counts down from. nil for an object with no max_health.
local hp, max = tree:health()if hp then bar.width = hp / maxendparent / children#
obj:parent() → object, or nil obj:children() → list of objects
Walk a group. parent is the object this one is a piece of (nil for a root); children are its direct pieces, in id order. A car's script finds its wheels without tagging them:
local car = objects:me()for _, part in car:children() do if part.name == "Wheel" then part:tween({ rot = vec(0, 0, 360), seconds = 1 }) endendWhy these can answer nil#
The reading comes from the region's mirror of the tick, exactly like self:position. It is nil in two cases, and both are real answers rather than failures:
- the object has left the world;
- the very first read in a region's life, before the region's next tick has published the mirror. Nothing is mirrored until a script asks for it: a world whose scripts never look at another object never pays for the mirror at all, so the first question arrives one tick early. Ask again on your next timer.
Never treat nil as the origin. A script that writes local p = obj:position() or vec() has just decided that a missing object is standing at 0, 0, 0.
Changing it#
set#
obj:set(changes) → true, or nil, reason
The same property table self:set takes.
lamp:set({ light = true, emissive = 2, color = "#ffe9b0" })tween#
obj:tween(opts) → true, or nil, reason
A smooth change, run by the region. Same options as self:tween.
No handle comes back. Use self:after(seconds, ...) when something must happen on arrival.
particles#
obj:particles(spec) → true, or nil, reason
self:particles for an object you may edit: the torch a lever lights, the chimney a stove starts. The same table; nil puts it out.
add_tag / remove_tag#
obj:add_tag(tag) → true, or nil, reason obj:remove_tag(tag) → true, or nil, reason
One tag at a time, applied atomically. Up to 16 tags per object. Removing a tag that is not there is harmless.
Tip
Tags are how objects announce themselves. You can read another object's properties with get, but a tag is what makes it findable: objects:tagged("door.open") lists every open door in one call, where get would have you look at each door in turn.
damage / heal#
obj:damage(amount, { by = uuid }) → true, or nil, reason obj:heal(amount) → true, or nil, reason
Hurts or mends a damageable object, meaning one with a max_health.
Unlike self:damage, these answer true, not the remaining health: the blow lands after your event. Read what is left with obj:health() on a later tick, or listen for world.destroyed.
remove#
obj:remove() → true, or nil, reason
Takes it out of the world, cascading through its group.
It goes to the world's trash, where it can be restored with its scripts and its memory intact. It is not an erasure.
A worked example#
Every lamp in the world comes on at dusk and goes off at dawn, driven by one object:
--!strictlocal self = require("self")local objects = require("objects")local world = require("world") local DAY = 600 -- a full cycle, in seconds local lit = false local function set_all(on: boolean) local lamps = objects:tagged("streetlight") if not lamps then print("too many lamps to address at once") return end for _, lamp in ipairs(lamps) do lamp:set({ light = on, emissive = on and 2.5 or 0, }) endend self:on_timer(10, function() local phase = world:clock() % DAY local should = phase > DAY * 0.5 if should ~= lit then lit = should set_all(lit) endend)Two things worth stealing:
- It only acts when the answer changes, not every ten seconds. Writing the same properties over and over would spend the region's budget for nothing.
- The lamps are found by tag, so adding a lamp to the world is a build action, not a code change.
See also#
- self is your own object
- events tells other scripts instead of reaching into them
- Object properties lists every key
setaccepts
Updated 17 October 2026