Help with Pathfinding. How would i make it so the AI isn't confused?

I was making a Pathfinding Script for my AI.

How it works is It’ll detect if there’s any possible Target or not in the field, if not it will move to the Base/Goal to attack it.

However the issue is if once i deployed one of my Units/Target the AI will act confused as he is about to move to the target but then refused and went for the Base instead.

Any possible way i could fix this?

local YIELDING = false
	
	local MAX_RETRIES = 10
	local RETRY_COOLDOWN = 0.7
	
	local reachedConnection
	local pathBlockedConnection
	
	local path = pathfindingservice:CreatePath({
		AgentRadius = 3,
		WaypointSpacing = 4,
		Costs = {
			Barrier = math.huge,
			HutWalls = math.huge,
		}
	})
	local function ComputePath(targetTorso,yield)
		local RETRY_NUM = 0 
		local success, errorMessage
		
		repeat
			RETRY_NUM += 1 
			success, errorMessage = pcall(path.ComputeAsync, path, enemy:WaitForChild("HumanoidRootPart").Position, targetTorso.Position)
			if not success then 
				task.wait(RETRY_COOLDOWN)
			end
		until success == true or RETRY_NUM > MAX_RETRIES

		if success then
			if path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				local currentWaypointIndex = 2

				if not reachedConnection then
					reachedConnection = zombiehumanoid.MoveToFinished:Connect(function(reached)
						if reached and currentWaypointIndex < #waypoints then
							currentWaypointIndex += 1

							zombiehumanoid:MoveTo(waypoints[currentWaypointIndex].Position)
						else
							reachedConnection:Disconnect()
							pathBlockedConnection:Disconnect()
							reachedConnection = nil
							pathBlockedConnection = nil
							YIELDING = false
						end
					end)
				end

				pathBlockedConnection = path.Blocked:Connect(function(waypointNumber)
					if waypointNumber > currentWaypointIndex then
						reachedConnection:Disconnect() 
						pathBlockedConnection:Disconnect()
						reachedConnection = nil
						pathBlockedConnection = nil
						ComputePath(targetTorso.Position, true) 
					end
				end)
				
				if targetTorso == nil then
					reachedConnection:Disconnect() 
					pathBlockedConnection:Disconnect()
					reachedConnection = nil
					pathBlockedConnection = nil
					ComputePath(targetTorso.Position, true)
				end

				zombiehumanoid:MoveTo(waypoints[currentWaypointIndex].Position)

				if yield then
					YIELDING = true
					repeat 
						task.wait()
					until YIELDING == false
				end
				
			else
				return
			end
		end
	end

	coroutine.wrap(function()
		while task.wait() do
			local torso = findtarget()
			if torso then
				ComputePath(torso,true)
			else
				ComputePath(Map:WaitForChild("Goal"),false)
			end
		end
	end)()
1 Like

When you say he is confused, it seems like you are the one confused. I would add some print statements around to see why and when the NPC recalculates it’s path back to the base.

Pathfinding is rather simple. The problem with making an ai is that it’s not just pathfinding. However I have a script here you can use. It’s already old as in I made a new version for one of my games but still works.

Exactly where print statement would you place to debug them?.

I might need to verify this but will it work without the Raycasting? My objects don’t really need them

All around. Whenever you are computing a new path and following it, print what the target is. Adding a few simple print statements is a pretty quick way to find and solve bugs, and you’ll get better at knowing where and what to print as you code more and run into more bugs.

Raycasting in my code ensures that the ai can actually see the target of it gets close too it. You can modify the code to not do that

1 Like

It’s not confused anymore but somehow. When the Target died the AI went backward and went for the dead unit position and stops there.

So add a getpropertychangedsignal and inside of it see if the humanoid is dead, if so recalculate the nearest player. You didn’t show your retargetting code, but I’m assuming you aren’t checking the humanoid’s health before considering them.

I recalculated the path now but it keeps running on the last dead body position. Was it because of the while loop or something?

Updated Code:

local YIELDING = false
	
	local MAX_RETRIES = 10
	local RETRY_COOLDOWN = 0.7
	
	local reachedConnection
	local pathBlockedConnection
	
	local path = pathfindingservice:CreatePath({
		AgentRadius = 3,
		WaypointSpacing = 8,
		Costs = {
			Barrier = math.huge,
			HutWalls = math.huge,
		}
	})
	
	
	local FoundNewTarget = false
	local MoveIsFinished = false
	local movingToGoal = false
	local movingToEnemy = false
	
	local pathfindingTarget = enemy:WaitForChild("PathfindingTarget")
	local HRP = enemy:WaitForChild("HumanoidRootPart")


