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#
--!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:
--!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#
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#
local function in_hand(): boolean return self:held_at() == "right_hand"endCareful
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#
self:on_drop(function(p, reason) print(p.name .. " let go: " .. reason)end)| Reason | What 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:
--!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.
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 })endHanding 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:
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#
| Reason | What 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