Proximity triggered door spams opening and closing continuously

Note, by proxmity, I only mean the :DistanceFromCharacter() and not ProximityPrompt object. Now, to begin…

I am attempting to do a door that opens when a player is in the range of 15 studs. For the most part, proximity detection is worked in the client, respective to the own’s client player. Also, it only trigger if the player is in front of it.

The door is such that, it is not actually walking through, but instead where you touch the glowing part inside it (will be visually clarified later on) and get teleported to another door that is under a folder that is marked as ‘exit’.

Client Sided Local Script:

function triggerMediumProximity(medium)
	local holyEntrance = medium.OpenVariant.HolyEntrance
	
	if medium.Name == "Door_" then -- Medium is referencing the actual object (medium like media of how you enter) 
		
		local doorHinge = medium.ClosedVariant.PrimaryPart
		local doorState = doorHinge.DoorState
		
		local doorRelativeToCharacter = (humanoidRootPart.Position - holyEntrance.Position).Unit
		local holyEntranceLookVector = holyEntrance.CFrame.LookVector
		
		local dotProduct = doorRelativeToCharacter:Dot(holyEntranceLookVector)
		
		if player:DistanceFromCharacter(holyEntrance.Position) <= 15 and dotProduct >= 0 then
			proximityResponseEvent:FireServer(medium, "open")
		end
		
		if player:DistanceFromCharacter(holyEntrance.Position) > 15 or dotProduct < 0 then -- Added this so performance is good as it only calls the doors that have initially been open (they are closed by default)
			if doorState.Value == "open" then -- I only check it here and not the previous one as by default, the door is closed. To save on the time events is called, it only really needs to work when the door is already opeend
				proximityResponseEvent:FireServer(medium, "close")
			end
		end 
	end
end

Server sided script:

mediumProximityEvent.OnServerEvent:Connect(function(player, medium, arg)
	if medium.Name == "Door_" then
		local holyEntrance = medium.OpenVariant.HolyEntrance
		
		local doorHinge = medium.PrimaryPart
		local doorState = doorHinge.DoorState
		local tweenState = doorHinge.TweenState
		
		if doorState.Value == "closed" and tweenState.Value == "idle" and arg == "open" then
			
			doorState.Value = "open"

			local goal = {}
			goal.CFrame = doorHinge.CFrame * CFrame.Angles(0, math.rad(90), 0)

			local holyEntranceGoal = {}
			holyEntranceGoal.Transparency = 0

			local openAnimateTween = TweenService:Create(doorHinge, moveDoorAnimateTweenInfo, goal)
			local openAnimateGlowTween = TweenService:Create(holyEntrance, moveDoorAnimateTweenInfo, holyEntranceGoal) 

			openAnimateTween:Play()
			tweenState.Value = "playing"

			local openSound = SoundService.LevelEntranceSystemAudio.DoorOpen:Clone()
			openSound.Parent = doorHinge

			openSound:Play()

			openSound.Ended:Connect(function() openSound:Destroy() end)

			task.wait(0.08)
			openAnimateGlowTween:Play()

			openAnimateTween.Completed:Connect(function() -- This, like the next time the :Connect was fired lower down the script was to make sure that the tweenicngs do not overlap and that it can keep its orientation and only close when wanted
				tweenState.Value = "idle"
			end)
			
			
		elseif doorState.Value == "open" and tweenState.Value == "idle" and arg == "close" then
			doorState.Value = "closed"

			local goal = {}
			goal.CFrame = doorHinge.CFrame * CFrame.Angles(0, math.rad(-90), 0)

			local holyEntranceGoal = {}
			holyEntranceGoal.Transparency = 1

			local openAnimateTween = TweenService:Create(doorHinge, moveDoorAnimateTweenInfo, goal)
			local openAnimateGlowTween = TweenService:Create(holyEntrance, moveDoorAnimateTweenInfo, holyEntranceGoal) 

			openAnimateTween:Play()
			tweenState.Value = "playing"

			local closeSound = SoundService.LevelEntranceSystemAudio.DoorClose:Clone()
			closeSound.Parent = doorHinge

			closeSound:Play()
			closeSound.Ended:Connect(function() closeSound:Destroy() end) -- Deletes sound when it finishes playing as to not use too much memory

			task.wait(0.08)
			openAnimateGlowTween:Play()

			openAnimateTween.Completed:Connect(function()
				tweenState.Value = "idle"
			end)
		end
	end
end)

