Hungrit Docs

Pickups and carrying#

An object a player can pick up, carry around and put down. It stays in the region. Carrying is not the same as an inventory.

The smallest version#

Luau
--!strictlocal self = require("self") self:on_touch(function(p)    self:hold({ player = p.id, at = "right_hand", settle = true })end)

The click is the interaction that grants permission, which is why this works and why handing something to somebody who has not touched anything does not.

settle = true says: be physical while it falls, ordinary static geometry once it stops. Set it here rather than on drop, because three of the four ways a carry can end never call drop at all.

Giving it a proper pose#

Centred on the bone with no rotation, anything longer than a coin runs through the body:

Luau
--!strictlocal self = require("self") local GRIP = {    pos = { x = 0.04, y = -0.02, z = -0.12 },    rot = { x = 0, y = 90, z = 0 },} self:on_touch(function(p)    self:hold({        player = p.id,        at = "right_hand",        pos = GRIP.pos,        rot = GRIP.rot,        settle = true,    })end)

Find the numbers with the grip window (</> → Pegada) rather than guessing. It lists what you are carrying, one row per socket, and hands back a table.

The axes are the same on every socket: +x right, +y up, −z forward. See Sockets.

Putting it down#

Luau
local players = require("players") self:on_action("drop", { key = "q" }, function(p)    if self:held_by() ~= p.id then        return    end     local who = players:get(p.id)    if who then        self:drop({            throw = { x = who.look.x * 4, y = 2, z = who.look.z * 4 },            settle = true,        })    else        self:drop({ settle = true })    endend)

Throw it forward, a little. Without the push it lands on the carrier's own feet and is immediately picked up again by the same click.

Always ask the engine who is holding it#

Luau
local function in_hand(): boolean    return self:held_at() == "right_hand"end

Careful

Never keep your own copy of "I am held". An object leaves a hand by routes your script never runs: the player pressing the put-down key, walking out of the region, losing their connection. A local variable would go on claiming an object lying on the floor is in somebody's hand.

Reacting to every ending#

Luau
self:on_drop(function(p, reason)    print(p.name .. " let go: " .. reason)end)
ReasonWhat happened
"script"you called self:drop
"released"they put it down themselves. Always available to them
"left"they are out of the region, including a lost connection
"gone"the object was deleted while held

Register this if the object matters. Three of the four are not something you asked for, and they are what keeps your bookkeeping honest.

A prompt that appears when you look at it#

A world HUD hanging on the object, with a hold-to-take gesture:

Luau
--!strictlocal self = require("self")local players = require("players") local PROMPT = {    id = "take",    world = { height = 1.1, distance = 3, fade = 6, gaze = 25 },    size = { 90, 90 },    vars = { grab = 0 },    pages = { main = {        { "ring", var = "grab", size = 40, thickness = 3 },        { "text", "Hold E", size = 11, align = "center" },        { "hold", key = "e", dur = 1.0, var = "grab", emit = "take" },    } },} self:on_region({    radius = 4,    near = function(p) p:show_hud(PROMPT) end,    far  = function(p) p:hide_hud("take") end,}) self:on_hud(function(p, event)    if event ~= "take" then return end    p:hide_hud("take")    self:hold({ player = p.id, settle = true })end)

The ring fills on the player's own machine, so nothing is sent while they hold the key. Only the completed emit reaches your script, and pressing that button is itself the interaction that permits hold.

Moving between hand and back#

Calling hold again with a different socket moves it. There is no second verb, and no new interaction is needed.

Luau
local ON_BACK = { pos = { x = 0, y = 0.1, z = 0.14 }, rot = { x = 0, y = 90, z = 30 } }local IN_HAND = { pos = { x = 0.04, y = -0.02, z = -0.12 }, rot = { x = 0, y = 90, z = 0 } } local function stow(id: string)    self:hold({ player = id, at = "back", pos = ON_BACK.pos, rot = ON_BACK.rot })end local function draw(id: string)    self:hold({ player = id, at = "right_hand", pos = IN_HAND.pos, rot = IN_HAND.rot })end

Handing out a copy instead of the object itself#

If two people should each be able to take one, do not move the original. Make another:

Luau
local world = require("world") world:spawn({    copy = true,    hold = buyer_id,    at = "right_hand",    grip = IN_HAND,    pos = counter,    life = 0,})

copy = true brings the shape, the properties, the children and the scripts, with fresh memory. life = 0 means it lasts as long as the region runs, rather than the default thirty seconds.

Careful

pos and grip are different things. pos is where it is born in the world; grip is where it sits on the bone. Putting world coordinates in grip puts the object metres away from the hand that bought it.

Refusals worth handling#

ReasonWhat to do
"permission"they have not interacted with this object. A touch, or a button on your HUD
"held"somebody else has it. Taking it from their hands is a rule you write
"full"their hands are full
"child"hold the root of a group, not one of its pieces
"socket"check the name against Sockets

See also#

Updated 04 September 2026