.Touched event not working correctly

I want a GUI to open up when I get inside a circle and then close when I leave it (it has a transparent cylinder inside and the script refers to it). With just .Touched and .TouchEnded the GUI was constantly flickering when I moved inside the circle so I added the distance from center condition. Now it appears with a short lag or doesn’t appear at all.

local circle = script.Parent

local function onTouch(otherPart)
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		if (character.HumanoidRootPart.Position - circle.Position).magnitude <= (circle.Size.Z/2) then
			player.PlayerGui.POS.Enabled = true
		end
	end
end
local function offTouch(otherPart)
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		if (character.HumanoidRootPart.Position - circle.Position).magnitude >= (circle.Size.Z/2) then
			player.PlayerGui.POS.Enabled = false
		end
	end
end

circle.Touched:Connect(onTouch)
circle.TouchEnded:Connect(offTouch)

You have to use a debounce.

local active = false
local circle = script.Parent

local function onTouch(otherPart)
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
                if not active then
                       active = true
		       local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		       if (character.HumanoidRootPart.Position - circle.Position).magnitude <= (circle.Size.Z/2) then
			player.PlayerGui.POS.Enabled = true
		end
	end
end
local function offTouch(otherPart)
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
                if active then
                       active = false
		       local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		       if (character.HumanoidRootPart.Position - circle.Position).magnitude >= (circle.Size.Z/2) then
			player.PlayerGui.POS.Enabled = false
		end
	end
end

circle.Touched:Connect(onTouch)
circle.TouchEnded:Connect(offTouch)

Sry 4 the trash formatting im on mobile rn

TouchEnded never worked, you need to use Magnitude or reigen3 or something else. OR just use Touched to enable and then a close button to disable it. That’s how you fix it.

1 Like
local circle = script.Parent
local toucheddb = false
local touchendeddb = false

local function onTouch(otherPart)
	if toucheddb then
		return
	end
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		toucheddb = true
		local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		player.PlayerGui.POS.Enabled = true
		task.wait(5)
		toucheddb = false
	end
end

local function offTouch(otherPart)
	if touchendeddb then
		return
	end
	local character = otherPart.Parent
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		touchendeddb = true
		local player = game.Players:GetPlayerFromCharacter(humanoid.Parent)
		player.PlayerGui.POS.Enabled = false
		task.wait(5)
		touchendeddb = false
	end
end

circle.Touched:Connect(onTouch)
circle.TouchEnded:Connect(offTouch)

What the previous post suggested but create a debounce for each function.

still doesn’t work, but thanks, I’ll try to work with just magnitude.

1 Like

This works fine, but do you think that while loop will affect the performance much?

local circle = script.Parent
local players = game:GetService("Players")

players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local playerGUI = player:WaitForChild("PlayerGui")
		local pos = playerGUI:WaitForChild("POS")
		if pos then
			while wait(0.1) do
				if (character.HumanoidRootPart.Position - circle.Position).magnitude <= (circle.Size.Z/2) then
					pos.Enabled = true
				else
					pos.Enabled = false
				end
			end
		end
	end)
end)

Use Run Service, I will code it for you so you can just copy and paste the code.

local circle = script.Parent
local players = game:GetService("Players")

players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local playerGUI = player:WaitForChild("PlayerGui")
		local pos = playerGUI:WaitForChild("POS")
		if pos then
			game:GetService("RunService").Heartbeat:Connect(function()
				if (character.HumanoidRootPart.Position - circle.Position).magnitude <= (circle.Size.Z/2) then
					pos.Enabled = true
				else
					pos.Enabled = false
				end
			end)
		end
	end)
end)
2 Likes

Just for future reference, you should be using a while loop for this situation. Heartbeat is for cases when you need it to update quickly and if you’re not connecting and disconnecting the function it has no use being here

