PSA: Using TouchEvents Properly!

Hi, I’m @RuizuKun_Dev!

Today I’ll be addressing the reliability issue when using TouchEvents, it’s not as unreliable as you think.

Sure you can use Region3, Raycast, GetTouchingParts or Magnitude instead however those are quite tedious and complex compared to TouchEvents.

and I’ll be showing you the reason why TouchEvents are unreliable is because of your Implementation not Roblox


How it works

Normal Implementation

Touched & TouchEnded fires when touchedPart starts Touching and stops Touching detectorPart

Special case:

  • When you Equip/Unequip a Tool Touched & TouchEnded fires
  • When you change the Character size Touched & TouchEnded fires

These are probably why TouchEvents to seems to be unreliable


The Problem

  • Detecting the entire Player.Character with no Touch Filters
  • Not checking if the touchedPart is still inside the detectorPart
  • Not knowledgeable of how TouchEvent works (due to lack of Documentation)

How to use it Properly

My Implementation

You need to be Detecting Touch on ONLY one Part at a time for Player.Character, which is preferably the HumanoidRootPart

and to prevent those special cases from causing trouble we need to check if the touchedPart is still inside that detectorPart

you can also Connect and Disconnect the TouchEvents for efficiency


Source Code

Code
--| SERVICES:
local plr = game.Players.LocalPlayer
	local Cha = plr.Character
		local Hum = Cha.Humanoid
			local RootPart = Hum.RootPart
			
--| VARIABLES:
			
local Red = Color3.new(1,0,0)
local Green = Color3.new(0,1,0)

local USE_NORMAL_IMPLEMENTATION = false

local detectorPart = workspace.Part

--| FUNCTIONS:

local function isPointInPart_Fn(Point, Part)
    local relPos = Part.CFrame:PointToObjectSpace(Point)
    return math.abs(relPos.X) < Part.Size.X*.5 and math.abs(relPos.Y) < Part.Size.Y*.5 and math.abs(relPos.Z) < Part.Size.Z*.5
end

-- Implementations

--|> Normal Implementation

local function NormalImplementation()
	detectorPart.Touched:Connect(function(touchedPart)
		if touchedPart == RootPart then
			print(".Touched")
			RootPart.Color = Green
		end
	end)
	
	detectorPart.TouchEnded:Connect(function(touchedPart)
		if touchedPart == RootPart then
			print(".TouchEnded")
			RootPart.Color = Red
		end
	end)
end

--|> My Implementation
local function MyImplementation()

	local IsInPart
	
	local touched_Fn, touchEnded_Fn
	local touched_Ev, touchEnded_Ev = detectorPart.Touched, detectorPart.TouchEnded
	local touched_Connection, touchEnded_Connection	
	
	
	-- touched
	function touched_Fn(touchedPart)
		if (not IsInPart) and touchedPart == RootPart then
			IsInPart = true
			touched_Connection:Disconnect()
			touchEnded_Connection = touchEnded_Ev:Connect(touchEnded_Fn)
			print(".Touched")
			RootPart.Color = Green
		end
	end
	
	-- touchEnded
	function touchEnded_Fn(touchedPart)
		if IsInPart and touchedPart == RootPart then
			if (not isPointInPart_Fn(RootPart.Position,detectorPart)) then
				IsInPart = nil
				touchEnded_Connection:Disconnect()
				touched_Connection = touched_Ev:Connect(touched_Fn)
				print(".TouchEnded")
				RootPart.Color = Red
			end
		end
	end
	
	touched_Connection = touched_Ev:Connect(touched_Fn)

end

--| SCRIPTS:

if USE_NORMAL_IMPLEMENTATION then
	NormalImplementation()
else
	MyImplementation()
end


for _,v in ipairs(Cha:GetDescendants()) do
	if v:IsA("BasePart") then
		v.Transparency = .9
	end
end

Cha.DescendantAdded:Connect(function(addedDescendant)
	if addedDescendant:IsA("BasePart") then
		addedDescendant.Transparency = .9
	end	
end)

RootPart.Transparency = 0
Cha.Head.face:Destroy()

