Pathfinding is extremely laggy even though it shouldn't be

okay so, this really cool pathfinding code

is responsible for… pathfinding!

the path gets created really perfectly, and all of that stuff (Which is seen in the video), but for whatever reason, the enemy goes backwards and then back to the player very quickly, this should also be visible in the video

i’m really not sure why this is happening?


the script that calls all the pathfinding things

local sScriptService = game:GetService("ServerScriptService")
local players = game:GetService("Players")

-- Path to module;
local enemyScriptsFolder = sScriptService:WaitForChild("Enemies")

-- Module;
local pathfindingModule = require(enemyScriptsFolder.PathfindingModule)
local enemyStats = require(enemyScriptsFolder.EnemyStats)

-- Folder of existing enemies (on the map);
local enemiesFolder = workspace:WaitForChild("Enemies")

-- Functions;
function GetEnemyBehaviorType(enemy: Model): number
	return enemyStats[enemy.Name]["behavior"]
end

function GetEnemyCriticalHealthAmount(enemy: Model): number
	return enemyStats[enemy.Name]["criticalHealth"]
end

function GetClosestPlayer(enemy: Model, limb: BasePart, closestPlayer: any): Player
	local character: Model = limb.Parent
	local humanoid: Humanoid = character:FindFirstChildOfClass("Humanoid")
	
	if not character or not humanoid then return end
	
	local targetPlayer = players:GetPlayerFromCharacter(character)
	
	if not targetPlayer then return end
	
	local enemyRootPart: BasePart = enemy:FindFirstChild("HumanoidRootPart")
	local playerRootPart: BasePart = character:FindFirstChild("HumanoidRootPart")
	
	-- long if statement
	-- checks for closest player
	if closestPlayer == nil or (enemyRootPart.Position - playerRootPart.Position).Magnitude < (enemyRootPart.Position - closestPlayer.Character.HumanoidRootPart.Position).Magnitude then
		closestPlayer = targetPlayer
	end
	
	return closestPlayer
end

function HandlePathfindInitiating()
	local enemies = enemiesFolder:GetChildren()

	if #enemies == 0 then return end

	for _, enemy in ipairs(enemies) do
		local detectionPart: BasePart = enemy:FindFirstChild("DetectionPart")
		local humanoid: Humanoid = enemy:FindFirstChildOfClass("Humanoid")
		local rootPart: BasePart = enemy:FindFirstChild("HumanoidRootPart")

		local closestPlayer = nil
		local playersInBox = workspace:GetPartBoundsInBox(detectionPart.CFrame, detectionPart.Size)

		for _, limb in playersInBox do
			-- yes i know, doing it twcie but to prevent overlaod on the server
			if not players:GetPlayerFromCharacter(limb.Parent) then continue end

			closestPlayer = GetClosestPlayer(enemy, limb, closestPlayer)
		end

		if closestPlayer == nil then continue end	

		task.spawn(function()
			-- if there's a closest player, pathfind to it
			if closestPlayer then
				local playerCharacter: Model = closestPlayer.Character
				local playerTorso: BasePart = playerCharacter:FindFirstChild("Torso")
				local playerPathfindPart: BasePart = playerTorso:FindFirstChild("PathfindPart")

				if not playerCharacter or not playerTorso or not playerPathfindPart then return end

				local targetPosition = playerPathfindPart.Position

				local enemyBehaviorType = GetEnemyBehaviorType(enemy)

				-- act based on enemy's behavior
				-- first, passive aggressive (flee on low health)
				if enemyBehaviorType == 0 then
					local enemyCriticalHealth = GetEnemyCriticalHealthAmount(enemy)

					if humanoid.Health <= enemyCriticalHealth then
						pathfindingModule.Flee(enemy)				
					else
						pathfindingModule.Chase(humanoid, targetPosition)
					end
					
					-- then, aggressive (attacks regardless of health)
				elseif enemyBehaviorType == 1 then
					pathfindingModule.Chase(humanoid, targetPosition)				
					-- then i need to add defensive here TODO
				end
				
				-- if there's no closest player, wander instead (if possible)
			else
				-- check if the enemy can wander
				if enemy:GetAttribute("WanderCooldown") == false and enemy:GetAttribute("Wandering") == false and enemy:GetAttribute("Chasing") == false then
					pathfindingModule.Wander(humanoid)

				else
					-- if the enemy cannot wander and is currently not wandering, 
					-- stop walking
					if enemy:GetAttribute("Wandering") == false then
						humanoid:MoveTo(rootPart.Position)
					end
				end
			end
		end)
	end
