How can I improve this pathfinding script?

I created this pathfinding module for NPC’s but it is not well optimized. My device isn’t that good but I still managed to get 20 FPS with game quality set to max and a cluster of 70 NPC’s chasing my character (with almost empty baseplate). I tried to optimize it as best I could, but I just don’t seem to get anywhere. Here is the modulescript:

local pathfinding = {}

function pathfinding.Handle(NPC)
	local PathfindingService = game:GetService("PathfindingService")
NPC.PrimaryPart:SetNetworkOwner(nil)
local path=PathfindingService:CreatePath() 
local range = NPC.Range.Value --range = 30
local currenttarget

--disabling useless humanoidstates
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Ragdoll, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Seated, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.StrafingNoPhysics, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.FallingDown, false)
NPC.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Flying, false)

--anchoring or unanchoring NPC's HumanoidRootPart based on speed
NPC.Humanoid.Running:Connect(function(speed)
	if speed == 0 then
		if not NPC.PrimaryPart.Anchored then --NPC.PrimaryPart is HumanoidRootPart
			NPC.PrimaryPart.Anchored = true
		else
			if NPC.PrimaryPart.Anchored then NPC.PrimaryPart.Anchored = false end
		end
	end
			
end)
while wait(0.4) do
	if not NPC.PrimaryPart then break end --if no NPC, then break
	
	for i, plr in ipairs(game:GetService("Players"):GetPlayers()) do
		if plr.Character then
			if plr:DistanceFromCharacter(NPC.PrimaryPart.Position) <= range then --not using .magnitude
				if not currenttarget then
					currenttarget = plr
				else
					if plr~=currenttarget and (plr:DistanceFromCharacter(NPC.PrimaryPart.Position) < currenttarget:DistanceFromCharacter(NPC.PrimaryPart.Position)) then
						currenttarget = plr
					end
				end
			end
		end
	end
	if currenttarget then
			if currenttarget.Character.PrimaryPart then
				NPC.PrimaryPart.Anchored = false
				local ray = Ray.new(NPC.PrimaryPart.Position, currenttarget.Character.PrimaryPart.Position - NPC.PrimaryPart.Position) --a ray from npc to player
				local part = workspace:FindPartOnRayWithIgnoreList(ray, {NPC, currenttarget.Character})
				if part then --pathfind
					path:ComputeAsync(NPC.PrimaryPart.Position,currenttarget.Character.PrimaryPart.Position)
					local waypoints = {}
					if path.Status == Enum.PathStatus.Success then
						waypoints = path:GetWaypoints()
						for i,point in pairs(waypoints) do
							if i < 6 then --6 is the maximum number of waypoints NPC can MoveTo because the while loop will make new waypoints for NPC to go to. this should increase if NPC's Walkspeed is increased
						if point.Action == Enum.PathWaypointAction.Jump then
							NPC.Humanoid.Jump = true
						end
						NPC.Humanoid:MoveTo(point.Position)
						--repeat wait() until (NPC.PrimaryPart.Position-point.Position).magnitude < 0.2 --if i enable this (or MoveToFinished:Wait()), the NPC anchors forever (or it just breaks)

						end
						end
					else
						NPC.Humanoid:MoveTo(NPC.HumanoidRootPart.Position)
					end
				else
					NPC.Humanoid:MoveTo(currenttarget.Character.PrimaryPart.Position) --if the player and NPC have nothing in between them blocking their path, then NPC doesnt need to pathfind. or does it? at least increases performance
				end
		end
	end
end
end
return pathfinding

Please dont just say “you are computing a new path way to fast” or “you should wait longer between points” or something because I tried it, but the NPC is not responsive and just seems to lag behind or pathfinding breaks.

2 Likes

Ah yes, they plague of :Wait() callbacks in MoveTo Functions.

If you find that your NPC is lagging behind each point you need to do a distance check.
Once then you take your studs per second, default 16.
An equation would be

distance/speed

So final is:

local distance = (waypoints[i-1].Point-point).magnitude
wait(distance/speed)

That should fix your issues (the script is untested).

Generally, I don’t use raycasts at all. It’s easier to condition if that number of waypoints is less then 3 and then just :MoveTo() the final position.

If you do end up using the wait() function you should also change your while true do loop to a smaller wait time, such as just wait() and make a condition if no path was found wait(0.4) this way performance isn’t sacrificed.

If you wanted to get in-depth I would recommend client-side tweening, which is what a lot of zombie games use and has extensibility of customizing the quality in the mind of performance.

Hope this helps!