Hungrit Docs

Moving things#

Platforms, lifts, spinning parts, patrols and anything else that travels.

Choosing how to move something#

You wantUse
there, nowself:move_to
there, smoothlyself:tween
turn a bit more, repeatedlyself:rotate_by
a shove, with physicsself:push
a constant speed, with physicsself:set_velocity
a path you already know at birthworld:spawn{ to =, seconds = }

Tip

Prefer tween to a timer that nudges. The region runs the movement and nothing is sent per frame, so it is smooth for everyone regardless of their connection, and your script sleeps through it.

A lift#

Luau
--!strictlocal self = require("self") local BOTTOM = { x = 0, y = 0.5, z = 0 }local TOP    = { x = 0, y = 8.0, z = 0 }local TRAVEL = 3 local up = falselocal busy = false self:on_touch(function()    if busy then return end     busy = true    up = not up     local t = self:tween({        to = up and TOP or BOTTOM,        seconds = TRAVEL,        ease = "in_out",    })     if t then        t:on_done(function() busy = false end)    else        busy = false    endend)

The busy flag matters. Without it, clicking during the journey starts a new tween that replaces the running one, and the lift changes its mind halfway with somebody standing on it.

A platform that goes back and forth forever#

Luau
--!strictlocal self = require("self") local A = { x = -6, y = 2, z = 0 }local B = { x =  6, y = 2, z = 0 }local TRAVEL = 4local PAUSE = 1 local at_a = true local function leg()    at_a = not at_a    local t = self:tween({        to = at_a and A or B,        seconds = TRAVEL,        ease = "in_out",    })    if t then        t:on_done(function()            self:after(PAUSE, leg)        end)    endend self:move_to(A.x, A.y, A.z)leg()

Chaining on on_done rather than running a repeating timer means the pause is measured from the arrival, so the two never drift apart.

Something that spins#

Luau
self:on_timer(0.05, function()    self:rotate_by(0, 6, 0)end)

rotate_by adds to the current orientation, so repeated calls accumulate cleanly. Repeated absolute angles do not. They can lock up when two axes line up.

For a constant spin you never change, one long tween is cheaper than a timer:

Luau
self:tween({ rot = { x = 0, y = 360, z = 0 }, seconds = 8, ease = "linear" })

A door on a hinge, using a part#

Luau
local leaf = self:part("leaf")if leaf then    leaf:rotate_by(0, 90, 0)end

A part's coordinates are local to the group, so a hinged door built with its pivot at the hinge turns correctly wherever the group is standing.

A patrol between several points#

Luau
--!strictlocal self = require("self") local ROUTE = {    { x = 0,  y = 1, z = 0 },    { x = 10, y = 1, z = 0 },    { x = 10, y = 1, z = 10 },    { x = 0,  y = 1, z = 10 },} local i = 0 local function step()    i = (i % #ROUTE) + 1    local target = ROUTE[i]     self:look_at(target.x, target.y, target.z)     local t = self:tween({ to = target, seconds = 5, ease = "linear" })    if t then        t:on_done(step)    endend step()

look_at keeps the object upright and only uses the horizontal direction, so a target above or below does not tip it over.

A conveyor#

Luau
self:set({ dynamic = true })self:set_velocity(2, 0, 0)

set_velocity needs a physics body. Without dynamic = true you get nil, "not dynamic".

A projectile#

State the whole flight at birth rather than animating it:

Luau
local world = require("world") world:spawn({    model = "arrow",    pos = tip,    to = target,    seconds = 0.4,    collide = false,})

One message instead of about fifty, and a player on a slow connection sees the shot already in progress when it reaches them rather than starting at the muzzle a round trip late.

Moving something you spawned#

Luau
local world = require("world") local id = world:spawn({ shape = "box", pos = start })world:tween(id, { to = finish, seconds = 3 })world:move(id, 0, 5, 0)world:despawn(id)

Careful

self:tween moves the object the script is on. Used on a turret it sends the turret flying at the player while its projectiles sit at the muzzle. self: is me; world: with an id is the thing I made.

Making a click feel instant#

The player's own machine can play the movement immediately:

Luau
self:on_touch({    predict = {        { when = "up",            set = { up = false },          tween = { to = BOTTOM, seconds = 3 } },        { when = { "not", "up" }, set = { up = true },          tween = { to = TOP, seconds = 3 } },    },    server = function(p) print(p.name .. " called the lift") end,})

Only for movements that always happen. Anything refusable would be seen and then snapped back.

Limits worth remembering#

Tween length0.05 to 600 s
Coordinates±100 km
Velocity and pushup to 100 m/s
Effects per event8

See also#

Updated 04 September 2026