Hungrit Docs

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.

Luau
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.

Luau
local lamps = objects:tagged("streetlight")if lamps then    for _, lamp in ipairs(lamps) do        lamp:set({ light = true })    endend

Refused 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.

Luau
local vec = require("vec")for _, crate in ipairs(objects:nearby(self:position(), 8) or {}) do  if crate:has_tag("crate") then crate:remove() endend

Like 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:

FieldMeaning
idits permanent id in this region
nameits display name, or nil once it has been removed
ownerthe id of whoever owns it, or nil if unstamped
tagsits tags, sorted. A snapshot, not a live view
Luau
obj:exists()            -- is it still in the world?obj:has_tag("door")     -- ignoring capitals

Moving it#

VerbMeaning
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.

ReadingAnswer
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
Luau
local door = objects:named("Main Door")[1]local p = door:position()if p then  print(p.x, p.y, p.z)end

No 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.

Luau
if turret:distance_to(player.pos) < 20 then fire() endif turret:distance_to(door) < 3 then door:set({ light = true }) end

properties#

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.

Luau
local door = objects:named("Main Door")[1]if door:get("open") then    print("the door is open")end

The 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.

Luau
local hp, max = tree:health()if hp then    bar.width = hp / maxend

parent / 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:

Luau
local car = objects:me()for _, part in car:children() do    if part.name == "Wheel" then        part:tween({ rot = vec(0, 0, 360), seconds = 1 })    endend

Why 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.

Luau
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:

Luau
--!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#

Updated 17 October 2026