So I made a custom HipHeight system by raycasting down 3 studs to check if there is a ground under the NPC but the problem is that raycasting every frame for hundreds of NPC’s start to decrease in performance. How could I reduce the lag?
300+ fps when i started, 120fps when the characters were there. that’s good considering the performance of many other games but questionable for weak devices. if you’d show your code it would be easier to help optimize. you considered task.desynchronize() and threads?
local RunService = game:GetService("RunService")
local character = script.Parent.Parent.Parent.Parent
local LerpToGround = require(script.Parent:WaitForChild("LerpToGround"))
RunService.Heartbeat:ConnectParallel(function(delta)
LerpToGround.LerpToGround(delta)
end)
LerpToGround Module:
local module = {}
local RunService = game:GetService("RunService")
local character = script.Parent.Parent.Parent.Parent
local HumanoidRootPart = character:WaitForChild("HumanoidRootPart")
local lerpSpeed = 100
local function CheckGrounded(origin, HipHeight, foundGround, position)
if foundGround then
local height = HipHeight - 0.01
local newPosition = position + Vector3.new(0, height, 0)
local newCFrame = CFrame.new(newPosition)
if origin.Y ~= newPosition.Y then
task.synchronize()
HumanoidRootPart.CFrame = newCFrame
end
end
end
function module.LerpToGround(delta)
local HipHeight = character:GetAttribute("HipHeight")
local origin = character:GetAttribute("CurrentPosition")
local cframe = CFrame.new(origin)
local oldPos = origin
local oldCFrame = cframe
local foundGround, position = script.Parent:GetAttribute("FoundGround"), script.Parent:GetAttribute("GroundPosition")
if not foundGround then
local distance = (oldPos - origin).Magnitude
local timeToReachTarget = (distance / lerpSpeed)
local alpha = math.clamp(delta / timeToReachTarget, 0, 1)
local lerp = oldCFrame:Lerp(cframe, alpha)
task.synchronize()
character:PivotTo(lerp)
task.desynchronize()
CheckGrounded(origin, HipHeight, foundGround, position)
else
CheckGrounded(origin, HipHeight, foundGround, position)
end
end
return module
Raycast module:
local module = {}
local character = script.Parent.Parent.Parent.Parent
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {character, workspace.NpcContainer}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
function module.GetGroundPosition()
local HipHeight = character:GetAttribute("HipHeight")
local rayOrigin = character:GetAttribute("CurrentPosition")
local rayDirection = Vector3.new(0, -HipHeight, 0)
local raycast = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
if raycast then
local position = raycast.Position
return true, position
end
return false, rayOrigin
end
return module
Well, except that it’s supposed to be parallelized, but every character is individually calling task.synchronize every frame, so nothing can actually run in parallel, ever.
1 raycast per character is nothing, the cost of that is negligible. The PivotTo, on the other hand, is not cheap for a model with multiple parts. Plus these are all running on the client, including all their animators? Yeah, it’s not the raycasts.
Yeah, CFrame tends to be a big bottleneck, it might help to implement BulkMoveTo but you’d probably see no improvement anyway.
You could always get creative and/or cheat a little bit, like, if it’s not necessary for each NPC to have their own raycasting & detection, you could have them act as a group and only raycast once and then position all of the NPCs based on that one result
A good tip for optimizing for time/performance is that somewhere, somehow, you’re going to be sacrificing memory; try to look for spots where you can cache information.
Additionally, in a lot of high-rate-of-activity (every frame) systems such as cameras, they’ll just give direct references to library functions like Vector3.new. I think you can see this in Roblox’s default camera scripts but I may be wrong.
ex: local vector3New = Vector3.new
I made this module Parallel Scheduler, which could help you parallelize your raycast. I would probably divide the work into like 10 - 50 raycasts per task
In my post, over 9 thousands raycasts are fired every frame (though, with a major impact to fps)
Another thing you can do is using BulkMoveTo as mentioned, give it a table of all the HumanoidRootParts, and a table with their corresponding CFrames and it should help a bit with fps
What is the point of this though? You can check what material a humanoid is walking on and if its air there is nothing, if you want to see how high a humanoid is from the ground then you could raycast only when the humanoid is walking on air. I’m also pretty sure there is a hipheight property in humanoid.
My point was that if you have 200 things running in a parallel context, but doing just microseconds worth of code evalution (like a single raycast or some distance checks), then overhead of the every-frame context-switching more than cancels out any benefits from parallelism. To get good benefit from parallelism, you have to be doing enough code crunching to make it worth the cost of the overhead.
Consider if you had to write a 10 page paper on some topic, say the Battle of the Bulge. Now you have 4 other people available to help you. There are smart ways to break up the work to make it faster, like each person writes 2 pages about a specific week during the battle, and then you compile them all in order at the end. And then there are really bad ways to divide up the work that will make it take much longer: like each person writes every 5th word of the report (and they “sync” on each sentence). This is not a perfect analogy, but you get the idea; just because something is running in a parallel execution context, doesn’t mean its actually benefitting from parallelism. Using parallel actors to each execute microseconds worth of code and then sync every frame is closer to the “no better than serial, probably worse” end of the continuum.