TouchEnded Alternative

One of the coolest features of the game programming is the Touched event. Its the easiest to figure out where the character is and to add some game triggers like opening the door or entering a bonus/safe area.

While it has some uses for non-humanoid parts, most of us use it to detect player’s avatar movement and actions. The issue with Touched event is that it fires multiple times for different avatar body parts and even the same parts if they keep leaving and entering the trigger area.

One of the solutions is the use of debounce. However it is far from ideal. It does not remove the problem completely, and we still have to handle multiple events. It also introduces lag if more than one player is entering the area. But multiple events are not the real problem here.

In my opinion the biggest issue is that there is no easy way to tell when player has left the area. You may think that we just need to wait until we get no more signals. Think again - once avatar stops moving, Touched events are stopped being fired, even though character is still in the area. So we cannot really rely on that.

There is possibility to use TouchEnded event. Unfortunately it is not predictable enough. Even if we create sophisticated system to count Touch and TouchEnded events, there is no guarantee that that we get an TouchEnded signal for every Touched event and vice versa.

After some research I have developed a better system that yet has to fail me. We can still use simplicity of Touched and keep track of every player in the area, without the need to use TouchEnded. We will use magnitude to detect leaving players.

Here is a sample script using this technique. Parent this script to the trigger part and voila.

local Players = game:GetService("Players")
local part = script.Parent
--we will keep track of players in this area in this register
local Register = {}
local approxPartRadius = (part.Size.X + part.Size.Y + part.Size.Z)  / 6

local function OnTouch(hit)
    
    --optional sanity check
    if (hit.Position - part.Position).magnitude > (approxPartRadius*1.5) then return end

    local root = hit.Parent:FindFirstChild("HumanoidRootPart")
    if not root then return end --non-humanoid trigger

    local player = Players:GetPlayerFromCharacter(hit.Parent)
    if not player then return end --NPC trigger

    if Register[player] then return end --player is already registered

    Register[player] = true
    --Do some stuff here
    
    --keep checking character position, and remove from register if player leaves
    --also keep checking Parent to make sure character is still in the game
    while hit.Parent and (root.Position - part.Position).magnitude < approxPartRadius do
        wait(1) -- check every second
    end

    --this section activates on player leaving the area
    Register[player] =nil

end
part.Touched:Connect(OnTouch)

This approach creates a invisible “bubble” that player needs to leave, before he/she can trigger the part again. If we need cubic or rectangular leave area it is super easy, barley an inconvenience to modify:

local xReach = part.Size.X/2
local yReach = part.Size.Y/2 --remove if you do not care about vertical movement
local zReach = part.Size.Z/2 

And in the function:

while hit.Parent and math.Abs(root.Position.X - part.Position.X) < xReach and math.Abs(root.Position.Z - part.Position.Z) < zReach and
math.Abs(root.Position.Y - part.Position.Y) < yReach do -- Y check can be ommited

I have been using this system for a while now, and it works like a charm. If you are a paranoid, you may increase the size of reach variables to make sure you will not get multiple events on the area edges.

The beauty of this hybrid system is that you do need to check constantly player position, like in magnitude system alone, only after player has entered the area. Also, unless you check the magnitude with very short intervals (which hits the performance), this method gives you faster feedback on players triggering the part.

On the other hand, in my experience, information on leaving the area is not a priority. So slight delay is usually OK, hence checks not need to be that frequent. However your mileage may vary, and if you really need instant TouchEnded information, this solution may not be for you.

EDIT: I added optional sanity check.

12 Likes

This was already addressed here

I have checked your code. Implementing touch events on the client, without server sided checks, is just an invitation for exploiters. I know that you could make a magnitude check on the server, but it kinda defeats the purpose.

There is also a possibility that the exploiter would not report touching negative effect part at all, by disabling the script or not sending a remote event.

On the other hand, implementing your method on server, would prevent multiple players activating the part at the same time.

Created this module specifically to solve that, let me know what you think!