Appearance
Triggers and zones
Reacting to where people are, rather than to what they click.
Which one to use
| Event | Fires when | Good for |
|---|---|---|
on_region | somebody enters or leaves a sphere around the object | greeters, checkpoints, ambience, traps |
on_contact | somebody's body touches the object | pressure plates, lava, hazards |
on_touch | somebody clicks it | doors, buttons, pickups |
on_region does not need the object to be solid, or even visible. on_contact does — it is real physical contact.
A greeter
luau
--!strict
local self = require("self")
self:on_region({
radius = 6,
near = function(p)
p:show_hud({
id = "welcome",
anchor = "top",
pages = { main = {
{ "text", "Welcome, " .. p.name, size = 18, align = "center" },
} },
})
end,
far = function(p)
p:hide_hud("welcome")
end,
})Both are edges: each fires once per crossing, never repeatedly while somebody stands there.
Counting who is inside
luau
--!strict
local self = require("self")
local inside = 0
self:on_region({
radius = 8,
near = function()
inside += 1
if inside == 1 then
self:sound("hum", { volume = 0.3, fade = 1.0 })
end
end,
far = function()
inside -= 1
if inside <= 0 then
inside = 0
self:stop_sound()
end
end,
})Clamp at zero. A player who disconnects inside still gets their far — every near gets its pair — and you would rather be safe than negative.
The sphere follows the object
A moving platform brings its trigger with it, at no extra cost:
luau
self:on_region({ radius = 3, near = function(p) ... end })
self:tween({ to = far_side, seconds = 10 })A pressure plate
luau
--!strict
local self = require("self")
local events = require("events")
local on = 0
self:on_contact(function()
on += 1
if on == 1 then
self:set({ color = "#55e38b", emissive = 1 })
events:emit("plate", { down = true })
end
end)
self:on_contact_end(function()
on -= 1
if on <= 0 then
on = 0
self:set({ color = "#3a3a3a", emissive = 0 })
events:emit("plate", { down = false })
end
end)on_contact_end also fires when somebody leaves the world while standing on it, so the plate cannot get stuck down.
A hazard
luau
--!strict
local self = require("self")
local events = require("events")
local burning = {}
self:on_contact(function(p)
if burning[p.id] then return end
burning[p.id] = true
local function tick()
if not burning[p.id] then return end
events:emit("combat:hit", { target = p.id, amount = 8 })
self:after(0.5, tick)
end
tick()
end)
self:on_contact_end(function(p)
burning[p.id] = nil
end)The damage repeats on a chain of one-shot timers rather than a loop, so each slice stays short.
A checkpoint
luau
--!strict
local self = require("self")
self:on_region({
radius = 4,
near = function(p)
local here = self:position()
self.store:set("spawn." .. p.id,
here.x .. "," .. here.y .. "," .. here.z)
p:show_hud({
id = "checkpoint",
anchor = "top",
pages = { main = { { "text", "Checkpoint reached", align = "center" } } },
})
self:after(2, function()
p:hide_hud("checkpoint")
end)
end,
})Key by player id, never by name.
Store the three numbers with a separator you can split on later. tostring on a point gives you something readable but not parseable back.
Asking who is nearby, instead of waiting
Sometimes you want the list, not an event:
luau
local players = require("players")
local events = require("events")
self:on_timer(2, function()
local crowd = players:nearby(20)
if #crowd >= 5 then
events:emit("crowd", { n = #crowd })
end
end)nearby measures from the object this script is in, and reading it is free.
Ranges and behaviour
radius | 0.5 – 96 m |
| leaving | takes half a metre more than entering, so nobody on the line flickers |
calling on_region again | replaces the volume and re-announces whoever is inside |
radius = 0 or omitted | removes the volume |