https://pastebin.com/nqjMnDuW


Resouces

  • Uncopylocked:

API References



35 Likes

This is a clever solution which does solve some of the issues originally posed by touched/untouched events:

  • It’s accurate! No need to worry about untouched firing before untouched now, and modifications of the character within a zone won’t cause any issues as you mentioned.

  • It’s relatively simple to setup.

There are still limitations which may dissuade its use:

  • You’re solely limited to the client. This method can’t be implemented on the server, therefore server-side implementations of features such as safe zones are impossible to achieve (without networking, which adds additional complexity).

  • Other players will still be triggering this event frequently, which is a huge issue as explained in this discussion.

  • It’s not as easy to setup with complex geometries. You’ll either be forced to convert zones into meshes/unions, or setup additional touched events which become incrementally inefficient.

10 Likes

The issue with TouchEvents is that they are handled by the client - which means it’s very easy for exploiters to either:

  • A) Teleport the brick to themselves
  • B) Teleport themselves to the brick
  • C) Fire the touch event directly without either of those

I don’t use TouchEvents due to easy exploitation, and I’d much rather use my own implementations than TouchEvents.

@ForeverHD

That’s not true, if the Part only exist on the Client and not on the Server it won’t trigger when other players Touch the Part.

correct me if I am wrong though

I’d recommend using Zone+ for something that requires Precision.

You are right about implementing this ServerSide, it is difficult to do when there is Latency however you could always do Sanity Checks to verify the input.


@avozzo

Sanity checks

Okay, imagine this scenario:
You use TouchEvents for swords, and everytime a humanoid limb touches it the target get’s damaged.
Now, what prevents the target from just deleting the touch interest? You can’t detect that, as it’s client sided - the connection would still be there on the server.

Touched events on the client function identically to those on the server, therefore all valid parts, including character parts will still trigger them. That’s why it’s always important to check hit.Parent.Name == localPlayer.Name when using touched events on the client (as you’ve done).

Here’s your touched method with two clients:

1 Like

I get your point, @avozzo and I’d like to thank you for mentioning it

However I wouldn’t be using TouchEvents for that anyways, I use FastCast and RaycastHitbox.

I’m not saying it’s superior … I created this Article to address the many concerns regrading reliability issues people have with TouchEvents.


@ForeverHD

That part is in workspace and it exists on the Server, I said if the part is Client Sided (a Local Part) only then it won’t detect when other players touch it.

This has nothing to do with the TouchEvents being Server or Client sided, I’m talking about the Part being Server or Client sided

I didn’t demonstrate this mechanic in my Resource because it wasn’t necessary, however I’d like to thank you for bringing this up.


TouchEvents definitely has flaws like any other method, henceforth I suggest that we use the appropriate methods to achieve what we want.

That’s correct if you create a part locally (e.g. cloning the server part then destroying it on the client), however the examples provided do not appear to do this. The touched events in the video above were handled on the client in local scripts.

The value of code that only runs when it should is something that should never be underestimated. That’s why I absolutely love this thread.

You mentioned GetTouchingParts (as well as some other notable alternatives) as if they were a competitor to the algorithm you’re advocating for. I however believe the opposite. Proper use of GetTouchingParts can actually complement this solution. After all, the only time GetTouchingParts would theoretically be used in this algorithm is to perform a check to ensure that the ‘touchedPart’ is still present within the ‘detectorPart’. Considering that this only actually occurs while a connection is already active, there would be no need to make significant amendments for the support of noncollidables. In short there would be no greater sacrifice than would already be present in the very calculation of those complex geometries in the first place. Even after the implementation of such heavy calculations, I’d still argue that in the majority of similar cases, this algorithm would easily outperform any nonevent-based alternative.

As Roblox works toward the security of TouchEvents in the future, I hope developers will learn to admire their elegance. For fully client-based applications such as the one exampled by your music system, I believe this to be a fairly unrivaled solution.

I agree with what you said but using GetTouchingParts introduces time completely, if there are many parts inside the Detector part it becomes inefficient.

Using CFrame to check is more practical since you only need to know about one part not every other part.