Skip to content

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.

See also

Hungrit scripting documentation.