How do I optimize raycasts?

I am running a lot of raycasts to ensure NPCs don’t clash into each other. This of course causes issues and I think is the reason why some of my rigs stopped moving but walked in place beforehand, but I decided to make a forum post once I couldn’t even run the game properly anymore (1 fps is frequent)

This is my script that is inserted to all rigs – the reason why I check if the humanoid’s ancestor is game or rigs is to seperate players and rigs, the rigs should only wait when i find a player

local anim = Instance.new("Animation")
anim.Parent = script.Parent
anim.AnimationId = "rbxassetid://WalkAnimIdThatIDidntPutInThisForumPost"
local antrac = script.Parent.Humanoid.Animator:LoadAnimation(anim)
while true do
	script.Parent.Humanoid.WalkSpeed = 10
	script.Parent.Humanoid:MoveTo(Vector3.new(math.random(-19.4, 392), 1.148, math.random(81.4, 363.3)))
	if antrac.IsPlaying == false then
		antrac:Play()
		antrac.Looped = true
	end
	local rayParams = RaycastParams.new()
	rayParams.FilterDescendantsInstances = {script.Parent}
	rayParams.FilterType = Enum.RaycastFilterType.Exclude
	local ray = workspace:Raycast(script.Parent.HumanoidRootPart.Position, script.Parent.HumanoidRootPart.Position + Vector3.new(0, 0, 3), rayParams)
	if not ray then return end
	if ray.Instance.Parent:FindFirstChild("Humanoid") then
		if ray.Instance.Parent:FindFirstChild("Humanoid").Parent.Parent.Parent.Name == "rigs" then
			ray.Instance.Parent:FindFirstChild("Humanoid"):MoveTo(ray.Instance.Parent:FindFirstChild("Humanoid").Parent.HumanoidRootPart.Position)
			antrac:Stop()
			continue
		elseif ray.Instance.Parent:FindFirstChild("Humanoid").Parent.Parent.Parent.Name ~= "rigs" then
			if ray.Instance.Parent:FindFirstChild("Humanoid").Parent.Parent.Name == "Game" then
				ray.Instance.Parent:FindFirstChild("Humanoid"):MoveTo(ray.Instance.Parent:FindFirstChild("Humanoid").Parent.HumanoidRootPart.Position)
				antrac:Stop()
				task.wait(math.random(5, 9))
				continue	
			end
		end
	end	
	script.Parent.Humanoid.MoveToFinished:Connect(function()
		antrac:Stop()
	end)
	task.wait(math.random(5, 9))
end
1 Like

I highly recommend having ONE script for all NPC’s. A centralized script essentially. Every time you create a new script, you are creating a new Lua thread (What roblox calls a “context”). Each thread has overhead. It needs its own execution state, stack space, and scheduling by Roblox’s task scheduler.

If you have 100 NPC’s, each with a script, you have 100 separate threads that need to be interpreted.

I also recommend looking into parallel Luau which I think will improve performance a ton. Here’s a video by CrusherFire that explains it perfectly:

7 Likes

Thanks! I’ll try looking into that

1 Like

at least cache non native values like RaycastParams, expensive as hell to create them so you must re use them

I’ll keep note of that as well

Wouldn’t a raycast be needed for each NPC nonetheless though? The origin will be different for each NPC, considering each NPC has a different HRP

Npcs are the perfect place to use OOP, please look into it. It works so good because every npc has the same methods, but can also carry their own data like their origins. You could just raycast from self.model.origin

4 Likes

Exactly what biasxed above said. OOP would work great here. You’re right, each NPC would need their own raycast, but you can do it in the same while true do loop.

Looked into it. Seems exactly like what I’m looking for, thanks!

1 Like

It’s really not. NPC logic is literally what ECS and functional programming patterns were built for.
Luau doesn’t have real OOP and never will; it’s just metatable fanfic.
Don’t throw corporate buzzwords at people like they’re universal truths.
OP, go with what actually performs and scales; don’t use cOOPe.

