Skip to content

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
--!strict
local 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 — 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
--!strict
local 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 })
    end
end)

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

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
--!strict
local 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 — 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.

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

Hungrit scripting documentation.