Distance-based action script not working: how to achieve something similar?

Hi, a while ago, I made this script:

while true do
	wait(1)
	
	if (RootPart.Position - Area.Position).Magnitude < 10 then
		Var = true
		
		DoStuff(true)
		
		Var = false
		
		wait(1)
		if Var == false then
			DoStuff(false)
		end
	end
end

It had the purpose to show a GUI when a player was inside a radius of a part, and it did the job correctly.

Recently, I found out that it doesn’t work anymore, and I wanna ask why, and how can I make it work again ?

At the moment, every second, the function fires as true, then fires as false, then as true, continuously.

Any help would be appreciated !

1 Like

CAn you explain a little bit more? What’s the expected and unexpected behaviour?

1 Like

The expected behaviour was for the function to fire as false when the player was not in the radius anymore, and for it to fire as true every second the player was in radius.

1 Like

It looks like you are setting the variable first to true and then doing something and then back to false and then checking if it is false, which of course it would be cause you just set it to false. You need an else statement in the first if block…

	if (RootPart.Position - Area.Position).Magnitude < 10 then
		
		DoStuff(true)
	else
		DoStuff(false)
	end

That solves the problem, but the function would constantly fire as false, I have multiple of these functions in my script, and it would cause some performance drawbacks.

Might need more info on what exactly your trying to accomplish.

if (RootPart.Position - Area.Position).Magnitude < 10 then
	--Player is within range of area now
	DoStuff(true) 
else
	--Player is not within range of area
	DoStuff(false)
end

Maybe a better way might be to create a “bubble” the size of the radius around the part. Weld the bubble part to the part in question and use the bubble as a detection object. When the player touches the bubble turn on the GUI using a touch event. That would eliminate the need to constantly poll whether the player was in range.

Example Implementation:

local somePart = game.Workspace.SomePart --part in question
local radius = 10 --range radius
local guiOn = false --flag to say if the GUI is on or not

--Create the Range Bubble to activate touch events
local bubble = Instance.new("Part", somePart)
bubble.Name = "RangeBubble"
bubble.Shape = "Ball"
bubble.Size = Vector3.new(radius, radius, radius)
bubble.CanCollide = false
bubble.Transparency = 0.5
bubble.Massless = true
bubble.Position = somePart.Position

--Weld the range bubble to the part in question
local weld = Instance.new("WeldConstraint", bubble)
weld.Part0 = somePart
weld.Part1 = bubble

--Function for handling the touch event of the range bubble
local function turnOnGui(touchPart)
	if(guiOn == true)then return end --Stop this from running over and over while player is touching bubble
	local character = touchPart.Parent --Assume this is the plyaer character
	local humanoid = character:FindFirstChildWhichIsA("Humanoid") --Check for a humanoid object
	if(humanoid)then

		--A player's character has touched the bubble, they are now in range
		local player = game:GetService("Players"):GetPlayerFromCharacter(character) --Get the player
		player.PlayerGui.MyGUI.Enabled = true --turn on the GUI object
		guiOn = true --mark that the gui is on now
		
		--[[
			Turning OFF the GUI now is another story.
			You need a way of determining when you will turn it off.
			This could be after so much time or when the player interacts with something else.
			or you could create a loop to poll when the player leaves the bubble using a distance.
			TouchEnded event is not going to be reliable for this.
		--]]
		
		
		
		wait(10) --wait 10 seconds
		guiOn = false --let the event run again on next touch
		player.PlayerGui.MyGUI.Enabled = false --Turn off the GUI
		
		
		--OR
		
		--[[
			while guiOn do
                                game:GetService("RunService").Stepped:wait()
				local distance = (character.HumanoidRootPart.Position - somePart.Position).Magnitude
				if(distance > 10)then
					guiOn = false
					player.PlayerGui.MyGUI.Enabled = false
				end
			end
		--]]
		
	end
end
bubble.Touched:Connect(turnOnGui) --set the listener for touch events


--Or create another bubble or wall somewhere else and when that is touched it can shut off the gui.

For one, I’d recommend syncing with physics as well as maybe speeding up the loop:

while true do
	wait(0.5) -- You may even remove this entirely. The performance cost is much much less if you use faster distance checks such as FuzzyEq.
	RunService.Stepped:Wait()
	-- Extra stuff
end

Secondly, if you want it to fire once you can use a standard debounce.

local prevValue = false
while true do
	wait(0.5) -- Wait 1/2 of a second
	RunService.Stepped:Wait() -- Synced with physics
	local currentlyClose = RootPart.Position:FuzzyEq(Area.Position, 10) -- Currently within 10 studs (this is much much faster than Magnitude because it does not use square roots which are one of the slowest, if not the slowest operations on pretty much all current CPUs)
	if currentlyClose ~= prevValue then -- If the value changed
		doStuff(currentlyClose) -- Do stuff!
	end
end

Edit: To clarify some things
Syncing with physics will improve reliability in laggy conditions. It doesn’t do much other than ensure that the player will receive the update in sync with the network as well as make sure physics is 100% up to date. (As soon as the player’s position is updated server side so is the check)

Your script immediately turns it back off after turning on (as another user mentioned above).

while true do
	wait(1)
	
	if (RootPart.Position - Area.Position).Magnitude < 10 then
		Var = true
		
		DoStuff(true)
		
		Var = false
	else
	   DoStuff(false)
	end
end

Edit: I’m not sure what you are using var for, but it seems useless in this example currently. If you aren’t using it for something else, it can be removed.

If you intended var to be a debounce so that the true function is only sent when it’s currently “closed”, then you could adjust the code like so:

(You can also simply use var instead of passing true or false, now that var will be set to whatever Boolean value the function is also passing. You also can further simplify the loop by placing wait(1) in the place of true in while true do. Wait(1) is a “truthy” value so that will not only allow the loop to continue, but also wait 1 second without the need for another line).

local var = false
while wait(1) do
	if (RootPart.Position - Area.Position).Magnitude < 10 then
        if not var then
		    Var = true
		    DoStuff(var)
        end
	elseif var then
        var = false
	    DoStuff(var)
	end
end