local PFS = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local PathHandler = {}
PathHandler.OriginalPositions = {}
-- Set player WalkSpeed on join
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.WalkSpeed = 50
end)
end)
function PathHandler.IsInVision(monster, targetCharacter)
-- Set field of view in degrees; adjust as needed.
local fovAngle = 45
local threshold = math.cos(math.rad(fovAngle))
local direction = (targetCharacter.PrimaryPart.Position - monster.PrimaryPart.Position).Unit
local lookVector = monster.PrimaryPart.CFrame.LookVector
local dot = lookVector:Dot(direction)
return dot >= threshold
end
-- Internal function to visualize waypoints
local function VisualizeWaypoints(waypoints, duration)
duration = duration or 5 -- default duration is 5 seconds
for _, waypoint in ipairs(waypoints) do
local marker = Instance.new("Part")
marker.Name = "WaypointMarker"
marker.Shape = Enum.PartType.Ball
marker.Material = Enum.Material.Neon
marker.Color = Color3.fromRGB(255, 0, 0) -- bright red
marker.Size = Vector3.new(1, 1, 1)
marker.Anchored = true
marker.CanCollide = false
marker.Position = waypoint.Position
marker.Parent = workspace
Debris:AddItem(marker, duration)
end
end
-- Attempts to follow the target character by computing a path and moving the NPC along it
function PathHandler.FollowPlayer(monster, targetCharacter)
-- Get the monster's dimensions for agent parameters
local extents = monster:GetExtentsSize()
local x = extents.Y -- Using Y as height (adjust if needed)
local y = math.max(extents.X, extents.Z) / 2 -- Approximate horizontal radius
local agency = {
AgentRadius = x,
AgentHeight = y
}
-- Create and compute the path using the monster's PrimaryPart
local path = PFS:CreatePath(agency)
path:ComputeAsync(monster.PrimaryPart.Position, targetCharacter.PrimaryPart.Position)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
VisualizeWaypoints(waypoints)
-- Connect the Blocked event once
local blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
print("Path is blocked at waypoint:", blockedWaypointIndex)
-- Optionally: Recalculate the path here if needed.
end)
-- Start from waypoint 2 (assuming waypoint 1 is the starting position)
local currentWaypointIndex = 2
if currentWaypointIndex <= #waypoints then
monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
end
-- Wait for the NPC to reach each waypoint before moving on
local moveConnection
moveConnection = monster.Humanoid.MoveToFinished:Connect(function(reached)
if reached then
currentWaypointIndex = currentWaypointIndex + 1
if currentWaypointIndex <= #waypoints then
monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
else
-- Completed the path; disconnect events.
moveConnection:Disconnect()
blockedConnection:Disconnect()
end
else
moveConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
else
warn("Pathfinding failed with status:", path.Status)
end
end
function PathHandler.ReturnHome(monster,originalCFrame)
if originalCFrame then
local homeTarget = {PrimaryPart = {Position = originalCFrame.Position}}
PathHandler.FollowPlayer(monster, homeTarget)
end
end
-- Checks for the nearest player and, if within range, calls FollowPlayer on that character
function PathHandler.CheckNearestPlayer(monster:Model)
-- First, check if the monster is too far from its original home position.
local IsReturning = monster:GetAttribute("IsReturning")
local originalCFrame = PathHandler.OriginalPositions[monster]
if originalCFrame then
local distFromHome = (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude
local MaxAway = monster:GetAttribute("MaxAway")
if distFromHome > MaxAway then
monster:SetAttribute("IsReturning", true)
end
if monster:GetAttribute("IsReturning") then
PathHandler.ReturnHome(monster,originalCFrame)
if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
monster:SetAttribute("IsReturning", false)
end
return
end
end
local players = Players:GetPlayers()
if #players > 0 or IsReturning == false then
local MaxDist = monster:GetAttribute("MaxDist")
for _, player in ipairs(players) do
local character = player.Character
-- Ensure the character exists, has a Humanoid, and a PrimaryPart
if character and character:FindFirstChildWhichIsA("Humanoid") and character.PrimaryPart then
local distance = (monster.PrimaryPart.Position - character.PrimaryPart.Position).Magnitude
if distance < 8 or (distance < MaxDist and PathHandler.IsInVision(monster, character)) then
PathHandler.FollowPlayer(monster, character)
end
end
end
end
end
function PathHandler.Start(monster,monsterCframe)
task.spawn(function()
PathHandler.OriginalPositions[monster] = monsterCframe
while true do
if monster:GetAttribute("IsReturning") == true then
task.wait()
continue
end
PathHandler.CheckNearestPlayer(monster)
task.wait()
end
end)
end
return PathHandler
well the issue on that pathfinder is when the npc returns to its original pos he starts to move randomly and the isreturning variable gets between true/false changed when he does that
I think you need to seperate your functions in the PathHandler.CheckNearestPlayer. Your While true do, which I wish people would stop using lol, is calling it over and over and over again this is causing the NPC to recalculate the path, a lot. Look at noobpath, they use an idle system and do a great job of showing how to do this properly. When the NPC finishes the path they idle, then this makes them stop moving and stops them from recalculating the path 100 times a second pretty much.
if u are still here
i ve changed the whilte loop to while monster:GetAttribute("IsRunning) == false do end
and still the problem occurs
when the npcs returns back he starts to go on and back and that attribute starts to changed from true to false for like 3 secs
That makes sense, looking at your code that is intended logic, seperate your functions, and call them in order. Make sure to never touch the attribute again only 2 times. You should only CHECK for the attribute once during path calculation in the check nearest player. But you are not only checking it, you are setting it and modifying it every tenth of a second. I think if you get that code out and only check it once during that function, and also DO NOT modify it in that same thread you will be fine. I will say side note is you can look at other path modules on here to see how others accomplished what you are doing.
local PFS = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local PathHandler = {}
PathHandler.OriginalPositions = {}
-- Set player WalkSpeed on join
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.WalkSpeed = 50
end)
end)
function PathHandler.IsInVision(monster, targetCharacter)
-- Set field of view in degrees; adjust as needed.
local fovAngle = 45
local threshold = math.cos(math.rad(fovAngle))
local direction = (targetCharacter.PrimaryPart.Position - monster.PrimaryPart.Position).Unit
local lookVector = monster.PrimaryPart.CFrame.LookVector
local dot = lookVector:Dot(direction)
return dot >= threshold
end
-- Internal function to visualize waypoints
local function VisualizeWaypoints(waypoints, duration)
duration = duration or 5 -- default duration is 5 seconds
for _, waypoint in ipairs(waypoints) do
local marker = Instance.new("Part")
marker.Name = "WaypointMarker"
marker.Shape = Enum.PartType.Ball
marker.Material = Enum.Material.Neon
marker.Color = Color3.fromRGB(255, 0, 0) -- bright red
marker.Size = Vector3.new(1, 1, 1)
marker.Anchored = true
marker.CanCollide = false
marker.Position = waypoint.Position
marker.Parent = workspace
Debris:AddItem(marker, duration)
end
end
-- Attempts to follow the target character by computing a path and moving the NPC along it
function PathHandler.FollowPlayer(monster, targetCharacter)
-- Get the monster's dimensions for agent parameters
local extents = monster:GetExtentsSize()
local x = extents.Y -- Using Y as height (adjust if needed)
local y = math.max(extents.X, extents.Z) / 2 -- Approximate horizontal radius
local agency = {
AgentRadius = x,
AgentHeight = y
}
-- Create and compute the path using the monster's PrimaryPart
local path = PFS:CreatePath(agency)
path:ComputeAsync(monster.PrimaryPart.Position, targetCharacter.PrimaryPart.Position)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
VisualizeWaypoints(waypoints)
-- Connect the Blocked event once
local blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
print("Path is blocked at waypoint:", blockedWaypointIndex)
-- Optionally: Recalculate the path here if needed.
end)
-- Start from waypoint 2 (assuming waypoint 1 is the starting position)
local currentWaypointIndex = 2
if currentWaypointIndex <= #waypoints then
monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
end
-- Wait for the NPC to reach each waypoint before moving on
local moveConnection
moveConnection = monster.Humanoid.MoveToFinished:Connect(function(reached)
if reached then
currentWaypointIndex = currentWaypointIndex + 1
if currentWaypointIndex <= #waypoints then
monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
else
-- Completed the path; disconnect events.
moveConnection:Disconnect()
blockedConnection:Disconnect()
end
else
moveConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
else
warn("Pathfinding failed with status:", path.Status)
end
end
function PathHandler.ReturnHome(monster,originalCFrame)
if originalCFrame then
local homeTarget = {PrimaryPart = {Position = originalCFrame.Position}}
if monster.Name == "Rig2" then
print(homeTarget)
end
if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
monster:SetAttribute("IsReturning", false)
return
end
PathHandler.FollowPlayer(monster, homeTarget)
end
end
-- Checks for the nearest player and, if within range, calls FollowPlayer on that character
function PathHandler.CheckNearestPlayer(monster:Model)
-- First, check if the monster is too far from its original home position.
local IsReturning = monster:GetAttribute("IsReturning")
local originalCFrame = PathHandler.OriginalPositions[monster]
if originalCFrame then
local distFromHome = (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude
local MaxAway = monster:GetAttribute("MaxAway")
if distFromHome > MaxAway then
monster:SetAttribute("IsReturning", true)
PathHandler.ReturnHome(monster,originalCFrame)
end
end
local players = Players:GetPlayers()
if #players > 0 then
local MaxDist = monster:GetAttribute("MaxDist")
for _, player in ipairs(players) do
local character = player.Character
-- Ensure the character exists, has a Humanoid, and a PrimaryPart
if character and character:FindFirstChildWhichIsA("Humanoid") and character.PrimaryPart then
local distance = (monster.PrimaryPart.Position - character.PrimaryPart.Position).Magnitude
if distance < 8 or (distance < MaxDist and PathHandler.IsInVision(monster, character)) then
PathHandler.FollowPlayer(monster, character)
end
end
end
end
end
function PathHandler.Start(monster,monsterCframe)
task.spawn(function()
PathHandler.OriginalPositions[monster] = monsterCframe
while monster:GetAttribute("IsReturning") == false do
PathHandler.CheckNearestPlayer(monster)
task.wait()
end
end)
end
return PathHandler
i ve updated the last three functions and yet the problem stiil occurs im so confused
and about i ve taken a look about the noobpath module but i don’t how to reverse engineer it (some stuff i don’t understand it )
You are still setting the attribute in the SAME thread as you are checking it. Make one function called PathHandler.SetReturnControl, one to handle just that. You moved it out but still have the same problem. Also I guess it is a little hard to understand some modules, but you don’t have to really reverse engineer it. Try looking at SimplePath as well.
Think about this, you are defining local IsReturning to the attribute, and you are ALSO checking that attribute to change it in the very next thread of if originalCFrame. Why not make one global function that just handles only setting that attribute, and removing it, or rather “updating” it.
Here is an example to stop the thread from even modifying the attribute in the returnhome function.
function PathHandler.ReturnHome(monster, originalCFrame)
if originalCFrame then
if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
monster:SetAttribute("IsReturning", false)
monster.Humanoid:MoveTo(originalCFrame.Position) --Move to the exact position to stop random movement.
return
end
local homeTarget = { PrimaryPart = { Position = originalCFrame.Position } }
PathHandler.FollowPlayer(monster, homeTarget)
end
end
Note: you still need to fix checknearestplayer function! To STOP changing the attribute.
i have a question when the npcs ( there are more than 1 ) return or genereally just move with the pathfinder that does computing every secondes does the new path overwrite the previous one and does the event movetofinished gets removed im so damned confused
It is suppose to yeild until movetofinished but the problem really comes in how you call it. Even if I try to yield a script if I keep calling the function, I create new threads. If I develop 10 threads, unwanted behavior starts happening. This is why you should always avoid while true do loops. This is why pathfinding modules make waypoints, and disconnect them then make new ones.
I will be honest, if you just youtube different pathfinding guides, you will understand a lot more than me trying to type out a full lesson. But I think you are on the right path! Good luck.