When I began scripting my combat game project one of the first decisions I had to make was how to handle the hitboxes.
I went for server side hitboxes using parts and spatial queries (workspace:GetPartsInPart()) each Heartbeat). I did not want to use 3rd party solutions and this seemed like a reliable way to detect hits. I have “preset” hitboxes for each move and I clone the hitbox part in and make it follow the player by setting it’s CFrame each Heartbeat.
This is roughly what it looks like:
-- Creating part for the hitbox
local hitbox_part = m1_info["hitbox_part"]:Clone() -- Cloning the preset m1 part
hitbox_part.Parent = debris_folder
hitbox_part.CFrame = root.CFrame * m1_info["hitbox_offset"] -- Setting initial posiiton
-- Creating params for the hitbox
local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {stand_user.user}
-- Creating hitbox object
local hitbox = Hitbox.new(hitbox_part, params)
-- Connecting an event named hit (name only needed for :Disconnect()) that calls the function when :GetPartsInPart() returned something
hitbox:Connect("hit", function(part)
-- ...
end)
-- This function makes hitbox_part follow the root at an offset of m1_info["hitbox_offset"]
UniversalUtils.followPart(hitbox_part, root, m1_info["hitbox_offset"])
-- Add part to debris
Debris:AddItem(hitbox_part, m1_info["hitbox_life"])
-- Start the hitbox
hitbox:Start()
(The hitbox class automatically destroys itself when the hitbox_part is destroyed)
Recently I saw that Roblox added ShapeCasts so my question is would changing my current hitbox solution to ShapeCasts be worth it? I’m thinking the results would be negligible but I thought it’s better to ask anyway.
Actually, you using Shapecasts instead of a part with GetPartsInPart will provide higher performance however at the cost of possibly changed behavior (However still depends and there may not be any sort of game changing or obvious side effects or even any negative effects of this). Personally i think you should more instead use GetPartBoundsInBox(). It should also provide higher performance while not changing how your hitboxes work overall. (However with GetPartBoundsInBox you would need to scrap the entire part instance along with its .Touched event. May be inconvenient to do this but again, should potentially provide better performance overall)
Personally, unless you could benefit from what shapecasts have to offer, stick with what you got.
From a strictly execution time standpoint, shapecasts are slower than workspace:GetPartsInPart(), although they can be more readily performed in parallel since they don’t require adjusting part properties to be performed. This code demonstrates the performance difference:
local QUERIES = 1000
local HEIGHT = 20
local RADIUS = 1
local QUERY_PART = Instance.new("Part")
QUERY_PART.Size = Vector3.new(RADIUS * 2, HEIGHT, RADIUS * 2)
local ORIGIN = CFrame.new(Vector3.yAxis * (HEIGHT / 2 - RADIUS))
local SIZE = Vector3.one * (2 * RADIUS)
local DIRECTION = Vector3.yAxis * -(HEIGHT - 2 * RADIUS)
local start = os.clock()
for _ = 1, QUERIES do
workspace:GetPartsInPart(QUERY_PART)
end
local stop = os.clock()
--Takes 5.40ms on my machine.
print("Performed " .. QUERIES .. " workspace:GetPartsInPart() spatial queries in " .. (stop - start) * 1000 .. "ms.")
start = os.clock()
for _ = 1, QUERIES do
workspace:Blockcast(ORIGIN, SIZE, DIRECTION)
end
stop = os.clock()
--Takes 8.19ms on my machine.
print("Performed " .. QUERIES .. " workspace:Blockcast() spatial queries in " .. (stop - start) * 1000 .. "ms.")
What’s important to bear in mind is that, ultimately, shapecasts and more general spatial querying methods are designed to solve different problems.
The spatial query methods can tell you everything in a given region, but not how it intersects with that region. Shapecasts can tell you exactly where they intersect with other geometry, but you will need to perform additional queries to find everything in a given span.
Shapecasts have the additional problem in that they will not intersect with objects they are already intersecting when they begin. If you don’t need to know information about the exact intersection between your query region and the objects it is intersecting with, it isn’t worth using methods designed to calculate it.
Are they? Personally i still see GetPartsInPart to be more slower. Not because of the raw function but more because a Part is also created (better say cloned) just before GetPartsInPart is ran while Shapecasts never have to create such additional instances before theire being ran.
Shapecasts have the additional problem in that they will not intersect with objects they are already intersecting when they begin. If you don’t need to know information about the exact intersection between your query region and the objects it is intersecting with, it isn’t worth using methods designed to calculate it.
Oh so they have the same behaviour as regular raycasts? I didn’t use shapecasts so I wouldn’t know.
In the picture below the raycast will not detect anything because it starts in the black part:
(Green is start, red is end, orange is the raycast)
So if I had a shapecast start out like this:
It wouldn’t detect the black part? Or am I misunderstanding?
@mayoozzzz Yes, they have the same behavior as raycasts in that way, and your understanding is correct.
Looking further into what @XenoDenissboss1 brought up, they are correct in that if you are creating, moving, and destroying a part every query, shapecasts are superior. Since you’re recycling the part, you only have to pay the creation cost once. Digging in a bit deeper, setting the part’s CFrame every operation still tends to be faster than shapecasts, but if you need to adjust its Size and CFrame every operation, shapecasts win out.
Personally i think you should more instead use GetPartBoundsInBox()
Do you think it’s worth it scraping the hitbox parts in favour of :GetPartBoundsInBox and GetPartBoundsInRadius? Would the performance impact be worth the effort? Also I’m not using .Touched at all as it’s unreliable, just GetPartsInPart.
Thank you for the response though! From what I’ve gathered from your response and @MysteriousVagabond’s response I think I’ll stick with GetPartsInPart() or change it up to the other spatial queries functions.
100% Would be worth the effort, if you arent even using .Touched then adapting your code to GetPartBoundsInBox or GetPartBoundsInRadius should be quite easy to do, along with providing higher performance overall.
Thats kinda something else entirely to be honest. He was only asking for the performance between different queries to use as hitboxes in whatever setup he already has. Then again doing hitboxes on the client would just lead to beyond poor game security and would allow any exploiter to change the contents of the hitbox table to whatever they wished for.
I mean… I’m not entirely sure what youre trying to get at here. Yes doing hitbox checking on the server from every player would 100% be more demanding on the server than if every client was checking for its own individual hitboxes sure. But again, doing this sort of thing on the client would lead to poor game security and again, it would allow any exploiter to just set their hitboxes to reach players way outside any game intended range.
I chose not to do hitboxes on the client to make the game less exploitable. It’s easier to detect or even just record (by a player) an exploiter that teleports around to hit you than it is to detect someone using hitbox expanders.
So far from my gameplay tests with random people the hitboxes are very consistent. I am using very basic form of movement prediction with hrp.AssemblyLinearVelocity to combat the client-server lag and it seems to work well. I don’t know how it will look on high ping but high ping players shouldn’t be favoured over low ping in my opinion.
I am doing EVERYTHING visual related on the client so it should be okay. Every big game in this genre (JoJo games) seems to be doing server side hitboxes as well and they don’t do every visual aspect on the client (for example Your Bizzare Adventure has their ability models on the server side I’m pretty sure). So I should still have better server performance than these games.