local function ComputePath(targetTorso,yield)
		local RETRY_NUM = 0 
		local success, errorMessage
		pathfindingTarget.Value = targetTorso
		
		repeat
			RETRY_NUM += 1 
			success, errorMessage = pcall(path.ComputeAsync, path, HRP.Position, targetTorso.Position)
			if not success then 
				task.wait(RETRY_COOLDOWN)
			end
		until success == true or RETRY_NUM > MAX_RETRIES

		if success then
			if path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				local currentWaypointIndex = 2
				

				if not reachedConnection then
					reachedConnection = zombiehumanoid.MoveToFinished:Connect(function(reached)
						if reached and currentWaypointIndex < #waypoints then
							currentWaypointIndex += 1
							zombiehumanoid:MoveTo(waypoints[currentWaypointIndex].Position)
						else
							reachedConnection:Disconnect()
							pathBlockedConnection:Disconnect()
							reachedConnection = nil
							pathBlockedConnection = nil
							YIELDING = false
							MoveIsFinished = true
						end
					end)
				end

				pathBlockedConnection = path.Blocked:Connect(function(waypointNumber)
					if waypointNumber > currentWaypointIndex then -- blocked path is ahead of the bot
						reachedConnection:Disconnect() -- disconnect these events to prevent memory leaks
						pathBlockedConnection:Disconnect()
						reachedConnection = nil
						pathBlockedConnection = nil
						ComputePath(targetTorso, false) -- compute and cycle new path
					end
				end)
				
				local targetHumanoid = targetTorso.Parent:FindFirstChild("Humanoid")
				
				if targetHumanoid then
					targetHumanoid:GetPropertyChangedSignal("Health"):Connect(function()
						if targetTorso and targetTorso.Parent:WaitForChild("Humanoid").Health <= 0 then
							
							ComputePath(targetTorso,false)
						end
					end)
				end
				
				
				if targetTorso == nil then
					reachedConnection:Disconnect() 
					pathBlockedConnection:Disconnect()
					reachedConnection = nil
					pathBlockedConnection = nil
					ComputePath(targetTorso, false)
				end

				zombiehumanoid:MoveTo(waypoints[currentWaypointIndex].Position)

				if yield then
					YIELDING = true
					repeat 
						task.wait()
					until YIELDING == false
				end
				
			else
				return
			end
		end
		

	end	

	while task.wait() do
		local torso = findtarget()
		if torso and torso.Parent:FindFirstChild("Humanoid").Health > 0 then
			movingToGoal = true
			movingToEnemy = true
			if enemy:FindFirstChild("Boss") then
				zombiehumanoid:MoveTo(torso.Position)
			else
				if movingToEnemy and not MoveIsFinished then	
					if torso.Parent.Humanoid.Health > 0 then
						movingToGoal = false
						ComputePath(torso,false)
					end

				end
			end
		else
			if enemy:FindFirstChild("Boss") then
				zombiehumanoid:MoveTo(workspace.Map.Goal.Position)
			else
				if movingToGoal == false then
					ComputePath(Map:WaitForChild("Goal"),false)
					movingToGoal = true
				end
			end
			
			
		end

You still haven’t sent the find target code, so I can’t know if you are checking the target humanoid’s health while you are finding a target. Add some print statements throughout the while loop to see what is going on.

The findtarget code just finds the closest target from the enemy posisition. It returns their Torso
If you need the code i can provide you that

When you are looping through humanoids, ensure they are alive before considering them. You should also print the status in a few places in the while loop at the bottom so you can keep track what state the NPC is at.

Uhh i think the codes are already ensuring that the humanoid are alive by checking if their health are above 0?.

If you could send your findTarget code or do what I’ve said above, we could fix your code faster. In the segment you sent, you only check the health once, when it should be checked multiple times in your find target function, so it won’t route to a dead body and will always go to the nearest alive player.

Neat, I was roaming around here
here’s the findtarget code anyways

local function findtarget()
		local agrodistance = math.huge
		local target = nil
		for i, v in pairs(game.Workspace.Unit:GetChildren()) do
			local human = v:FindFirstChild("Humanoid")
			local torso = v:FindFirstChild("Torso")
			
			if v:FindFirstChild("Undetectable") then
				if not enemyStats.DetectHidden then
					continue 
				end 
			end
			
			if human and torso and v ~= script.Parent and human.Health > 0 then

				if (zombietorso.Position - torso.Position).magnitude < agrodistance then
					agrodistance = (zombietorso.Position - torso.Position).magnitude
					target = torso
				end
			end
		end
		return target
	end

Ok, so you do check that the humanoid is alive when searching through in this method. The next step is to put some print statements around your while loop to see when he changes target, and what his status is.

You meant status like if they exist right?

What his destination is, who he is targetting, just get an idea of what he is doing so you know what is going on and can tell me what the issue is. You should be able to filter it down to a single line.