2 Likes

Hmm maybe the video I found when researching about it wasnt truly OOP, but the video was similar enough for me to implement my system, so I marked it as the solution

You can’t have real OOP in Luau.
The language doesn’t have classes, inheritance models, or any of the machinery real OOP relies on.
What people call “OOP” in Luau is just tables + metatables pretending to be something they’re not - cOOPe.
ECS/FP sits way closer to how the VM actually works, so it performs better, scales better, and doesn’t fight the compiler.
The “OOP” patterns people copy-paste are just overhead and usually slow the game down.

Yeah that seems about right… Saw a lot of metatables and tables in the video. What do you recommend? You could have suggested something in that reply in the 4th sentence but I don’t really understand it tbh, seems like theres a lot of acronyms there

1 Like

See, that’s the issue - metatables aren’t just confusing at first, they’re also slow. There’s basically zero upside to using them for “OOP” stuff (aside from __mode, which is super niche).
ECS in Luau is basically functional programming with extra steps, but in a good way:

E - Entity (your ID, usually the instance)
C - Component (data stored in a dictionary keyed by the entity)
S - System (a function that processes those dictionaries)
Here is a sample pseudo code:

local Health = {}

Players.PlayerAdded:Connect(function(plr)
    Health[plr] = 100
end)

Players.PlayerRemoving:Connect(function(plr)
    Health[plr] = nil
end)

That’s it. Super lightweight, super fast.

Just for God’s sake, don’t use JECS; it’s a fake ECS and all OOP underneath. Use this pattern I provided instead.

1 Like

Researching about ECS keeps giving me results about JECS and OOP too :sweat_smile:

I’ll try understanding it though. I found some topics about it. :grinning_face:

1 Like

Basically, instead of having “class” you have folders, if that makes sense.
Instead of

local class = {Health=100;Name="Inser Name Here"}

Instead you have a signature (entity), and most of the time it’s either a Player (Plr) or player ID.
So like:

local Health = {[Plr]=100}
local Names = {[Plr]="Insert Name Here"}

It’s better than OOP because you cut the amount of lookups.
You can instantly get a player’s health by using its ID, etc.:

print(Health[Plr])

But with OOP, you would’ve had to first get class, so:

print(Classes[Plr].Health)

And as you can see, there is always 1 more lookup that is pretty bad for performance, and I’d say it takes longer to write.

2 Likes

OP please, don’t listen to them. I know the solution is marked, but I’m trying to recorrect you so you don’t dig yourself a hole.

That is absolutely NOT ECS; the example he gave. That is literally just storing stuff in a dictionary. By their logic I’ve been doing ECS my very first days in Luau.
ECS zealots seem to come from backgrounds where they’ve read about how big studios optimize engines, and they want to apply these patterns everywhere.

Real ECS involves systems that iterate over ENTITIES with specific COMPONENT combinations. What was shown is just Health[Player] = 100. Thats literally just normal key-value storage.

“OOP bad, ECS good” take is architectural philosophy, not reality. Focus on optimizing your actual raycasts instead of rewriting your architecture based on misconceptions spread by incompetent people.

8 Likes

OOP seemed completely fine to me to be honest. It seems as if ECS and OOP work together as in what I’ve found but I’m not an expert.

4 Likes

this is your OPINION, and there’s a lot of ways for different people to do the same thing. just because you think that one way is better because you understand it, doesn’t mean other people want to use the same strategy. OOP is perfectly fine, and though I realize it’s not “real OOP,” and is just a workaround, it doesn’t really matter. it is scalable, and tons of people use it. it isn’t throwing out buzz words if it is a legitimate valid solution to the question. and optimization wise, there’s more important things you can focus on.

2 Likes

How many raycasts are you shooting for it to start lagging? Because raycasts are dirt cheap and you shouldn’t have to optimize them from what I’m reading in your code. It’s only of length 3 so it’s super short as well