Check if players are within a set distance of multiple doors

What I am trying to create is housing system, with a number of houses, and there’s and outside and inside door to each house (enter the outside door, it will teleport you to the inside of the house, and vice versa)

RenderStepped = RunService.RenderStepped:Connect(function()
	local LastInput = UserInputService:GetLastInputType()
	local InsideDoors = {}
	local OutsideDoors = {}
	
    for _, v in pairs(Plots:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            InsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
        end
    end

	for _, v in pairs(Doors:GetDescendants()) do
		if v.Name == 'Door' and v:IsA('BasePart') then
			OutsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
		end
	end

    if InsideDistance <= 5 or OutsideDistance <= 5 then
         print('Player is near a door')
    end
end)

The problem atm is that it only works for 1 set of doors (so if theres 2 doors in the game, only one door will print ‘Player is near a door’)

2 Likes

I recommend looking through this tutorial. I know it is NPCs, but you could take a basic idea of how it is done. I am from my phone right now, so I do apologise if my response is not the best, but here you go: https://youtu.be/QYXCqa5knRk . It also prints only once due to the fact that it is asking if the person is near Inside Door or if user is near Outside Door, which only requests one of the two and prints it on once. The tutorial above is very useful as it looks through multiple objects.

1 Like

I’ve already got that stuff sorted. I need it so it can work for a dozen models, instead of a single model

In the video, it looks through all the NPCs which are located in the folder instead of just one and if one is near, then it reads it but does not stop there, so you could do something similar for the doors. I made a door system before and I used a similar code like that one and it worked as expected. You could also add another loop which searches between different folders, but I do not recommend that.

1 Like

Problem is tho I have two seperate folders containing the doors. It would be too much of a hassle to have all the doors stored under one folder

There is a reason for them being seperate, but Im not gonna go into detail

Tried this, still only worked on one door

for _, v in pairs(Plots:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            InsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
			InsideDoor = v.Parent
			for _, v in pairs(Doors:GetDescendants()) do
				if v.Name == 'Door' and v:IsA('BasePart') then
					OutsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
					OutsideDoor = v.Parent

Once I come back home, the first thing that I will do is work on that. I am guessing that you will need to separate scripts for each door due to the fact that they are located in different folders/models. If possible, could you send an example of where all doors are in the Game Explorer?

1 Like

Check if each door/plot meets the requirements when looping through them, and if it does check if the current closest door is either nil or further away.

RenderStepped = RunService.RenderStepped:Connect(function()
    local LastInput = UserInputService:GetLastInputType()
    local closestDoor = nil

    for _, v in pairs(Plots:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            InsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            -- check if the door is closer than the current chosen door
            if (not closestDoor or InsideDistance <= closestDoor[1]) then
                closestDoor = {InsideDistance, v}
            end
        end
    end

    for _, v in pairs(Doors:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            OutsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            if (not closestDoor or OutsideDistance <= closestDoor[1]) then
                closestDoor = {OutsideDistance, v}
            end
        end
    end

    -- there is now a table with {distance, model} or it is nil if there is no door near.
    if closestDoor then
        local dist = closestDoor[1]
        local door = closestDoor[2]
        print("The closest door is " .. tostring(dist) .. " studs away!")
        if dist <= 5 then
            -- do anything with the close door
        end
    end
end)

This will leave you with the variable closestDoor, which contains the distance to the door and the door!

Written on mobile, sorry if it doesn’t work lol!

You could expand this to check if a door is open, or you could make it more readable by calling a function for each folder!

You could also simplify the loops somehow so they are looping through less objects (right now they are checking every descendant of models Doors and Plots, which is probably a lot more items than necessary). However, this may not be possible, considering how you may organizing the folders.

2 Likes

You could just add an invisible and non-collidable part to detect when something touches it and check if the part is a child of a character and if the character is already inside the detector. That would avoid having to iterate over all doors on each frame.

I really do not recommend this method as you have to create a touch part for each door. If you are working with a lot of doors, or even user placed doors, then you would have to add that part to every single door, or make a script that clones the part there. It is really not recommended to do something like that.

1 Like

Your data structure seems really complex imo… are there ways you can simplify it? For example, can you make a list of all the doors in existence, and then just reference that whenever you’re looking for the nearest door? Giving all the doors a tag and using CollectionService would also make the logic extremely simple.

1 Like

Trying to have it when player presses a key it will teleport them, however running into trouble here

if OutsideDistance <= 5 then
			for _, v in pairs(Plots:GetDescendants()) do
			    if v.Name == Player.Name then
			        Character:SetPrimaryPartCFrame(v.Extras.InsideDoor.Teleport.CFrame)
					Side = 'Indoors'
			    end
		    end
		elseif InsideDistance <= 5 then
			print(1)
		    for _, v in pairs(Doors:GetDescendants()) do
				if v.Name == Player.Name then
					Character:SetPrimaryPartCFrame(v.Teleport.CFrame)
					Side = 'Outside'
				end
		    end
		end

No errors, it just always teleports me inside. Never prints 1, so that means OutsideDistance is always <= 5. How can I go about fixing this so it will teleport me inside if im close to the outside door and teleport me outside if im close to the inside door

Why are you looping through each door/plot model again? Is this after the previous code?

In the example I left the model in closestDoor[2].

Unless you’re trying to teleport the player to the door with their name no matter which door they are using?

Could you just elaborate a little more, I’m slightly confused! Thanks

RenderStepped = RunService.RenderStepped:Connect(function()
	local LastInput = UserInputService:GetLastInputType()
    local ClosestDoor = nil

    for _, v in pairs(Plots:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            InsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            if not ClosestDoor or InsideDistance <= ClosestDoor[1] then
                ClosestDoor = {InsideDistance, v}
            end
        end
    end

    for _, v in pairs(Doors:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            OutsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            if not ClosestDoor or OutsideDistance <= ClosestDoor[1] then
                ClosestDoor = {OutsideDistance, v}
            end
        end
    end

    if not ClosestDoor then return end
        
	local Distance = ClosestDoor[1]
    Door = ClosestDoor[2]

    if Distance <= 5 then
		local EditMenu = PlayerGui.HUD:FindFirstChild('EditMenu')
		if EditMenu.Visible then return end
					
		local EnterClone = PlayerGui:FindFirstChild('Enter')
		if EnterClone then return end
				
		local EnterClone = Enter:Clone()
		if not EnterClone then return end
				
		if LastInput == Enum.UserInputType.Gamepad1 then
			EnterClone.Control.PC.Visible = false
			EnterClone.Control.Controller.Visible = true
			EnterClone.Control.ImageTransparency = 1
				
			EnterClone.Control.Controller.ImageRectOffset = ButtonImages[Settings.ControllerKey][1]
		else
			EnterClone.Control.PC.Visible = true
			EnterClone.Control.Controller.Visible = false
				
			EnterClone.Control.PC.Text = Settings.PCKey
		end
				
		if InsideDistance <= 5 then
			EnterClone.Control.Label.Text = 'to Leave!'
		else
			EnterClone.Control.Label.Text = 'to Enter!'
		end
				    	
		EnterClone.Parent = PlayerGui
				
		Tween(EnterClone.Control, UDim2.new(0.45, 0, 0.75, 0), 0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.In):Play()
	else
		local EnterClone = PlayerGui:FindFirstChild('Enter')
		if not EnterClone then return end
				
		Tween(EnterClone.Control, UDim2.new(0.45, 0, 0.775, 0), 0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out):Play()
		wait(0.25)
		EnterClone:Destroy()
	end
end)
    
UserInputService.InputBegan:Connect(function(input)
	local CurrentTime = tick()
	
    if CurrentTime - LastTeleportTime < Settings.Timeout then
    	return
    end

    if input.KeyCode == Enum.KeyCode[Settings.ControllerKey] or input.KeyCode == Enum.KeyCode[Settings.PCKey] then
    	local EnterClone = PlayerGui:FindFirstChild('Enter')
    	if not EnterClone then return end

		local UIClone = Transition:Clone()
		UIClone.Parent = PlayerGui
		
		Tween(UIClone.Left, UDim2.new(0, 0, 0, 0), 0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.In):Play()
		Tween(UIClone.Right, UDim2.new(0.5, 0, 0, 0), 0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.In):Play()
			
		HumanoidRootPart.Anchored = true
				
		wait(0.5)
		
		if OutsideDistance <= 5 then
			for _, v in pairs(Plots:GetDescendants()) do
			    if v.Name == Player.Name then
			        Character:SetPrimaryPartCFrame(v.Extras.InsideDoor.Teleport.CFrame)
					Side = 'Indoors'
			    end
		    end
		elseif InsideDistance <= 5 then
			print(1)
		    for _, v in pairs(Doors:GetDescendants()) do
				if v.Name == Player.Name then
					Character:SetPrimaryPartCFrame(v.Teleport.CFrame)
					Side = 'Outside'
				end
		    end
		end
		
		LastTeleportTime = CurrentTime
		
		wait(0.5)
						
		Tween(UIClone.Left, UDim2.new(-0.5, 0, 0, 0), 0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out):Play()
		Tween(UIClone.Right, UDim2.new(1, 0, 0, 0), 0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out):Play()
		
		local HUD = PlayerGui:FindFirstChild('HUD')
		if not HUD then return end
		
		local HouseMenu = HUD:FindFirstChild('HouseMenu')
		if not HouseMenu then return end
		
		local LevelMenu = HUD:FindFirstChild('LevelMenu')
		if not LevelMenu then return end
		
		if Side == 'Indoors' then
			HouseMenu.Visible = true
			LevelMenu.Visible = false
		else
			HouseMenu.Visible = false
			LevelMenu.Visible = true
		end
		
		HumanoidRootPart.Anchored = false
			
		wait(0.5)
		
		UIClone:Destroy()
	end
end)

Basically, when they press the button it should teleport them from one door to another door. The outside door model is named after the player, meanwhile the inside door is inside of a plot model named after the player.

When a player is near the outside door, it should teleport them to the inside door.

Like
If player is near NinjoOnline Outside Door then
Teleport player to NinjoOnline Inside Door

To do that, I’d have to check if they closer to an inside or outside door. Then loop thru the corresponding locations of the doors.

So if near NinjoOnline’s Outside Door (which the model would be named NinjoOnline) then search thru the plots folder and find the plot called NinjoOnline.

I think it would be easier if you structured the door folders somehow differently.
I can’t think of an easy way for the solution without a proper understand of how everything is laid out;

Have you tried putting ObjectValue’s inside the Outside/Inside doors which point to each each other? Perhaps you could also put a StringValue within the door which states the owner/player’s name.

This way, you could do something like:

local otherDoor = closestDoor[2]:FindFirstChild("OtherDoor") -- object value within door
local playerName = closestDoor[2]:FindFirstChild("PlayerName") -- string value within door

if otherDoor and playerName and playerName.Value == Player.Name then
    Character:SetPrimaryPartCFrame(otherDoor.Teleport.CFrame)
end

which would handle every door in a much simpler fashion! Let me know what you think of this kind of idea.

On a seperate note, you seem to be cloning the GUI object “EnterClone” on every frame in the loop when the Distance <= 5. Not sure if this is what you’re intending to do lol!

Is there a way to just get which door is closer?

for _, v in pairs(Plots:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            InsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            if not ClosestDoor or InsideDistance <= ClosestDoor[1] then
                ClosestDoor = {InsideDistance, v}
            end
        end
    end

    for _, v in pairs(Doors:GetDescendants()) do
        if v.Name == 'Door' and v:IsA('BasePart') then
            OutsideDistance = (v.CFrame.p - HumanoidRootPart.CFrame.p).magnitude
            if not ClosestDoor or OutsideDistance <= ClosestDoor[1] then
                ClosestDoor = {OutsideDistance, v}
            end
        end
    end

If that gets the closest door, then using OutsideDistance and InsideDistance I should be able to get the door. However, what I mentioned earlier was doing this

if OutsideDistance <= 5 then
			for _, v in pairs(Plots:GetDescendants()) do
			    if v.Name == Player.Name then
			        Character:SetPrimaryPartCFrame(v.Extras.InsideDoor.Teleport.CFrame)
					Side = 'Indoors'
			    end
		    end
		elseif InsideDistance <= 5 then
			print(1)
		    for _, v in pairs(Doors:GetDescendants()) do
				if v.Name == Player.Name then
					Character:SetPrimaryPartCFrame(v.Teleport.CFrame)
					Side = 'Outside'
				end
		    end
		end

Always returned as the OutsideDistance. Even if I was closer to the inside door

This is probably because in the example I wrote originally, InsideDistance and OutsideDistance are being set on every loop as global variables. This means that both could be < 5 later in script, after they are being changed.

Your if statement would therefore return true on the first “if OutsideDistance <= 5 then” even if the inside door is closer, but both are < 5.

The closer door would be closestDoor[2].

Still much better than iterating and checking the magnitude for each. Roblox already has a physics engine that handles collisions for you and with much better performance than any per-frame loop you could ever make. Also, why would he have to make one for each door if he could just make the templates have a “Detector” part in them?