Raycasting every frame lags a lot

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?

Game Link: (200 NPC’s)

1 Like

I have experienced no significant client frame drops.

You ran 60 fps in the game that I posted?

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?

Client:

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

seems pretty optimized, of course, there’s room for more but I assume raycasting would still outweigh the computational expenses.

the best optimization here would be to do math (raycast) as little as possible.

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. :laughing:

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.

I switched out PivotTo to setting the Root cframe (HumanoidRootPart.CFrame) and saved about 8 fps.

I disabled the HipHeight system and it saved me 15-20 fps.

1 Like


Unless I’m misinterpreting, no?

1 Like

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

2 Likes

I just tested and I started with 280 and it dropped to 50.

That is just horrible.

1 Like

Average FPS: 300

I disabled every script and only spawned 200 and it reduced my fps by about 50. They are all anchored too.

Only the movement system reduced my fps by 230.

Only the hipheight system reduced my fps by 230.

So I guess moving the character every frame causes the most lag.

1 Like

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

3 Likes

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.

Edit: here is the documentation Humanoid | Documentation - Roblox Creator Hub

1 Like

I’m also pretty sure that you can also improve performance by excluding as many parts as possible using OverlapParams

1 Like

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.

2 Likes

I found out that when I only spawn in ONE NPC, it reduces my fps by almost 50.

I am able to maintain almost 70 fps with 200 NPC’s!

I guess I optimized it enough. I am able to run 100 NPC’s with 100+ fps!