Hello all.
I’ve recently been deliberating in regards for the best solution for my game’s hitbox system - I had issues finding the solution for the most efficient, easy-to-setup, ping friendly, and exploit-proof system.
After talking it through with some friends and other experienced developers, I believe I’ve found a solution which fits all of the criteria met above.
I’ll firstly explain the theory behind it - after all, how can you use something properly without fully understanding the underlying concept behind it?
Theory, and how I found the answer (big read!)
I. Efficiency
What I first wanted to take into account for would be efficiency. Our studio’s game is an MMORPG, and as such efficiency is our utmost priority. I thought of some concepts, and in the end decided that these are the best methods available on Roblox currently to utilize:
GetTouchingParts
, Touched
, Raycasting
and Region3
hitboxes.
All of these methods rely on having Roblox do most of the heavy-work such as calculating what’s inside your hitbox bounds and whatnot, since Roblox’s engine is optimized specifically to allow for these tasks to run at top efficiency.
I decided upon all those methods due to the fact that other methods (such as Magnitude
checking) would either be too inefficient, or simply too inaccurate. All of those methods are one of the most precise options available (Region3
isn’t as precise, but you can use @EgoMoose’s Rotated Region3
module for precision).
So, I went through the exclusion process:
-
GetTouchingParts
only works on parts withCanCollide
on - instant nope for me, as our game’s weapons haveCanCollide
off. -
Touched
is very easily exploitable, and as such was scratched off the list (I’ll get to this later!). -
Region3
is the most expensive option on the list, and doesn’t even support rotation - then again, you could use @EgoMoose’s solution, but it would be even more expensive then.
So, we are now left with Raycasting
.
Raycasting is extremely light-weight and efficient, whilst also highly customizable. It even brings bonus information regarding precisely where it hit something!
II. Ease of use
As we were now left with Raycasting
we had to work with it here. Raycasting is probably the hardest to setup out of the options we were originally left with - since all that a raycast does is just send a line through space, and check if that line intersects with an object. What are we meant to do with this!?
Well, there’s already pretty great modules that utilize this. Let’s assume you’re using this module (you can construct your own, but you’d be responsible for everything, including optimization, calculation etc. This would also ignore the ‘Ease-of-use’ we want in our system). Let’s go to the next chapter.
III. Client Ping & Lag
Probably the most annoying one to deal with; the fact that clients see the sword hitting someone on their screen, and yet the server doesn’t. Why? Precisely due to client ping. It can take anywhere from 0 to even 5000ms for the server to receive client data, and update it all accordingly on server. So, how do you combat this?
The only solution would be to compromise - let the client tell the server when it hit something.
“But @avozzo”, you say, “that’s easily exploitable!” I know. I’ll get to this in the next chapter.
IV. Exploitability
So, since we want to let the client have as smooth of an experience as possible, we’ll need to have the client tell the server when, and where, it hit something.
This means the client would handle the hitbox and then send data of when and what it hit.
Okay. So, how do I raycast on client and send that data in the most efficient way possible to server?
Well, you don’t have to. We’re actually going to use the Touched
event!
V. The Touched Event
Most of the experienced developers probably know of the “horror” that is Touched
. It has a lot of bad reputation going around it, and how it shouldn’t be relied upon - nor even used in the first place. Why?
I won’t go into the depths of network ownership and it’s exploitability, so I’ll give a more simple-to-digest explanation:
Internally, Roblox let’s the client calculate all physics. This means that the client handles collision, movement and so on. This means that the client tells the server when it’s collided with something - the server doesn’t calculate it, the client does! Now, this is a really good approach when you don’t want your engine’s games to get real laggy with a lot of players, but this has some caveats.
When you for instance have a Touched
event for your game’s parts, the client is the one that tells your scripts “Hey, I touched this! please fire the Touched
event of this part”. This means that not only can an exploiter can tell the server if it hit something whenever they want, they can prevent from even saying it to the server in the first place. This means that an exploiter can “touch” a part, while in reality being 1000 studs away from it, or never tell the server they’re touching it, even when they’re right on top of it.
See how bad that sounds? Yeah. For this reason, I had a really bad conscience regarding Touched
for a long while. But after investigating more about Touched
recently, I’ve found out that Touched
is extremely useful when it comes to taking into account for ping! since it’s the client controlling the collision, that means this is an extremely good solution for the ping problem.
But, how do I stop the whole exploiting part? Well, since the client is controlling the collision and there can be delay, you’ll have to be more negligible towards the hitbox - but even if hackers will even bother trying to abuse this minor flaw, it’s going to barely change anything anyway. Hackers can say “Hey, my sword totally hit this guy from a mile away”, but you can just check if the touched part is less than 5 studs away from the sword / player.
Even if other clients delete the TouchTransmitter
object on their ends, it’s not them who calculate the collissions anyway - nothing to exploit if they’re not in control of it!
I’ll get into the specifics of how you can protect yourself against Touched
exploits after this theory section.
Implementation
There is no specific way to implement this, as all you’re doing is simply connecting a Touched
event to your weapon on the server. What you have to make sure is that the weapon’s network owner is the client - either by parenting the weapon into the player’s character, or using SetNetworkOwner
.
What’s especially important is that exploiters can teleport the weapon around, if it’s not attached to the client properly - my game uses a Motor6D
to rig the weapon to the client’s arms, which has a few innate flaws.
All of these flaws are connected to the fact that if the exploiter manages to un-rig / detach their weapon from their body, they could simply teleport the weapon around, directly into someone’s body to make the server think the weapon is truly there.
You can teleport the weapon around and have it replicate, because the client still has network ownership over the weapon - this means if a hacker teleports the weapon around on the client, it moves on the server too.
A simple way to exploit this would be to:
Delete the Motor6D
/ Weld
object that rigs the weapon with the player’s limbs. This allows you to teleport the weapon around, as stated above.
So, how would you protect against this?
KEEP IN MIND THIS ONLY WORKS IN SOME SPECIFIC CASES - THIS SOLUTION MAY NOT APPLY TO EVERYONE.
How I go about is detecting whether either the Motor6D
/ Weld
object gets deleted, and kick the player if so, and also check if the limb gets deleted. Make sure to check whether the Weapon
is still parented to the character when the Motor6D
/ Weld
/ limb object gets deleted!
Example code for your weapon’s Touched
server code would be:
MyWeapon.Touched:Connect(function(HitObject)
local Distance = (MyWeapon.Position - HitObject.Position).Magnitude
if Distance > 5 then
print('Hit an object that was too far away from weapon - possibly a hacker!')
return
end
-- rest of your code here.
end)
You could use better checks to find out whether it’s illegible, but that’s up to you. That is the easiest and most straightforward solution.
I believe I have covered everything that had to be addressed and everything that was important to the solution, but if you have any questions feel free to post them. I’ll try addressing them if I can .
Other than that, I’ll end this thread abruptly here.
Thank you for the read, and if you believe something needs to be elaborated better or differently, please let me know. Thanks!