§ How to · with Picoo
How to make a door system in Roblox
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A door is the smallest interactive object in a game and the one most likely to end up half-open on someone else's screen.
Getting it right means moving a model correctly, animating it without desync, and putting the lock check somewhere the client cannot skip.
ProximityPrompt interaction
Range, hold duration and prompt text configured, with line-of-sight so you cannot open a door through a wall.
Swing, slide or lift
Any door shape driven by the same open/close state machine.
Server-side locking
Keys, keycards and team requirements checked on the server, never on the prompt.
Correct model movement
Whole-model doors move by pivot so hinges, handles and glass stay attached.
Auto-close
Doors close after a delay, and reopening resets the timer instead of stacking.
Sound and state
Open/close audio and a state value other systems can read.
Files Picoo ships for this prompt
2 files · 140 lines · ~25s · 1 credit
DoorService
State machine, lock validation, auto-close, tween driving.
105 lines
DoorConfig
Per-door type, swing angle, lock requirement, auto-close delay.
35 lines
Sample output: ServerScriptService.DoorService
-- A door made of several parts is a Model, and setting .CFrame on a Model does
-- not exist. PivotTo moves the whole assembly around its pivot — hinge, handle
-- and glass all keep their relationship. Moving the parts individually is how
-- doors end up with a handle floating a stud away from the slab.
local function setAngle(door: Model, angle: number)
door:PivotTo(door:GetAttribute("ClosedPivot") :: CFrame
* CFrame.Angles(0, math.rad(angle), 0))
end
-- The lock check belongs HERE, on the server, in the handler. Putting it on the
-- ProximityPrompt (Enabled = false) only hides the prompt: a client that fires
-- the remote directly walks straight through a "locked" door.
prompt.Triggered:Connect(function(plr: Player)
local cfg = CONFIG[door.Name]
if cfg.requiresKey and not hasKey(plr, cfg.requiresKey) then
notify(plr, "Locked - needs " .. cfg.requiresKey)
return
end
if door:GetAttribute("Busy") then return end -- mid-swing spam guard
door:SetAttribute("Busy", true)
local opening = not door:GetAttribute("Open")
tweenAngle(door, opening and cfg.openAngle or 0, cfg.speed)
door:SetAttribute("Open", opening)
door:SetAttribute("Busy", false)
end)Building a door system in Roblox
Doors look trivial until two players stand on opposite sides of one.
The first problem is mechanical. A door with a frame, a handle and a pane of glass is a Model, and Models have no CFrame to set — the property people reach for does not exist. Model:PivotTo moves the entire assembly around its pivot point, keeping every part where it belongs. Rotating the parts individually works for exactly one door and falls apart the moment anything is nested or the pivot is not where you assumed.
The second is authority. It is tempting to open doors on the client: it is instant, it is smooth, and it costs nothing. It also means every player sees a different door. Someone walks through what is, on their machine, an open doorway and, on yours, a solid slab — and it reads as a hacker rather than a sync bug. Doors are shared world state, so the server drives them.
The third is the lock, and it is the one people get subtly wrong. Setting ProximityPrompt.Enabled to false for a player without the keycard removes the prompt from their screen, and that feels like locking the door. It is not: the interaction can still be triggered from the client. The prompt's state is a hint to the player; the check inside the server handler is the actual lock.
Beyond that it is polish that compounds. A busy flag stops a door being spammed mid-swing into a stuck angle. Auto-close should reset its timer when reopened rather than queueing a second close. And keeping the open state as an attribute rather than a local variable means the alarm system, the AI pathing and the quest trigger can all read it without a new remote.
See more on the Luau generator, the game builder, or browse the full blog.
Frequently asked
Why can't I set CFrame on my door?+
Because it is a Model, and Models have no CFrame property. Use Model:PivotTo(cframe), which moves every descendant while preserving their relative positions. The alternative — moving each part yourself — is exactly how doors end up with the handle detached from the slab after a few rotations.
Should the door tween on the server or the client?+
On the server, for anything that matters. A client-side tween means each player sees a different door state, and a player who walks through while it is closed on someone else's screen looks like they phased through a wall. Server tweening costs a little replication and keeps the world consistent.
How do I stop players opening doors through walls?+
ProximityPrompt.RequiresLineOfSight is true by default and does exactly that — keep it on. It is a common mistake to turn it off to fix a prompt that will not appear, when the actual problem is that the prompt's parent part is inside geometry.
Where does the lock check go?+
In the server handler. Disabling the ProximityPrompt for players without a key hides the UI, but the underlying interaction can still be fired. Treat the disabled prompt as a hint and the server check as the rule.
How do I make a door open only once?+
Disconnect the handler or set an attribute the handler checks first. Do not rely on destroying the prompt alone — anything still holding a reference can trigger it.
Related Picoo prompts
Roblox inventory system
6 files · 230 lines · 56 seconds · 1 credit. 30-slot inventory + 5-slot hotbar.
How to teleport players in Roblox
Moving a character inside a place is one line and cannot fail. Sending them to another place is a network call that fails regularly and needs handling.
How to prevent exploits in Roblox
4 files · 160 lines · 44 seconds · 1 credit. Drop-in guards + a one-prompt audit of your existing scripts.