Melee Hitboxes - What Is The Best Solution, and is the Touched event really that bad?

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 with CanCollide on - instant nope for me, as our game’s weapons have CanCollide 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 :slightly_smiling_face:.
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!

58 Likes

Great guide, there are still a lot of people using .Touched for hitboxes when they really shouldn’t be. I agree with majority of your post, however, I’d still advise avoiding touch completely for hitboxes, it’s just not worth it. I use Raycasting for projectiles to slashes and punches, the only case I ever use magnitude is for AOE, the alternative is :GetTouchingParts()

As to address your issue

I believe I’ve heard someone give a trick where you utilize physics service to remove collision between the objects but you can still have CanCollide on or add a TouchedEvent before running :GetTouchingParts(). You should give these a try and maybe it can be a work around.

3 Likes

You should elaborate your reason as to why you think so - I don’t have much to say otherwise.

That is avoiding the issue, and is still using Touched (that’s the whole reason GetTouchingParts() even works with CanCollide off in that scenario - it has a Touched event connected). I’m unsure about the specifics of how GetTouchingParts() works and it’s network ownership / physics calculation.

Also, I’ve been using raycasting among other alternatives for half a year prior to this. I’m well aware of all the downsides and upsides of most options.

3 Likes

I’ve tested GetTouchingParts with .Touched and I can ensure you that the calculations are done on the server. If the client were to delete said part, the connected .Touched event would not run, however, :GetTouchingParts() is able to pick up any touching parts. The purpose of .Touched is to simply allow :GetTouchingParts() without collision to function.

And your post is just a very informative post.

3 Likes

I’ve confirmed that too just now, but GetTouchingParts() had many incidents where it was really unreliable - many of my friends had verified this. Either way, connecting a Touched event is still just avoiding the issue, and shouldn’t be relied upon as that behavior could be changed at any time without any notice.

2 Likes

Isn’t this true for anything else or is it specific to GetTouchingParts and Touched? Are there any release notes hinting towards this? Personally, I don’t use :GetTouchingParts() but it’s good to know it’s an option, I know a fair bit of users that utilize GetTouchingParts (The only good thing about this is that you have more control of the shape of your hitbox).

3 Likes

I’m unsure - you’d have to look it up yourself. I’ve never seen any references, only users abusing this hidden feature to get GetTouchingParts working on cancollide off parts. There’s nothing on the wiki about it, and from my years of being on devforum I haven’t seen much official talk about it either.

4 Likes

Raycasting is fine for hit detection. Raycasting from multiple points. This is how games like Mordhau and Chivalry do their hit detection.

For general collision detection that needs accurate physics(not just orientated bounding boxes), GetTouchingParts is your best bet for reliability. TouchEnded doesn’t always fire so using it to keep track of collisions is not great.

I personally wouldn’t GetTouchingParts just for hit detection for a sword or something though, since again raycasts are more suited and less costly for the task.

4 Likes

Are you referring to my solution to the problem? I’ve proposed using Touched; I’m assuming you’re talking to Mystifine.
Raycasting isn’t bad and is very useful in some situations; the issue with it would be that it scales horribly, and as stated has ping issues if you don’t handle it properly. It’s also O(n) in terms of number of parts in the workspace (even though its a very low n) - I’ve benchmarked it to confirm this previously.

3 Likes

I was just chiming in on the topic as a whole , I didn’t really read in depth about everything that was being discussed.

Ping issues seem like more of a fault in however you’re handling your netcode/a game design fault rather than raycasts problems

4 Likes

Speaking of the raycasting method, I remember a module that was made for specifically melee weapons:

Oh looks like I overlooked the dropdown details.

3 Likes

I’ve taken that module into account and used it in the theory part of the thread, showing an example of a good hitbox module that utilizes raycasting.

3 Likes

Yeah all that does is raycast from multiple points, same thing that games like mordhau do. Pretty easy way to get decent hit detection working for objects like swords

4 Likes

Here’s a decent enough result I got from the same raycast from multiple points method
(Contains blood)
https://streamable.com/mgqr8

5 Likes

I still believe that Touched is a better alternative if you’re looking for efficiency; as it’s handled by Roblox’s engine, it’s bound to be more efficient than an O(n) solution which is constantly fired up to 10 times every frame, per weapon.
Regarding the ping, yeah I didn’t mean raycasting in specific. Taking ping into account with raycasting is slightly more complex (if you’re going to be smart about it), as it requires good client-server communication. Touched already takes all of this into account internally as all collision calculations are handled on client, and I’ve reasons to believe the ping would be (even if very slightly) faster, as it’s connected towards the server internally, without any remoteevents being used as “proxies” to forward it.

2 Likes

Big issue with touched is usually for games like this (like the video I linked - a chivalry inspired game) , you really need the information that rays provide such as the actual point of impact. Touched doesn’t give you this. Not only for effects but also for cases like mine where I had to detect if the impact point of a blade’s raycast result was within my parry box, to evaluate whether or not it was a parried attack or a successful hit.

If roblox passed in the point of impact on Touched that’d be much more useful - but that’s unlikely going to happen

3 Likes

Sorry if real late reply…

So I like your idea, you’re right touched can be absolutely horrid for exploits… the way you handled it is decent.

But let’s talk about magnitude… assuming you used magnitude client based… One thing we know is client can be seen by practically anyone and that’s bad because about 70% of people who use injectors know a decent amount of lua. Magnitude in my opinion is a very basic level of understanding and with that they then can take steps of making ownership theirs starting with adjusting magnitude. Now like I said assuming magnitude is client also must mean damage is client which personally does not make sense if you want damage to well… do damage. (Correct me if im wrong in rusty and havent touched studio for about 8 months)Humanoid is a property of a players character(meaning it contains everything that your player has like body parts, hat’s items, etc.) HUMANOID is in the character. So it would be better to handle hit boxes and damage server sided prevents a lot of headaches. FE and letting the server take control of what it should, should prevent bug central.

Like I said correct me if im wrong. Even though I’m pretty sure I thought of it all… Maybe?

1 Like

I’m… unsure what your point is.

Well, if your game is fe, a client will not be able to do things like put stuff in workspace or edit it, at least not what ALL players can see. So if damage is client based a hacker can think he’s really killing players but, they are really just walking around wondering why is this weird guy just running into me. Do you get what I mean now?

If you’re talking about how Touched is bad because it’s client-based (I still don’t understand your point really) and thus has to take into account for magnitude (which it doesn’t collisions are calculated way differently), then your point would be flawed. A lot of games have to take into account for the player ping and position at the end of the day. Yes, it is spoofable, but the difference wouldn’t even be that big at the end of the day, otherwise you’d see exploits where you could kill people across the map on games like Fortnite etc. The same meaning would apply in this case.