Any tips for optimising (npc finding-pathfinding) code?

I’ve been wondering if there is much more improvement to optimise this code for npc pathfinding,
If so please tell me, thanks in advance

local pathfindservice = game:GetService("PathfindingService")
local lastattack = 0
local pathmade = false
local target = nil
local ally = nil
local ignoretable = {}
local players = game:GetService("Players")
local par = RaycastParams.new()
local enemy = nil
local enemyteam = nil
table.insert(ignoretable,1,npc)
par.FilterDescendantsInstances = ignoretable
par.FilterType = Enum.RaycastFilterType.Blacklist

local function makepath()
	pathmade = true
	local raycast = workspace:Raycast(npc.PrimaryPart.Position,CFrame.new(npc.PrimaryPart.Position,target.Position).lookVector * (8 * npc.Humanoid.WalkSpeed),par)
	if raycast  then
		if raycast.Instance.Parent == target.Parent then
			local check
			check = target ~= nil and target.Parent ~= nil and target.Parent.Humanoid.Health > 0 
			if check then
				npc.Humanoid:Move(CFrame.new(npc.PrimaryPart.Position,target.Position).lookVector)			
			end
		else
			local path = pathfindservice:CreatePath()
			path:ComputeAsync(npc.PrimaryPart.Position,target.Position)
			if path.Status == Enum.PathStatus.Success then
				for _,v in ipairs(path:GetWaypoints()) do
					local ifcheck
					ifcheck = target ~= nil and target.Parent ~= nil and target.Parent.Humanoid.Health > 0 and (target.Parent.PrimaryPart.Transparency < 0.75 or (npc.PrimaryPart.Position - target.Parent.PrimaryPart.Position).Magnitude < 30)
					if ifcheck then
						local ray = workspace:Raycast(npc.PrimaryPart.Position,CFrame.new(npc.PrimaryPart.Position,target.Position).lookVector * (8 * npc.Humanoid.WalkSpeed),par)
						local ifcheck2 = ray and ray.Instance.Parent == target.Parent
						if ifcheck2 then
							break							
						else
							if v.Action == Enum.PathWaypointAction.Walk then
								npc.Humanoid:MoveTo(v.Position)
								npc.Humanoid.MoveToFinished:Wait()
							else
								npc.Humanoid.Jump = true
							end
						end						
					end
				end
			end
		end					
	end
	pathmade = false
end



game["Run Service"].Heartbeat:Connect(function()
	if npc.Parent == game.Workspace.DefenderNpc then
		enemy = workspace.AttackerNpc
		enemyteam = game.Teams.Attackers
		ally = workspace.DefenderNpc
	elseif npc.Parent == game.Workspace.AttackerNpc then
		enemy = workspace.DefenderNpc
		enemyteam = game.Teams.Defenders
		ally = workspace.AttackerNpc
	end		
	table.insert(ignoretable,2,ally)
	par.FilterDescendantsInstances = ignoretable
	local lowestdistance = 2500	
	for _,v in ipairs(enemyteam:GetPlayers()) do	
		local check = v.Character.PrimaryPart ~= nil and v.Character:FindFirstChild("Humanoid").Health > 0 
		if check then
			local detectrange = (50 * (1 - v.Character.Torso.Transparency))
			local detectcheck 
			detectcheck = (v.Character.Torso.Position - npc.PrimaryPart.Position).Magnitude < lowestdistance and (v.Character.Torso.Transparency < 0.75 or (npc.PrimaryPart.Position - v.Character.Torso.Position).Magnitude < detectrange)
			if detectcheck then
				lowestdistance = (v.Character.Torso.Position - npc.PrimaryPart.Position).Magnitude
				target = v.Character.Torso				
			end
		end		
	end
	for _,v in ipairs(enemy:GetChildren()) do
		local check
		check = v ~= nil and v.Parent ~= nil and v:IsA("Model") and v.PrimaryPart ~= nil and v:FindFirstChild("Humanoid")
		if check then
		local check2 
		local detectrange = (50 * (1 - v.PrimaryPart.Transparency))
			check2 = v.Humanoid.Health > 0 and (npc.PrimaryPart.Position - v.PrimaryPart.Position).Magnitude < 2500 and (npc.PrimaryPart.Position - v.PrimaryPart.Position).Magnitude < lowestdistance and (v.PrimaryPart.Transparency < 0.75 or (npc.PrimaryPart.Position - v.PrimaryPart.Position).Magnitude < detectrange)
			if check2 then
				lowestdistance = (npc.PrimaryPart.Position - v.PrimaryPart.Position).Magnitude
				target = v.PrimaryPart
			end
		end	
	end

	if target ~= nil and enemy ~= nil then
		if pathmade == false then
			local newpath = coroutine.wrap(makepath)
			newpath()
		end

		local targetcheck 
		targetcheck = target ~= nil and target.Parent ~= nil and target.Parent.Humanoid.Health > 0 and (npc.PrimaryPart.Position - target.Position).Magnitude < 2500 and npc.Humanoid.Health > 0 and npc.PrimaryPart.Anchored == false and time() - lastattack > npc.AttackCooldown.Value 
		if targetcheck then -- attack
				
		end
	end		
	table.remove(ignoretable,2)	
end)

3 Likes

You are running a lot of code very fast. Do not do that. ComputeAsync on paths should rarely be called as it is quite expensive. Also, NPCs don’t need frame-exact behavior. You can get away with a loop that runs an iteration of your NPC’s brain every 0.25 seconds or some unnoticeable time.

Additionally, you’re computing data like targets and paths a lot. You can cache these and only recompute them when a meaningful change occurs. An example is the path being blocked or the current target being too far or dying.

8 Likes

so nothing wrong with the raycasts and stuff?
just a debounce in the heartbeat loop (using time() so it wont get permanently stuck if it errors)
i got no time right now so not a long message

The raycasting shouldn’t be an issue, and shouldnt cause any lag for players since it’s on the server. The main problem will just be how frequently you’re calling this function as Autterfly said.

Also, I don’t know if you do this already, but you could check if the path to the player is a straight line and is clear. If it is, don’t calculate any paths for that npc and just move it directly to the player.

it conducts a test before ‘making a path to the player’, by raycasting towards the player by 8 studs x the walkspeed of the npc and if the raycast hits the player, it won’t make a path but moves directly, and calls move() (not moveto, there for preference so it wont look choppy), but is there any difference in move() rather than moveto() (apart from the fact it wont stop moving if you dont set move() to another lookvector)?

I’ll apply what autterfly said although I have to sleep really soon

thanks autterfly for the solution, i ran 100 of those npc scripts in studio only with the debounce and managed to only get 1.7%ish script activity compared to 15% activity

1 Like