Attempting to make a Pathfinding AI


You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I want to make Pathfinding AI, that has 2 Paths

  2. What is the issue? Include screenshots / videos if possible!
    You can see the issue on the video.


    `

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I’ve tried to do it with while loop cycle and it was working, but was really really slow.

---Services---
local PLS = game:GetService("Players")
local RS = game:GetService("RunService")
local PFS = game:GetService("PathfindingService")
local Debris = game:GetService("Debris")

---Variables---
local Character = script.Parent
local Humanoid = Character:WaitForChild("Humanoid")
local RootPart = Character.HumanoidRootPart

local CanJump = false
local fov = 50;

local PathParams = {
	AgentRadius = 6,
	AgentHeight = 5,
	AgentCanJump = CanJump,
}

RootPart:SetNetworkOwner(nil);

local WPs = workspace:FindFirstChild("Targets");

---Functions---
local function GetNearestPlayer()
	local nearest = math.huge
	local target = nil

	for _, player in pairs(PLS:GetPlayers()) do
		local character = player.Character

		if character == nil then
			continue
		end

		local distance = (character.HumanoidRootPart.Position - RootPart.Position)
		if distance.Magnitude <= fov then
			if distance.Magnitude < nearest then
				nearest = distance.Magnitude
				target = player
			end
		end
	end

	return target
end

local function getPath(destination)
	local path: Path = PFS:CreatePath(PathParams);
	
	path:ComputeAsync(RootPart.Position, destination);
	
	return path;
end;

local function attack(target)
	local tchar = target.Character;
	if tchar then
		local root = tchar:FindFirstChild("HumanoidRootPart");
		if root then
			local distance = (RootPart.Position - root.Position).Magnitude;
			
			if distance > 8 then
				local path = getPath(root.Position);
				
				if path.Status == Enum.PathStatus.Success then
					local waypoints = path:GetWaypoints();
					
					for index, waypoint in pairs(waypoints) do
						Humanoid:MoveTo(waypoint.Position);
						Humanoid.MoveToFinished:Wait();
					end;
				end;
			else
				local hum = tchar:FindFirstChild("Humanoid");
				if hum then
					hum.Health = 0;
				end;
			end;
		end;
	end;
end;

local function walkTo(destination)
	local path = getPath(destination.Position);
	
	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints();
		
		for index, waypoint in pairs(waypoints) do
			local target = GetNearestPlayer()
			if target then
				attack(target);
				break;
			else
				Humanoid:MoveTo(waypoint.Position);
				Humanoid.MoveToFinished:Wait();
			end;
		end;
	end;
end;

local function patrol()
	local waypoints = WPs:GetChildren();
	local rand = math.random(1, #waypoints);
	
	walkTo(waypoints[rand]);
end;

RS.Heartbeat:Connect(function()
	patrol();
end);

--[[while wait(0.01) do
	patrol()
end;]]
3 Likes

Is there a need for so many semi colons???

Try replacing the runservice.heartbeat with a while loop with a task.wait like this - while task.wait() do

i’ve tried it and it’s the same as while wait() do

Do yourself a favor and use SimplePath (see some examples).

Let me know if you still want to do it manually.

1 Like

I agree, ROBLOX Pathfinding is really bad.

thanks for letting me know about that ModuleScript, but now i have other issue
When AI reaches the player it stops moving and dont do anything (you can see that in a video)

---Services---
local PLS = game:GetService("Players")
local RS = game:GetService("RunService")
local PFS = game:GetService("PathfindingService")
local SS = game:GetService("ServerStorage");
local Debris = game:GetService("Debris")

---Variables---
local Freddy = script.Parent
local Humanoid = Freddy:WaitForChild("Humanoid")
local RootPart = Freddy.HumanoidRootPart

local SimplePath = require(SS.SimplePath);

local MovingTo = nil;
local PreviousPath = nil;

local CanJump = false
local fov = 50;

local PathParams = {
	AgentRadius = 6,
	AgentHeight = 5,
	AgentCanJump = CanJump,
}

RootPart:SetNetworkOwner(nil);

local WPs = workspace:FindFirstChild("Targets");

---Functions---
local function GetNearestPlayer()
	local nearest = math.huge
	local target = nil

	for _, player in pairs(PLS:GetPlayers()) do
		local character = player.Character

		if character == nil then
			continue
		end

		local distance = (character.HumanoidRootPart.Position - RootPart.Position)
		if distance.Magnitude <= fov then
			if distance.Magnitude < nearest then
				nearest = distance.Magnitude
				target = player
			end
		end
	end

	return target
end;

local function createFunc(path, argument)
	path.Blocked:Connect(function()
		path:Run(argument);
	end);

	path.Error:Connect(function()
		path:Run(argument);
	end);

	path.WaypointReached:Connect(function()
		local Target = GetNearestPlayer();
		if not Target then
			path:Run(argument);
		else
			local char = Target.Character;
			if char then
				local root = char:FindFirstChild("HumanoidRootPart");
				if root then
					path:Run(root);
				end;
			end
		end;
	end);
	
	path.Reached:Connect(function()
		if argument.Parent == WPs then
			MovingTo = nil;
			PreviousPath = argument;
		end;
	end);
end;

local function attack(target)
	local tchar = target.Character;
	if tchar then
		local root = tchar:FindFirstChild("HumanoidRootPart");
		if root then
			local distance = (RootPart.Position - root.Position).Magnitude;
			
			if distance > 8 then
				local path = SimplePath.new(Freddy);
				
				path.Visualize = true;
				
				path:Run(root);
				
				createFunc(path, root);
			else
				local hum = tchar:FindFirstChild("Humanoid");
				if hum then
					hum.Health = 0;
				end;
			end;
		end;
	end;
end;

local function walkTo(destination)
	local path = SimplePath.new(Freddy);
	local target = GetNearestPlayer();
	
	if target then
		attack(target);
	else
		path:Run(destination)

		path.Visualize = true;

		path:Run(destination);

		createFunc(path, destination);
	end
end;

local function patrol()
	if not MovingTo then
		local waypoints = WPs:GetChildren();
		local rand;
		
		repeat
			rand = math.random(1, #waypoints);
		until rand ~= PreviousPath;
		
		MovingTo = waypoints[rand];
		
		walkTo(waypoints[rand]);
	end;
end;

RS.Heartbeat:Connect(function()
	patrol();
end);

I figured it would be more interesting for you to try and debug the problem yourself.
that came off as threatening didnnit?

Here’s an idea: using print in order to verify if everything is going as planned, i.e., “print debugging”.

You should try using print to verify the correctness of data (basically testing the code), so place it wherever the bug is more likely to be at, or wherever you feel like it.

For example, it could be that the if statement in the patrol function is failing because MovingTo has been set, but for whatever reason not reset. This “reason” is what we’re trying to find. It will be crucial in the debugging process.

Speaking of which, there is only one place where MovingTo is reset: in the callback of the path.Reached connection. In that same function, you check if argument (a HumanoidRootPart) is parented to WPs. And, if so, MovingTo is reset. Here’s the thing: what if it isn’t parented to WPs? Or even better: why would a player’s body part be parented to a instance other than the character, at all?

I wasn’t even trying to, but it appears I’ve found the issue. How would I solve this otherwise? Printing MoveTo, which would instantly signal the root of the problem.

Give a man a fish, and you feed him for a day; teach a man to fish, and you feed him for a lifetime
Sun Tzu - Art Of War

1 Like

HumanoidRootPart cant be paranted to WPs, WPs is just a Waypoints Folder in Workspace. Nextbot will be moving to one of children of that Folder, if he don’t see nearest player around him. I was printing out MovingTo and it was giving me one of parts located in WPs. And attack function now looks like this and still has the same issue

local function attack(target)
	local tchar = target.Character;
	if tchar then
		local root = tchar:FindFirstChild("HumanoidRootPart");
		if root then
			local distance = (RootPart.Position - root.Position).Magnitude;
			
			if distance > 8 then
				local Path = SimplePath.new(Freddy);
				
				Path.Visualize = true;
				
				Path:Run(root);
				
				Path.Blocked:Connect(function()
					Path:Run(root);
				end);

				Path.Error:Connect(function()
					Path:Run(root);
				end);
			else
				print("killed that noob");
				print(MovingTo, PreviousPath);
				local hum = tchar:FindFirstChild("Humanoid");
				if hum then
					hum.Health = 0;
				end;
			end;
		end;
	end;
end;

I see. I misunderstand WPs, then.

Also, wouldn’t this new code suffer from the very same problem of MovingTo not being set to nil? Print MovingTo inside the patrol function.

1 Like

sooo it’s still prints out the random part of the WPs Folder…
After Freddy Reaches the Player, script just stops working, really weird…

Could you try using a while loop instead of RunService.Heartbeat? Like

while true do
    patrol()
end
2 Likes

i tried this and that works the same as RunService…
and i made my Freddy so it won’t kill my Player and for some reasons script won’t stop as soon as Freddy reaches the player…
I’m assuming that’s the problem of SimpePath probably or maybe there’s other ways out

If you comment out the Humanoid.MoveToFinished:Wait() that might stop the jitter. (On your first script)

Nah, that don’t working either sadly

My last attempt: can you remove the if argument.Parent == WPs check?

path.Reached:Connect(function()
    MovingTo = nil
    PreviousPath = argument
end)
1 Like

that don’t works too sadly
i think that after attack function something goes so wrong

Only take out the --Humanoid.MoveToFinished:Wait() in the attack function.

---Services---
local PLS = game:GetService("Players")
local RS = game:GetService("RunService")
local PFS = game:GetService("PathfindingService")
local Debris = game:GetService("Debris")

---Variables---
local Character = script.Parent
local Humanoid = Character:WaitForChild("Humanoid")
local RootPart = Character.HumanoidRootPart

local CanJump = false
local fov = 50

local PathParams = {
	AgentRadius = 6,
	AgentHeight = 5,
	AgentCanJump = CanJump,
}

RootPart:SetNetworkOwner(nil)

local WPs = workspace:FindFirstChild("Targets")

---Functions---
local function GetNearestPlayer()
	local nearest = math.huge
	local target = nil

	for _, player in pairs(PLS:GetPlayers()) do
		local character = player.Character

		if character == nil then
			continue
		end

		local distance = (character.HumanoidRootPart.Position - RootPart.Position)
		if distance.Magnitude <= fov then
			if distance.Magnitude < nearest then
				nearest = distance.Magnitude
				target = player
			end
		end
	end

	return target
end

local function getPath(destination)
	local path: Path = PFS:CreatePath(PathParams)

	path:ComputeAsync(RootPart.Position, destination)

	return path
end

local function attack(target)
	local tchar = target.Character
	if tchar then
		local root = tchar:FindFirstChild("HumanoidRootPart")
		if root then
			local distance = (RootPart.Position - root.Position).Magnitude

			if distance > 8 then
				local path = getPath(root.Position)

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

					for index, waypoint in pairs(waypoints) do
						Humanoid:MoveTo(waypoint.Position)
						--Humanoid.MoveToFinished:Wait()
					end
				end
			else
				local hum = tchar:FindFirstChild("Humanoid")
				if hum then
					hum.Health = 0
				end
			end
		end
	end
end

local function walkTo(destination)
	local path = getPath(destination.Position)

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

		for index, waypoint in pairs(waypoints) do
			local target = GetNearestPlayer()
			if target then
				attack(target)
				break
			else
				Humanoid:MoveTo(waypoint.Position)
				Humanoid.MoveToFinished:Wait()
			end
		end
	end
end

local function patrol()
	local waypoints = WPs:GetChildren()
	local rand = math.random(1, #waypoints)

	walkTo(waypoints[rand])
end

--RS.Heartbeat:Connect(function()
--	patrol()
--end)

while wait(0.01) do
	patrol()
end
1 Like

well, that actually works but it just moves forward to me, without computing the path

so i tried to make one that i think does what we want. See if you can make it work for your AI.


local Debris = game:GetService("Debris")
local PathFindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Character = script.Parent	
local root = Character.HumanoidRootPart
local FOV = 50

local function points(pos, color)
	local point = Instance.new("Part")
	point.Position = pos
	point.Color = color
	point.Size = Vector3. new(.3,.3,.3)
	point.Anchored = true
	point.CanCollide = false
	point.Parent = workspace
	Debris:AddItem(point,4)
end

local function Target() 
	local closestHumanoid = nil
	local distance = FOV
	for i, v in Players:GetChildren() do
		if (v.Character.PrimaryPart.Position - Character.PrimaryPart.Position).Magnitude < distance then
			distance = (v.Character.PrimaryPart.Position  - Character.PrimaryPart.Position).Magnitude
			closestHumanoid = v.Character
		end
	end

	if closestHumanoid == nil then	
		local random = math.random(-FOV,FOV)
		local position = root.CFrame * CFrame.new(random,0,random)
		return position.Position,true
	else
		local playerVelocity = closestHumanoid.Humanoid.MoveDirection * closestHumanoid.Humanoid.WalkSpeed
		local futurePosition = closestHumanoid.PrimaryPart.Position + playerVelocity * 1

		return futurePosition,false
	end
end





local function followPath(target)
	local targetDistance = (target - Character.PrimaryPart.Position).magnitude
	local dynamicSpacing = math.clamp(targetDistance / 2.75, 1, 15)
	print(dynamicSpacing)
	local path1 = PathFindingService:CreatePath({
		AgentCanJump = true,
		AgentCanClimb = true,
		WaypointSpacing = dynamicSpacing,--
		AgentRadius = 3,
		AgentHight = 8,

		Costs = {
			DangerZone = math.huge
		}
	})

	local path2 = PathFindingService:CreatePath({
		AgentCanJump = true,
		AgentCanClimb = true,
		WaypointSpacing = dynamicSpacing / 10,--
		AgentRadius = 3,
		AgentHight = 8,

		Costs = {
			DangerZone = math.huge
		}
	})
	path1:ComputeAsync(Character.PrimaryPart.Position, target)

	local waypoints1 = path1:GetWaypoints()

	for i = 1,3 do
		if waypoints1[i] == nil or waypoints1[i+1] == nil then
			else
			points(waypoints1[i].Position, Color3.fromRGB(43, 0, 255))
			path2:ComputeAsync(Character.PrimaryPart.Position, waypoints1[i+1].Position)
			local waypoints2 = path2:GetWaypoints()
			for _, waypoint2 in waypoints2 do
				points(waypoint2.Position, Color3.fromRGB(9, 255, 5))
				if waypoint2.Action == Enum.PathWaypointAction.Jump then
					print("JUMP")
					Character.Humanoid.Jump = true
				end
				Character.Humanoid:MoveTo(waypoint2.Position)
			end
		end	
	end
end

repeat 
	local target,patrol = Target()
	if patrol == true then
		followPath(target)	
		Character.Humanoid.MoveToFinished:Wait()
	else
		task.spawn(followPath,target)	
	end
	task.wait(.25)
until nil
1 Like