Disclaimer
What is .Touched
?
How is .Touched
easily exploitable?
How do we fix this?
Let's get to it!
Precision?
Latency?
Tutorial continued
Roundup
Going advanced
Modules!
Roundup, again...
This is not a complete fix, exploiters could still teleport to the parts in which .Touched
is checked. The server can see if a player is teleporting around, so you could create an anti exploit for this already. But the caveat here, the server is blind to the .Touched
event itself and that is what I will be diving into in this tutorial!
.Touched
?.Touched
is an event used on BaseParts. Once a BasePart touches another Part, the .Touched
event will be fired for that given otherPart. It is important to remember that at least one of the parts cannot be Anchored upon collision, and that manipulating the CFrame will be ignored as well. Touch detection is physic based.
.Touched
easily exploitable?The server does not check where a Player or any part has touched something. It is the client (the players device) which informs the server when something has been touched. This means an exploiter could move the object or otherwise activate the .Touched
event on their client, and the server would have no idea that they cheated.
This does not have to be just the player character actually, they can even cause server parts to activate .Touched of other server parts even if they aren't touching on the server! That is how much trust is given to the client when it comes to touch.
Why it works this way is explained in a bonus section further down, titled Latency?
With .Magnitude
! There are more ways to check if the player is in the right place. It varies depending on your needs, but I will keep this tutorial to .Magnitude
as it is really simple to use.
local Distance = (BasePart1.Position - BasePart2.Position).Magnitude
print(Distance) -- Returns the distance between the positions in studs
Here is an example of how .Magnitude
works. You enclose 2 positions in a set of parentheses/brackets ()
. And the .Magnitude
function calculates the distance between them returning you a number in measurement of studs. It is measured from the center of the parts so the size of the parts is not accounted for.
Start with creating our variables and reference the part we want to be touched.
local BasePart = script.Parent -- This expects the script to be put inside the BasePart
local Distance = 4 -- How close they need to be
Alternatively, you could do BasePart.Size.X
or whichever of X,Y,Z
that is the longest instead of manually inputting the distance you desire. This could cause a problem if you care about precision, I explain why in the section below, otherwise feel free to skip that and continue to the code underneath.
These 2 chapters are bonus information:
Precision?
Since
.Magnitude
is measured around the center of a position. This means that if you just take the longest side of the part as the criteria. From the furthest distance it would be detecting an area that is twice the size of the part you are trying to detect touch from. This still creates a limited area for touch detection, so it should be alright.
The cube resembles the physical area you need to touch for touch detection to be registered, before we check if they are within the area resembled as a sphere. ㅤ
BasePart.Size.X / 2
would be precise if your BasePart is a sphere. As you can see the furthest distances reaches the centers on each side of the cube. But for a cube or most shapes, this may not be ideal, unless collission is off and you want them to walk into the part before it counts. ㅤBasePart.Size.X * math.sqrt(2) / 2
Could be fine, it reaches the edges, but not the corners. Depending on the size and shape of your item, if collission is on the corners may block you from reaching the area you put as your criteria. ㅤmath.sqrt(3 * BasePart.Size.X^2)
This is as precise as it gets. It reaches just out to the corners, but a little further everywhere else, which is fine. If you want something more precise Magnitude may not be the soluton for you.Latency?
Internet connection is not instant, you have to wait for the server, and the server have to wait for you to know when anything has changed. This creates an issue in that the server, or other players, they do not get your real time position in the game of your player character. Your movement will be slightly slacking behind from what the server can see, to where you actually are in the game on your own screen. ㅤ This means, when we check if the player is within the needed distance. The player may see that they are actually touching the part, or within that distance. But the server is still waiting for an update that the players location is actually there! ㅤ This is actually why the server is blind to where the player is upon
.Touched
, and the client is all that checks if the player touched something. Because then we can act upon the touch much faster than when we see the player actually touching the thing on the server. This creates a smoother and more responsive experience for the player. ㅤ Most of the time, the difference in latency is less than a second, but the game will feel much smoother the faster it responds upon the.Touched
event. The way to fix this is to allow a little more headroom for how close the player has to be! If you use the precise distance just add+ 2
, adjust as you please. We already check if they are really close, so we do not need it to be more precise. ㅤ However, if you would like for the player to be inside the BasePart to consider it properly touched, you may not need to give any headroom.
BasePart.Touched:Connect(function(hit) -- This runs when the BasePart is touched
if (BasePart.Position - hit.Position).Magnitude < Distance then
print(hit.Name .. ' is confirmed to be within reach of '.. BasePart.Name)
end
end)
Now we have confirmed that whatever hit/touched the BasePart is within appropiate distance to be reaching said BasePart. This could be either the player themselves, or other BaseParts manipulated on the client.
Let's check for the player specifically.
local Players = game:GetService('Players')
local BasePart = script.Parent -- This expects the script to be put inside the BasePart
local Distance = 4 -- How close they need to be
Same part as before, but with the addition of the player service.
BasePart.Touched:Connect(function(hit) -- This runs when the BasePart is touched
if (BasePart.Position - hit.Position).Magnitude < Distance then
local Player = Players:GetPlayerFromCharacter(hit.Parent) -- Checks if the touching part is a player
if Player then
print(Player.Name .. ' is confirmed to be within reach of ' .. BasePart.Name)
end
end
end)
Now we have confirmed that whatever touched the BasePart is both within appropiate distance to reach, as well being a player and not some other object.
if Player and (BasePart.Position - hit.Position).Magnitude < Distance then
print(Player.Name .. ' is confirmed to be within reach of ' .. BasePart.Name)
elseif Player and (BasePart.Position - hit.Position).Magnitude < Distance + 15 then
print(Player.Name .. ' is not within reach of ' .. BasePart.Name)
end
Technically you could use this as an anti-exploit, however you would have to be careful. The server will be slow at updating a player location for some players with terrible internet, so you would have to allow a lot of headroom to not incorrectly moderate players. You can always log it as further evidence to back up other instances or reports of an exploiter.
Some games that have teleporters, teleport menues or fast travel options. They add a timeout on the anti-exploit whenever the player gets teleported to allow the client and server to understand the players new location before checking if they're actually exploiting.
Thanks a lot for reading this tutorial, I hope it has given a bit of insight on how .Touched
works underneath the surface and ways to work with it.
If you are not concerned or just don't care about exploiters activating .Touched
events at incorrect times or distances, that's completely fine. In many cases it wouldn't really matter, like in a tycoon where only the owner of the tycoon can buy things, we are not concerned about it affecting other players. But lootboxes or money spawning in the map, they could easily be stolen by an exploiter if they activate by .Touched
.
If you are interested in doing this for your game and would like to make life a lot easier on you, or get a tad more advanced, keep reading!
Finding out you can do this to make your game just a bit more exploit proof, you may find yourself wanting to do this for all of your .Touched
events! And that's great!
But having to type out the whole if (BasePart.Position - hit.Position).Magnitude < Distance then
every single time gets very repetitive and tiring... so what is the solution?
Yep, we're going here! This module will be kept relatively simple, but you could make it much more advanced with metatables which allows for more customization, features and simplification in use.
First I'll show you how we're going to be using this module in our primary scripts, and how it makes life easier for us.
-- require your module, you can call it anything you want.
local betterTouch = require(game.ServerStorage.betterTouchModule)
local BasePart = script.Parent
betterTouch(BasePart):Connect(function(hit)
print(hit.Name .. ' is confirmed to be within reach of '.. BasePart.Name)
end)
--Here's the regular touched for comparison:
BasePart.Touched:Connect(function(hit)
print(hit.Name .. ' we have no idea if this .Touched is exploited or not')
end)
Let's start by creating our module.
local module = {}
return module
So this is the default text we find in a ModuleScript, which is a great start.
return function(object : Instance)
local functions = {} -- a table which contains the functions we would like to return
return functions
end
But I'm gonna completely change that around by returning a function directly.
This will allow us to do a simple function call function(BasePart)
and then connect our custom touch event.
return function(object : Instance)
local functions = {} -- a table which contains the functions we would like to return
function functions:Connect(func : (otherPart : BasePart) -> (), Distance : number)
return
end
return functions
end
Here is our custom connect function, now we just have to check for the touch.
return function(object : Instance)
local functions = {} -- a table which contains the functions we would like to return
function functions:Connect(func : (otherPart : BasePart) -> (), Distance : number)
object.Touched:Connect(function(hit) -- same function as previously
if (hit.Position - object.Position).Magnitude < (Distance or FindDistance()) then
print(Player.Name .. ' is confirmed to be within reach of ' .. BasePart.Name)
end
end)
return
end
return functions
end
Now we've added the touch function and check for the distance, same as before. But you may have seen I've added a new thing. (Distance or FindDistance())
, in case we want to set our own distance requirement, there is the option, just as before. But to make it easier on ourselves, we don't require to input any distance, we let it find the distance itself if we don't give it any.
local functions = {} -- just to show where we put FindDistance in the script
local function FindDistance() -- This function returns which side of the object is the longest
return (object.Size.X > object.Size.Y and object.Size.X > object.Size.Z) and object.Size.X or (object.Size.Y > object.Size.Z and object.Size.Y or object.Size.Z)
end
This will return the number of whichever side is the longest. If you think that's a mess, here is the if statement instead:
local function FindDistance() -- This function returns which side of the object is the longest
if object.Size.X > object.Size.Y and object.Size.X > object.Size.Z then
return object.Size.X
elseif object.Size.Y > object.Size.Z then
return object.Size.Y
else
return object.Size.Z
end
end
Alright, back to our module.
return function(object : Instance)
local functions = {} -- a table which contains the functions we would like to return
local bind = Instance.new('BindableEvent') -- Create a bind that works as our custom event
local function FindDistance() -- This function returns which side of the object is the longest
return (object.Size.X > object.Size.Y and object.Size.X > object.Size.Z) and object.Size.X or (object.Size.Y > object.Size.Z and object.Size.Y or object.Size.Z)
end
function functions:Connect(func : (otherPart : BasePart) -> (), Distance : number)
object.Touched:Connect(function(hit) -- same function as previously
if (hit.Position - object.Position).Magnitude < (Distance or FindDistance()) then
bind:Fire(hit) -- Fires our bind and sends the hit with it
end
end)
return bind.Event:Connect(func) -- Returns the connection from our bind
end
return functions
end
And this completes our module! Feel free to include what you learned from Latency? and Precision? section in the module yourself.
Going to show again how this is used.
local Players = game:GetService('Players')
-- require your module, you can call it anything you want.
local betterTouch = require(game.ServerStorage.betterTouchModule)
local BasePart = script.Parent
betterTouch(BasePart):Connect(function(hit)
local Player = Players:GetPlayerFromCharacter(hit.Parent)
if Player then
print(Player.Name .. ' is confirmed to be within reach of ' .. BasePart.Name)
end
end) -- You can add your own distance by putting it after the end like so `end,Distance)`
-- You can also :Disconnect() the connection like any other connection.
This is my first tutorial/article I've ever made, so please inform me of any errors or confusions. I would like for my tutorial to be very easy to understand and follow, so I'm likely going to come back and make some adjustments to better the learning ability in the readers!
I educate scripting to people in real life, so I'm excited to gain some experience in creating articles like this.