Note: Both code above is only JUST a snippet, you won’t be able to copy and run it yourself just like that unless you make your own repeatable thingy by adding stuff

This works completely fine in the client, and for some situations, even the server script for when there are 2 or more players. However

The Problem

When all players are out of range of the door, I want to it to close back to its original orientation (but only when all the players are out of the 15 studs). So, it does so, I tested all my own players for when I do a test run via a local server, when all of them are out of range, it is closed (as expected) - or open for when all of them are in range.

However, as soon as one player is in bounds and another player, or more others, are out of bounds, the door will do this thing where it opens and closes continusly.


Here, there are 2 players in the local server, the other copy of me is just somewhere far off out of range

The expected result is for it to keep open.

What I know, and the question

I am aware this occurs due to one of the clients firing "open" and another firing "close", this causes the event to respond accordingly and on each instance, opens and closes depending which was called in order. However, how on earth could I avoid this (in a performant way).

What I tried

I made a dictionary that stored the player calling and what they were calling (either the arg to "open" or to "close) in the global scope of the code to check when all the players are calling for it to close (meaning all of them are out of bounds). This worked, but didn't as soon as the other player (copy of myself) called another door to be open (there are multiple doors which I want this to work with). I want all of them to work accordingly. Keep it mind, whatever I will code here will copy over to the doors under the 'exit' folder due to how I looped it.

Thank you in advance, please message or comment if you want further clarification

you’re going to have to add a whole bunch of checks for every scenario. There is no way around it

add a global debounce to the door

How would I go abotu doing that?

Alright, now that I can add a little wiggle room to performance, what else could I do for such a system?

You gotta have the server check, because you might have something like this:

  1. Client says it’s within range, tells server to open door
  2. Other client says it’s out of bounds, tells server to close door
  3. Client in range detects a closed door within range, tells server to open door

Don’t let the server follow client instructions about whether the door should be open or not. Instead, just let the server know that this player is within range, and the server should decide what to do.

Now, when the server gets that player within range signal, have it check if any players are actually within range.
If there are, then start a loop that monitors the range of all players.
If there is at least one player within range, keep it open (or open it if it’s not yet open).
If there are none, break the loop and close the door.

For the client script, have the players send the within range signal if the door is not fully open, so that the server still gets a signal even while the door is closing and reopens it. There is no need to send an exit signal, because the server will be the one to decide that during the loop.

Btw, the loop doesn’t need to run every frame. You can have it run at 1 second or half second intervals if performance is something you’re worried about. Though honestly in my experience I haven’t run into a distance check performance issue, even in a zombie game with a lot of zombies run by the server.

2 Likes

Another add:
If I made this I would just have the server door check everyone’s range all the time and have that loop running always. I don’t know why you’re relying on the player to tell the server it’s within range. I’m assuming you’re using some sort of custom implementation for it, but if it was me I’d either just use the same algorithm to check the distance and direction, or I’d just use ZonePlus with a detector in front of the door if more precision is necessary. Performance is usually negligible for something like this anyway, you should only worry about that if you actually notice it in game. Just my extra two cents.

tsym baby. To fit it towards my situation, I did not really need to use a while loop. To maintain my architecture, since it gets fired all the time, I just had a list of players that has a for loop run through (and a .PlayerAdded function to add whenever a new player joins) which temporarily checks if all players are in range only at that instance for when close is called. This is AMAZING for performance, like:
image <–LOOK HOW LITTLE IS THAT PERFORMANCE USAGE

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.