Doors and switches#
Doors that open on a click, close by themselves, open as you walk up, or answer to a switch across the room. Each one is complete: paste it onto a box.
A door that opens when you click it#
--!strictlocal 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 restartself: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)Click the door and it swings a quarter turn open. Click again and it closes. Whichever way you left it, that is how you find it after a restart.
A door that closes itself#
--!strictlocal self = require("self") local WAIT = 5local open = falselocal 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) endend)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:
--!strictlocal 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,})Walk within four metres and it opens ahead of you. It stays open until the last person has walked away.
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, so 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.
--!strictlocal self = require("self")local events = require("events") self:on_touch(function(player) events:emit("gate", { by = player.id })end)--!strictlocal 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)Touch the switch and the door swings, across the room, out of sight.
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#
self:on_touch(function(player) if player.id ~= self:owner() then return end -- ...end)A door that opens on a worn badge#
--!strictlocal 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 rather than 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:
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:
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,})Careful
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.
See also#
Updated 17 October 2026