What do you mean by “using a `White”?

(my keyboard sucks and clicked enter by accident, check edit)

While loops make game lag and it makes anything below that stop working but if you use runservice it will run depending on how good your platform is and it will continue with anything below it. That’s what I got told.

1 Like

I use a heartbeat 99% of the time over while loops, but for other reasons not related to performance. while loops and heartbeat’s have pretty much identical performance impacts, and in this specific case a while loop would be better.

Two examples below:

local UPDATE_FREQUENCY = 1/10

task.spawn(function()
    while true do
        task.wait(UPDATE_FREQUENCY)
    end
end)

vs

local UPDATE_FREQUENCY = 1/10

local lastUpdate = os.clock()
RunService.Heartbeat:Connect(function()
    if (os.clock() - lastUpdate >= UPDATE_FREQUENCY) then
        lastUpdate = os.clock()
    end
end)

These two examples accomplish the same goal, but the above does it in a much simpler way. And it’s pretty clear which one is more performant if you’re trying to run an infinite loop with no break out conditions. I only use heartbeat when I need it to disconnect at some point and not run forever. Also to solve the problem that anything below it won’t continue running, wrap it in a task.spawn()

I created my own trigger module due to touched events not working properly. It basically uses wait() when :TouchEnded() is triggered to see if a :Touched() event is rapidly fired before running the actual disconnect code.

local Trigger = {}

local Part = {} -- Part to be used in the trigger system
local TouchCount = {} -- Touch count for individual parts
local Touches = {} -- Filtered list of touching parts
local Touched = {} -- BindableEvent for Touched
local TouchEnded = {} -- BindableEvent for TouchEnded
local TouchedEvent = {} -- Exposed reference to Touched.Event
local TouchEndedEvent = {} -- Exposed reference to TouchEnded.Event
local TouchedConnection = {} -- Reference to Part.Touched:Connect()
local TouchEndedConnection = {} -- Reference to Part.TouchEnded:Connect()

local ReadOnly = {
	Part = Part,
	Touched = TouchedEvent,
	TouchEnded = TouchEndedEvent
}

function Trigger.__index(self, name)
	return ReadOnly[name] and self[ReadOnly[name]] or nil
end

function Trigger.new(part, pred)
	local touches = {} -- Filtered parts touching
	local touchCount = {} -- Number of times a part is touching
	local touched = Instance.new("BindableEvent")
	local touchEnded = Instance.new("BindableEvent")
	
	if not pred then pred = function (hit) return hit end end
	
	local self = {
		[Part] = part,
		[TouchCount] = touchCount,
		[Touches] = touches,
		[Touched] = touched,
		[TouchEnded] = touchEnded,
		[TouchedEvent] = touched.Event,
		[TouchEndedEvent] = touchEnded.Event
	}

	self[TouchedConnection] = part.Touched:Connect(function (hit)
		hit = pred(hit)

		if not hit then return end

		if not touchCount[hit] then
			touchCount[hit] = 1
		else
			touchCount[hit] += 1
			return
		end

		-- Add to parts touching
		touches[#touches+1] = hit

		-- Begin touch
		touched:Fire(hit)
	end)

	self[TouchEndedConnection] = part.TouchEnded:Connect(function (hit)
		hit = pred(hit)
		
		if not hit then return end

		-- Give time to twitch
		wait()

		-- Adjust touch count
		touchCount[hit] -= 1

		-- Sink all but last event
		if 0 < touchCount[hit] then return end

		-- Remove from touches
		local j for i = #touches, 1, -1 do
			touches[i], j = j, touches[i]
			if j == hit then break end
		end

		-- Remove touch counter
		touchCount[hit] = nil

		-- End touch
		touchEnded:Fire(hit)
	end)
	
	return setmetatable(self, Trigger)
end

function Trigger:Destroy()
	--print("Destroying Trigger")
	
	local touches = self[Touches]
	local touchCount = self[TouchCount]
	
	for n = #touches, 1, -1 do
		self[TouchEnded]:Fire(touches[n])
		touches[n], touchCount[touches[n]] = nil
	end
	
	self[TouchedConnection]:Disconnect()
	self[TouchEndedConnection]:Disconnect()
	
	self[Part], self[Touched], self[TouchEnded], self[TouchedEvent], self[TouchEndedEvent], self[TouchedConnection], self[TouchEndedConnection] = nil
end

return Trigger

And here’s an quick example:

local Trigger = require(PATH_TO_MODULE)

-- Optional: This particular function returns a player instance
-- when touching it's HumanoidRootPart
local function partFilter(hit)
	return hit == hit.Parent.PrimaryPart and game.Players:GetPlayerFromCharacter(hit.Parent)
end

local trigger = Trigger.new(part, partFilter)
trigger.Touched:Connect(function (plr) print("Hello", plr.Name) end)
trigger.TouchEnded:Connect(function (plr) print("Goodbye", plr.Name) end)

Hope this helps others looking for a solution.

2 Likes