How to make a one way door?

I want to make a door that you can’t collide one way but collide the other way. I just want a door where you can walk in from one side and not the other.

I don’t really have any idea on how to make this but can you reply with some suggestions for me.

You could place the door, and then just before it on one side, place a transparent detector so that a script can pick up the touched event and then change the properties of the door.
The transparent detector, of course, would be a part.

You could have the door be changed to CanCollide = true via a LocalScript that uses a .Touched event, so that the change does not replicate for all players.

Three things led me here.

  • Free time browsing the forum.
  • The fact that I love to help others on the forum as best I can.
  • A new challenge. I’ve never done this before but I’ve definitely seen my fair share of one-way doors and maybe I might need one in the future, so it’s a good problem to tackle.

Initially I, like some of the above responses, thought of just replying that you could use a Touched event. That doesn’t seem like it’d be accurate though and it might be difficult to determine which way is the non-colliding side… so that called for a new solution!

I just want to preference that I hate CFrames and know nothing about working with them or vectors beyond very basic stuff, but I didn’t want to leave this problem alone so I decided to dig a little deeper and teach myself a few things about CFrames (they also help for other use cases I’ve had!). With that I set to work and produced an interesting solution. It is client-sided and may not be the best since I literally just learned how to do some of this stuff today but I hope it’ll help you out.

So my idea here was to collect all the doors that should be one-way only and toggle the collision of the door based on the player’s direction in relation to the door. Players would be able to enter through the front (in) face of the door but would not be able to enter through the back (out) face.

It took me about a few hours to come up with it (admittedly a lot of it was figuring the order of CFrames for ToObjectSpace) but I got something working. There are some weird cases to be wary of though if you use my exact solution, namely that it takes one way door literally. If you’re on the side of the door where collisions should be enabled, you can also walk in through the sides of the door.

Here’s a video demonstrating what I came up with:

Here's the code if you're interested. It's got typechecking mostly set up as well as comments throughout the code explaining my process.
--!strict

--- Toggle collisions depending on the direction of the character from a one-way door
-- @author colbert2677

local CollectionService: CollectionService = game:GetService("CollectionService")
local ReplicatedStorage: ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players: Players = game:GetService("Players")
local RunService: RunService = game:GetService("RunService")

local LocalPlayer: Player = Players.LocalPlayer :: Player
-- We need this so we can calculate the player's position near a door.
local HumanoidRootPart: BasePart

local DOOR_TAG: string = "OWD"
-- How close doors should be to compute collisions. Do note that this is
-- only for a simple magnitude check based on the center of the part; use
-- math.huge instead or raycasting if you need big one-way doors too.
local CONSIDER_DISTANCE: number = 30

local function setCollisionOnDoors()
	-- Do not run this code if a HumanoidRootPart is not defined
	if not HumanoidRootPart then return end
	
	for _: number, door: Instance in ipairs(CollectionService:GetTagged(DOOR_TAG)) do
		-- Make sure we're working with BaseParts
		if door:IsA("BasePart") then
			local doorCFrame = (door :: BasePart).CFrame
			
			-- Skip computation for doors that are far from the player
			-- Comment this out or use math.huge for CONSIDER_DISTANCE to account for larger doors
			local doorDistance: number = LocalPlayer:DistanceFromCharacter(doorCFrame.Position)
			if doorDistance >= CONSIDER_DISTANCE or doorDistance < 1 then continue end
			
			-- Translate the CFrame of the door into object space of the root part. If you do this
			-- the other way around then a character can spin around to change the sign of the Z value
			-- and walk backwards. Instead of one-way doors, it'd be one-direction doors!
			local relativeCFrame: CFrame = door.CFrame:ToObjectSpace(HumanoidRootPart.CFrame)
			
			-- Set the collision based on the sign of the Z-axis of the relative CFrame
			-- We take half of the Z-size of the door so that this works with doors of any width
			door.CanCollide = relativeCFrame.Z > (door.Size.Z/2)
		end
	end
end

local function onCharacterAdded(character: Model)
	HumanoidRootPart = (character.PrimaryPart or character:WaitForChild("HumanoidRootPart")) :: BasePart
end

LocalPlayer.CharacterAdded:Connect(onCharacterAdded)
-- We need to use Stepped so collisions update before physics simulation occurs
RunService.Stepped:Connect(setCollisionOnDoors)

if LocalPlayer.Character then
	onCharacterAdded(LocalPlayer.Character :: Model)
end

Place file if you’d like to try it out in Studio:

OneWayDoorsTest.rbxl (34.4 KB)

I am normally against uploading tech demos to my profile since I try to keep it as clean as possible and archive my experiences en masse when they aren’t being used so I do not have an environment where you can test this live, sorry. I can see myself eventually taking the place down if I put it up, so feel free to reupload it and try it out yourself. Should work the same as in Studio.

Have fun!

14 Likes

:ok_hand:

-- <in local script>

local frontOfDoorPosition = door.CFrame * Vector3.new(0, 0, -1)
local behindDoorPosition = door.CFrame * Vector3.new(0, 0, 1)

local isCloserToCollidableSide = (hrp.Position - frontOfDoorPosition).magnitude > (hrp.Position - behindDoorPosition).magnitude

door.CanCollide = isCloserToCollidableSide