Appearance
Doors and switches
A door that opens when you click it
luau
--!strict
local self = require("self")
local open = self.store:get("open") == "yes"
local function swing()
self:tween({
rot = { x = 0, y = open and 90 or 0, z = 0 },
seconds = 0.6,
ease = "in_out",
})
end
-- put it back where it was, without animating, after a restart
self:rotate_to(0, open and 90 or 0, 0)
self:on_touch(function()
open = not open
self.store:set("open", open and "yes" or "no")
swing()
end)A door that closes itself
luau
--!strict
local self = require("self")
local WAIT = 5
local open = false
local timer = nil
local function set(to_open: boolean)
open = to_open
self:tween({
rot = { x = 0, y = open and 90 or 0, z = 0 },
seconds = 0.6,
ease = "in_out",
})
end
self:on_touch(function()
if timer then
timer:cancel()
timer = nil
end
set(not open)
if open then
timer = self:after(WAIT, function()
timer = nil
if open then set(false) end
end)
end
end)Two habits worth keeping:
- Cancel the old timer before arming a new one. Without it, clicking twice leaves two timers and the door slams while somebody is walking through.
- Check the state inside the timer. Five seconds is long enough for somebody to have closed it by hand.
A door that opens by itself as you approach
No clicking at all:
luau
--!strict
local self = require("self")
local inside = 0
local function set(to_open: boolean)
self:tween({
rot = { x = 0, y = to_open and 90 or 0, z = 0 },
seconds = 0.5,
ease = "in_out",
})
end
self:on_region({
radius = 4,
near = function()
inside += 1
if inside == 1 then set(true) end
end,
far = function()
inside -= 1
if inside <= 0 then
inside = 0
set(false)
end
end,
})Count people, do not track a boolean. Two people inside and one leaving must not close the door. The counter is clamped at zero because a player who disconnects inside gets their far — every near gets its pair — and you would rather be safe than negative.
A switch across the room
The switch and the door are different objects. They talk with events.
On the switch:
luau
--!strict
local self = require("self")
local events = require("events")
self:on_touch(function(player)
events:emit("gate", { by = player.id })
end)On the door:
luau
--!strict
local self = require("self")
local events = require("events")
local open = false
events:listen("gate", function()
open = not open
self:tween({
rot = { x = 0, y = open and 90 or 0, z = 0 },
seconds = 0.6,
ease = "in_out",
})
end)Three switches and five doors work with no extra code: everything that emits "gate" opens everything that listens for it.
emit is private to your scripts. Another creator's build cannot hear it, even if they guess the name.
A door only the owner may open
luau
self:on_touch(function(player)
if player.id ~= self:owner() then
return
end
-- ...
end)A door that opens on a worn badge
luau
--!strict
local self = require("self")
local players = require("players")
local BADGE = "…the asset id of the badge…"
self:on_touch(function(player)
if not players:is_wearing(player.id, BADGE) then
print(player.name .. " has no badge")
return
end
-- ...
end)is_wearing counts visible items only, and answers nil — not false — if the person is not in the region.
Opening without moving
A door does not have to swing. Turning off collision lets people walk through a solid-looking doorway, which is what a force field or a shimmering portal wants:
luau
self:set({ collide = false, transparency = 0.6 })Making the click feel instant
A click travels to the server and back, which is a visible pause. Describe the movement as well, and the player's own machine plays it immediately:
luau
self:on_touch({
predict = {
{ when = "open", set = { open = false },
tween = { rot = { x = 0, y = 0, z = 0 }, seconds = 0.6 } },
{ when = { "not", "open" }, set = { open = true },
tween = { rot = { x = 0, y = 90, z = 0 }, seconds = 0.6 } },
},
server = function(player)
print(player.name .. " used the door")
end,
})Only predict what always succeeds. A locked door, a purchase or anything that can be refused would be seen to happen and then snap back.