end

-- Runtime;
while task.wait(0.05) do
	HandlePathfindInitiating()
end

module script (relevant parts only)

function CreatePath(startPos: Vector3, targetPos: Vector3)
	local path: Path = pfService:CreatePath(pathParameters)

	local success, result = pcall(function()
		path:ComputeAsync(startPos, targetPos)
	end)

	local waypoints = path:GetWaypoints()

	if path.Status == Enum.PathStatus.Success and #waypoints > 0 then
		return waypoints
	end
end

function MoveTo(waypoints, humanoid: Humanoid)
	for i = 1, #waypoints do
		local waypoint: PathWaypoint = waypoints[i]

		if waypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end

		VisualizePath(waypoint)

		humanoid:MoveTo(waypoint.Position)
		humanoid.MoveToFinished:Wait()
	end
end

function pathfinding.Chase(Humanoid: Humanoid, targetPosition: Vector3) -- Chase the player;
	-- Used in: [Aggressive, Passive-Aggressive]
	if not Humanoid then return end		
	if not targetPosition then return end
	
	local char: Model = Humanoid.Parent
	
	char:SetAttribute("Chasing", true)
	
	-- Getting the needed pathfinding data;
	local path: Path = pfService:CreatePath(pathParameters)
	local humanoidRootPart = char:FindFirstChild("HumanoidRootPart")

	local startPosition = humanoidRootPart.Position
	
	local waypoints = CreatePath(startPosition, targetPosition)
	
	if not waypoints then
		char:SetAttribute("Chasing", false)
		return
	end
	
	Humanoid.WalkSpeed = enemyStats[char.Name]["chaseSpeed"]
	
	MoveTo(waypoints, Humanoid)
	
	char:SetAttribute("Chasing", false)
end

okay, i have located the issue (i think)

for whatever reason, the MoveTo function takes into account any of the previous paths, so the enemy gets extremely confused and just starts walking back and forth, this is really visible with that print statement, and also with the fact older paths continue to be visualized even though a new one has been created

nevermind! turns out it’s just extremely laggy, even when it shouldn’t be…

i am SO confused

1 Like

OKAY

I FOUND THE REAL ISSUE, THIS TIME I’M VERY SURE

the enemy just stops chasing the player extremely quickly, even though it should stop chasing the player once it completes the entire path and there is no player in its radius

then it respots the player and chases again, resulting in path spam, THAT’S why

I don’t think you should be doing this every single time just to call :ComputeAsync once.

What code is the rerunning!! statement under? It looks like it’s being called a lot. Trying to :ComputeAsync too frequently can lead to requests taking longer, which gets worse with more complicated navigation scenarios.

don’t you need to create the path before computing it?

i’m really bad at pathfinding, sorry

You can save it in a variable at the beginning, and re-use it for any number of calls to :ComputeAsync

You can also look into SimplePath, which is made for the purpose of continuous pathfinding by allowing to spam it’s :Run function, and have it automatically update and follow waypoints without any weird “turning around”. I would recommend this over spending your time trying to build your own system for continuous pathfinding:

just a quick question

does this mean i need to remove my entire pathfinding module, and do all the calculations in the server script, and only then call the :Run() function?

I believe you can replace your pathfinding.Chase with Path:Run

pathfindingModule.Chase(humanoid, targetPosition)

Path:Run(targetPosition)

Path should be defined, once per enemy, as:

local Path = SimplePath.new(Enemy, pathParameters)

, where Enemy is the Character of the Enemy, with a Humanoid. SimplePath is just require(path.to.module).

1 Like