Touch Events and Network Delay

This article outlines some internal functionality of Touched and TouchEnded event replication in the distributed physics engine, with the goal of helping you understand how to approach touch event scenarios in your Roblox experiences.

To get the most out of this material, you should be an advanced developer who’s familiar with network replication delay.


Example Scenarios

Server/Client Async Scenario

In one example scenario, a server script has Touched and TouchEnded event listeners for a PartB, and the server owns that part. When PartB touches/un-touches another part PartA on the server (both with CanTouch set to true), an event is triggered and replicates to other clients. Note that if PartA and PartB have touched/un-touched on one client, it might not be the case on the server or another client.

The following diagram illustrates this scenario across time (t):

t = 0
Client1 PartA, owned by Client1
Server PartB, owned by Server
t = 1
Client1 PartA moves further away from PartB.
Server PartB moves closer to PartA and touches it, since the movement of PartA on Client1 has not yet broadcast to Server. A Touched event is triggered here.
t = 2
Client1 Receives the Touched event broadcast from Server at t=1, as well as the CFrame update of PartB. However, the received event appears inaccurate since PartA previously moved on this client at t=1 and does not overlap PartB.
Server CFrame of PartA is received from Client1, meaning PartA and PartB move apart and trigger a TouchEnded event.
Client2 Receives the Touched event broadcast from Server at t=1, as well as the CFrame update of PartB. On this client, the event appears accurate since Server has not yet replicated the new non-touching CFrame of PartA.
t = 3
Client1 Receives the TouchEnded event broadcast from Server at t=2, although this event appears inaccurate since PartA and PartB never touched on this client across t=0 to t=3.
Client2 Receives the TouchEnded event broadcast from Server at t=2. On this client, the event appears accurate since Server has replicated the new non-touching position of PartA (note that PartA and PartB were touching at t=2 on this client).

Client Event Dispatching

If PartA is owned by one client and PartB is owned by another client, you might get the same event twice everywhere, because each client triggers its event and broadcasts it to the server which then rebroadcasts it to the other client, as long as both parts exist on the receiving client. In our engine, we try our best to merge this “same” event, however, it’s difficult to identify and merge all of the same events.

The following diagram illustrates this scenario across time (t):

t = 0
Client1 PartA, owned by Client1
Client2 PartB, owned by Client2
t = 1
Client2 PartB moves closer to PartA and touches it, triggering a Touched event which is broadcast to Server. The CFrame change of PartB is also replicated to Server.
t = 2
Server The Touched event and the CFrame update for PartB from Client2 at t=1 is received and then rebroadcast to Client1.
t = 3
Client1 Receives the Touched event and CFrame update for PartB broadcast from Server at t=2. A second Touched event is triggered on PartA which is broadcast to Server.
t = 4
Server The Touched event for PartA from Client1 at t=3 is received and then rebroadcast to Client2.
t = 5
Client2 Receives the Touched event for PartA broadcast from Server at t=4.

Guidance

  • If the callback function needs to be executed immediately, make sure the local client owns at least one of the two parts (see Network Ownership) so the event triggers locally.

  • You can always expect to receive a Touched or TouchEnded event if it was ever triggered on remote server/clients, but anticipate a delay. This guarantees eventual consistency.

  • For security purposes, use the server to validate if a Touched or TouchEnded event is valid or not if the event was triggered on a client. For example, check the distance between the two parts to make sure they are close enough to trigger the touch event.

54 Likes

Thanks for the heads up. Also, if you are wondering how exploiters call into Touched, they often use functions such as firetouchinterest, and they should be able to forcefully call Touched callbacks on the client.

4 Likes

This is very useful! Knowing how those events work at a deeper level is very helpful

There is one (or two) things that are confusing me though

I don’t understand how Client1 can replicate the .Touched event to the server, if it doesn’t have network ownership of the part?
And, the client will still trigger .Touched events for parts that aren’t owned by it, instead of just relying on the event being replicated to it, is that right?

2 Likes

In the second diagram, Client1 has ownership over PartA.


iirc even in a LocalScript, it’ll listen to the network owner. If the client owns the part, the client will trigger Touched instantly. If the server or another client owns the part, there’s a delay since it waits for the event to be reported.

4 Likes

Good catch! It should be

A second Touched event is triggered on PartA

I’ll update the doc. Thanks!

7 Likes

Nice, altrough personally i think i’d just have Touched events that deal damage to a player that is managed by a NPC be owned by a client, even if a exploiter fires they will damage themselves.
Volume-based triggers can just use Overlap Params API on the server.

3 Likes

The second figure should probably also be updated to show the second Touched even on PartA instead of PartB

Is this the reason that .Touched fires twice? So like PartA collides with PartB from Client1’s perspective, and triggers the .Touched for PartA and PartB (and not only PartA), but then the .Touched coming from Client2 replicates and triggers .Touched for PartB again, is that right?

2 Likes

Sorry update the figure takes longer.

Yes, that’s correct.

4 Likes

Jumping inside of a Part that listens to Touched re-fires it everytime :thinking:

3 Likes

Thanks for bringing it up! This is something we are actively looking into.

3 Likes

I am still a beginner I do not understand how to put to the server the function touched for which come back as a client

1 Like

How so? any recommendations?

I’m running into issues where the Server has a .Touched and .TouchEnded on an Anchored Part (let’s call this PartA) however ,Touched and .TouchEnded would sometimes fire even when the Player.Character is still inside of the PartA and this has caused a lot of issues so I have implemented a new method to deal with this

Code
local touchUtil = {}

local function isPointInPart(point: Vector3, part: BasePart): boolean
	local relPos: Vector3 = part.CFrame:PointToObjectSpace(point)
	return math.abs(relPos.X) < part.Size.X * 0.5
		and math.abs(relPos.Y) < part.Size.Y * 0.5
		and math.abs(relPos.Z) < part.Size.Z * 0.5
end

export type TouchHandler = {
	Parts: { [BasePart]: boolean },
}

function touchUtil.create(part: BasePart): TouchHandler
	local touchHandler: TouchHandler = {
		Parts = {},
	}

	part.Touched:Connect(function(otherPart: BasePart)
		print(`{part} Touched {otherPart}`)

		print(part:GetTouchingParts())

		if touchHandler.Parts[otherPart] then
			return warn(`{otherPart} Has touched {part}`)
		end

		touchHandler.Parts[otherPart] = true

		return print(`Touch Started {otherPart}`)
	end)

	part.TouchEnded:Connect(function(otherPart: BasePart)
		print(`{part} TouchEnded {otherPart}`)

		print(part:GetTouchingParts())
		
		if not touchHandler.Parts[otherPart] then
			return warn(`{otherPart} Has not touched {part}`)
		end

		if isPointInPart(otherPart.Position, part) then
			return warn(`{otherPart} Is in {part}`)
		end

		touchHandler.Parts[otherPart] = nil

		return print("Touch Ended")
	end)

	return touchHandler
end

return touchUtil

The problem is TouchEnded fires before Player.Character has fully exited PartA so isPointInPart would return true and thus breaking my code, please observe the output especially from :GetTouchingParts()

The same code however works perfectly when ran locally as per this Thread here

I could run this code on the client but then it’d have security issues, when I run this code on the server it has inaccuracies

so from your perspective what would be the best solution to the problem? @APandaPoet

2 Likes