Oh my god this is soo goos I love it. It will really change a lot, and help me out
Thanks guys
Oh my god this is soo goos I love it. It will really change a lot, and help me out
Thanks guys
Will these new region3 functions have any performance benefits over the old ones?
I noticed a spelling error in “Workspace” you had put “Worksapce”
It’s only baseparts, so no. Zone plus is still still great tool!
Yess! Now I can hopefully continue working on my guitar hero Roblox remake!
Will we be getting a boxcasting and spherecasting implementation next? Or will line-of-sight checks fall to developers?
I swear everytime I am in the middle of developing a system using my own method you guys release a beautiful alternative and I have to go in and change what I made the same day.
Brilliant! Thank you for this!
In my game, monsters are placed in a specific directory. If the returned result can be limited to the children in the specified directory with type of “Model”, that will be more better. I will not need to traverse which part is that monster.
When there is a limit on the number, it would be even better if we can choose to filter by distance
this is cool but still please dont remove findpartsinregion3 as a lot of scripters use it. (ofc they can update it) but some games have very lively communities while still not receiving updates.
This is deprecation, not removal. It probably won’t be removed for a long time due to the amount of code that relies on it.
Deprecation doesn’t mean removal.
Deprecation is usually a standardized way to point developers at the newer better function that superseded an older one, not an actual indication that the older function will be removed at some point. Heavily used APIs like this will likely never be removed despite being deprecated.
I may be using it incorrectly but from my tests :GetTouchingParts() performed significantly better than using :GetPartBoundsInBox() despite creating a new CheckArea part at the start of the FindSpace() function.
For context, I have tree models in ServerStorage which I then use their BoundingBox to get both the Size for TouchingParts and BoundingBox for PartBoundsInBox methods, then find a space via Raycast and set the area to check based on the area found to locate other trees in the area.
GetTouchingParts (30-40 FPS, 4 second render time)
local CheckPart = nil
if typeof(ModelSize) == "Vector3" and #GrassVectors > 0 then
CheckPart = ServerStore.CheckArea:Clone()
CheckPart.Size = ModelSize
CheckPart.Touched:Connect(function(HitPart) end)
CheckPart.Parent = Work
end
local Attempts = 0
repeat
Attempts = Attempts + 1
local GrassData = GrassVectors[math.random(1, #GrassVectors)]
local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
if Result.Instance == GrassData.Part then
if CheckPart then
CheckPart.CFrame = CFrame.new(Result.Position + Vector3.new(0, CheckPart.Size.Y / 2, 0))
local Parts = CheckPart:GetTouchingParts()
for Index, Object in pairs(Parts) do
if Object:FindFirstAncestor("Trees") and Object:FindFirstAncestor("Trees") == Configs.TreeStorage then
Parts = false
break
end
end
if Parts == false then
continue
end
end
if CheckPart then
CheckPart:Destroy()
end
return Result.Position + Vector3.new(0, 0.5, 0)
end
until Attempts >= Configs.FindSpaceAttempts
PartBoundsInBox (2.5 FPS, 29 second render time)
local Attempts = 0
repeat
Attempts = Attempts + 1
local GrassData = GrassVectors[math.random(1, #GrassVectors)]
local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
if Result.Instance == GrassData.Part then
--[Temp] Tree Models are not oriented correctly, X and Y axis are reversed
local TreeParts = workspace:GetPartBoundsInBox((ModelFrame - ModelFrame.Position) + Vector3.new(Result.Position.X, Result.Position.Y + (ModelSize.X / 2), Result.Position.Z), ModelSize, Configs.TreeFilter)
if #TreeParts > 0 then
continue
end
return Result.Position + Vector3.new(0, 0.5, 0)
end
until Attempts >= Configs.FindSpaceAttempts
return false
GetPartsInPart (2.5 FPS, 31 second render time)
local CheckPart = nil
if typeof(ModelSize) == "Vector3" and #GrassVectors > 0 then
CheckPart = ServerStore.CheckArea:Clone()
CheckPart.Size = ModelSize
CheckPart.Touched:Connect(function(HitPart) end)
CheckPart.Parent = Work
end
if #GrassVectors > 0 then
local Attempts = 0
repeat
Attempts = Attempts + 1
local GrassData = GrassVectors[math.random(1, #GrassVectors)]
local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
if Result.Instance == GrassData.Part then
if CheckPart then
CheckPart.CFrame = CFrame.new(Result.Position + Vector3.new(0, CheckPart.Size.Y / 2, 0))
local Parts = game.Workspace:GetPartsInPart(CheckPart, Configs.TreeFilter)
if #Parts > 0 then
continue
end
end
if CheckPart then
CheckPart:Destroy()
end
return Result.Position + Vector3.new(0, 0.5, 0)
end
until Attempts >= Configs.FindSpaceAttempts
if CheckPart then
CheckPart:Destroy()
end
return false
end
Edit: Did another quick test and :GetPartsInPart() had the same issue when attempting to convert :GetTouchingParts() to it.
After being initially excited, I am a bit confused and slightly disappointed with this new library for the following reasons:
Performance differences of new APIs
The performance of these new functions, while may be improved over alternative methods (like external libraries which support rotated region detection and native solutions such as GetTouchingParts
which have problems such as collision requirements) are very poor compared to a simple AABB test through API like FindPartsInRegion3
. Here’s an example of this poor performance when we use GetPartsInPart
:
The first number indicates the amount of parts, second number is the amount of milliseconds for 100 API calls, third element is if GetPartsInPart
is used (true
) or FindPartsInRegion3
is used (false
):
0 0.0778ms false
0 0.4018ms true
25 0.0913ms false
25 6.1577ms true
100 0.0825ms false
100 21.1336ms true
1000 0.0768ms false
1000 240.5298ms true
5000 0.0632ms false
5000 1459.7305ms true
10000 0.0665ms false
10000 3090.1008ms true
Source code for benchmark: GitHub Gist
Now this is nearly unusable for games that have large amounts of parts inside the spatial query (30.9ms for a call with 10,000 parts), so I switched to try GetPartBoundsInBox
which was better with these results:
Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox
is used (true
) or FindPartsInRegion3
is used (false
):
0 0.1103ms false
0 0.4088ms true
25 0.0964ms false
25 4.1149ms true
100 0.0760ms false
100 5.3696ms true
1000 0.0620ms false
1000 49.1931ms true
5000 0.0646ms false
5000 528.7402ms true
10000 0.0846ms false
10000 1249.3728ms true
Source code for benchmark: GitHub Gist
While this is certainly improved, an average API call of 12.49ms is nearly a full frame! This is a very expensive operation but how often do we really need to query 10,000 parts which are all inside where we are looking? Not that often, so I decided to try out the MaxParts
and filtering features to see if this changes the speed.
As a developer, I have use-cases where only a simple AABB test is necessary rather than one which utilizes rotated regions. The speed implications of having to do rotational checks makes it infeasible for some of my use cases, so I have to resort to using deprecated functions like FindPartsInRegion3
.
I think it’s important to note here that the intended use case of FindPartInParts
is where your BasePart has custom geometry, like a MeshPart — which is harder to calculate, so this probably explains why FindPartInParts
is so slow. It’s also important to note that in all these benchmarks all the parts are inside our query, so it is much slower. I explain how a spread out map makes this much more efficient at the end of this post in the interesting benchmarks section.
MaxParts is not fully effective
In my tests, I found this to be partially true, and the API still consumes a large amount of time. This is only a minor problem, as FilterDescendantInstances
solves most of the use case of checking if specific parts are inside a box, but it is still important to note that calls are still expensive even with MaxParts
set to 1
and increase proportionally to the amount of parts.
Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox
is used alongside MaxParts
set to 1
(true
) or FindPartsInRegion3
is used (false
):
0 0.1057ms false
0 0.9415ms true
25 0.1370ms false
25 1.1478ms true
100 0.0670ms false
100 2.0912ms true
1000 0.0869ms false
1000 19.3874ms true
5000 0.0662ms false
5000 103.2925ms true
10000 0.2380ms false
10000 213.2466ms true
Source code for benchmark: GitHub Gist
So we can see that while MaxParts does work to some extent, API calls are quite slow. This is particularly concerning because it should have only had to iterate over one part, as all our parts are made inside our hitbox the first part it looks up should fulfill the maximum parts it needs to look for — so I was not expecting the time to still scale linearly.
FilterDescendantsInstances causes worse performance
In my tests, I found that using FilterDescendantsInstances
actually causes worse performance than if you were to not use it, even if the instance whitelist was only a small subsection of parts under WorldRoot
. I thought this was very bizarre, because naturally I would expect this to not scale linearly since I have set the descendant whitelist to be the first 25 parts, but similar to MaxParts, it seems to be scaling again, which is very counter-intuitive. Here’s the benchmark results:
Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox
is used alongside FilterDescendantsInstances
set to 25 parts (true
) or FindPartsInRegion3
is used (false
):
0 0.0708ms false
0 0.3183ms true
25 0.0760ms false
25 2.4035ms true
100 0.0624ms false
100 7.2634ms true
1000 0.1041ms false
1000 81.3375ms true
5000 0.0742ms false
5000 765.0460ms true
10000 0.0678ms false
10000 1717.8362ms true
Source code used for this benchmark: GitHub Gist
As we can see from the benchmark results, this is actually slower than just not using FilterDescendantsInstances
at all (see GetPartBoundsInBox
vs FindPartsInRegion3
results).
I think this is very important to know about, because you would be better off not using the FilterDescendantsInstances
and instead iterating over results and checking if they are what you are looking for yourself in many cases. These footguns with the new API make it very concerning for me to use.
Concluding thoughts
While these new APIs seem very easy to use and simple, they have huge performance implications which make them unusable in some cases where a large amount of parts are inside the area being checked. My biggest problem is that since the old methods have been marked as deprecated, what am I supposed to use for a simple AABB test when I do not need rotational checking and I need high performance code?
Overall, this is exciting! But there are some things I am confused about how this new API works which would be great to have some clarification.
Sidenote, some small interesting benchmarks
Some users have asked about whether using GetPartBoundsInRadius
is faster than GetPartBoundsInBox
— I found that GetPartBoundsInBox
is marginally better more often, but I do not think that the difference is significant and you should go with whatever your use case is.
I ran these on a Hitbox with size 50, 50, 50
Similar format results to before, where the first number indicates the amount of parts second is the amount of milliseconds for 100 API calls third element is if GetPartBoundsInRadius
is used (true
) or GetPartBoundsInBox
is used (false
):
0 0.5943ms false
0 0.8691ms true
25 2.1426ms false
25 2.3293ms true
100 5.0867ms false
100 4.4336ms true
1000 47.9035ms false
1000 34.5973ms true
5000 606.7480ms false
5000 637.8543ms true
10000 1367.7854ms false
10000 997.0864ms true
Source code for this benchmark: GitHub Gist
The size of the box influences the amount of time it takes when using GetPartsInPart
— I found that there was at least some level of correlation between the size of the part and the amount of time that it took. I did not extensively test this however, so feel free to do so yourself.
This new API uses some really cool spatial understanding to filter out what it needs to check. In all these benchmarks I’ve shared, all the parts have been inside the query. When I spread the parts out a bit, so that roughly ~1/4 are inside the query, the function performs 30x faster! This is really good news, so definitely benchmark these new APIs in your game to see if they perform faster than custom rotated solutions (sidenote: It is still much slower than FindPartsInRegion3
, I had 0.11ms vs 48ms when I spread the parts out so that 1/4 of the parts were inside my query). I cannot stress how cool this is, because it means most use cases of these new APIs will have perfectly fine performance if you need a rotated check.
If you want to run any of the benchmarks I have posted throughout this thread, my Hitbox was made with the following parameters:
local Workspace = game:GetService("Workspace")
local ChangeHistoryService = game:GetService("ChangeHistoryService")
ChangeHistoryService:SetWaypoint("BeforeHitbox")
local Hitbox = Instance.new("Part")
Hitbox.Name = "Hitbox"
Hitbox.Size = Vector3.new(42.1, 25.1, 54.3)
Hitbox.CFrame = CFrame.new(-94.5620041, 23.2386665, -55.8205566, 0.492402792, -0.413175881, -0.766040146, 0.642785311, 0.766043305, -1.1920929e-07, 0.586822629, -0.492403567, 0.642783582)
Hitbox.Transparency = 0.5
Hitbox.Anchored = true
Hitbox.CanCollide = false
Hitbox.Parent = Workspace
ChangeHistoryService:SetWaypoint("AfterHitbox")
I often use magnitude in for loops to get parts in a radius. will these new additions not only be a better alternative but also better performance-wise?
As a combat-games developer, this is probably the best thing that I could ever ask for. Thanks a lot for this Roblox.
Anyone know how to convert FindPartsInRegion3
to WorldRoot:GetPartBoundsInBox
.
Semi-joke aside, is there a performance difference between FindPartsInRegion3
versus GetPartBoundsInBox
? I run a very small region check every heartbeat for my anticheat and I’m curious if it’ll improve speeds/decrease possible script activity in Script Performance.
Edit:
I’m extremely pleased with this update. Originally I had some not-so-performant code when running a Region3 check every heartbeat and with this new library I was able to easily condense it. Because of this new library I cut down on a nice chunk of script activity.
Thanks for another nice update.
I’m so excited. Another great update .
It’s almost sad how much easier it is now, due to all of the lines I made to do these things manually… Althoug I guess that’s the point!
this is a unrealistic benchmark – what game would ever have 10k or more parts in 1 part?
plus your stats could be faked
You really just don’t want to be nice for once, but that type if thing is called a BENCHMARK, theres a very low chance you would need that many parts in one part, but it is used for BENCHMARKING performance.