I’m trying to make an AI that will follow the player. One of the things I wanted to fix instead of just doing a constant humanoid:MoveTo() loop is that the AI will eventually run into walls. To fix this, I’m trying to make it dynamically switch from doing the constant humanoid:MoveTo() to pathfinding.
The biggest issue is that when the player is walking behind a wall, the AI will constantly be creating paths causing the AI to “stutter”. I need help to figure out how to fix this. What I tried to do was I tried making it so that when the AI creates a new path, it will continue walking on it’s old path but that didn’t work. Any help is appreachiated!
local character = script.Parent
local humanoid = character.Humanoid
local root = character.HumanoidRootPart
local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
raycastParameters.IgnoreWater = true
local pathfindingService = game:GetService("PathfindingService")
local agentParameters = {}
agentParameters.AgentRadius = 4
agentParameters.AgentHeight = 5
agentParameters.AgentCanJump = true
agentParameters.WaypointSpacing = 4
local path:Path = pathfindingService:CreatePath(agentParameters)
local function positionChanged(old:Vector3, current:Vector3)
local distance = (old - current).Magnitude
if distance > 1 then
print("Position Changed")
return true
end
return false
end
local function blocked(target:Vector3)
local origin = root.Position
local direction = target - origin
local raycastResult = workspace:Raycast(origin, direction, raycastParameters)
if raycastResult then
print("Blocked")
return true
end
return false
end
local function resetNetworkOwner()
for _, child:Instance in pairs(character:GetChildren()) do
if child:IsA("BasePart") and not child.Anchored then
child:SetNetworkOwner(nil)
end
end
end
local function getClosestPlayer()
local closestPlayer:Player, closestPlayerDistance:number
for _, player:Player in pairs(game.Players:GetPlayers()) do
local playerCharacter = player.Character
if playerCharacter then
local playerRoot:Part? = playerCharacter:FindFirstChild("HumanoidRootPart")
local playerHumanoid:Humanoid? = playerCharacter:FindFirstChild("Humanoid")
if playerRoot and playerHumanoid and playerHumanoid.Health > 0 then
local distance = (root.Position - playerRoot.Position).Magnitude
if not closestPlayer or closestPlayerDistance > distance then
closestPlayer = player
closestPlayerDistance = distance
end
end
end
end
return closestPlayer
end
local function stuck()
print("Checking if stuck")
raycastParameters.FilterDescendantsInstances = {character}
local raycastResult = workspace:Raycast(root.Position, root.CFrame.LookVector * 1000, raycastParameters)
if raycastResult then
print("Found something")
if (root.Position - raycastResult.Instance.Position).Magnitude <= 4 then
print("That something is in range, we're stuck")
return true
else
print("That something is outta range, we ain't stuck")
return false
end
end
print("Didn't find anything, we ain't stuck")
return false
end
local function makePart(position:Vector3)
local part = Instance.new("Part")
part.Name = "Waypoint"
part.Parent = workspace.Waypoints
part.Size = Vector3.new(0.3, 0.3, 0.3)
part.Position = position
part.Anchored = true
part.BrickColor = BrickColor.new("Deep orange")
part.Material = Enum.Material.Neon
part.Shape = Enum.PartType.Ball
end
local pathfindingTo = false
local function pathfindTo(goal:Vector3, part:Part)
pathfindingTo = true
workspace.Waypoints:ClearAllChildren()
path:ComputeAsync(root.Position, goal)
local waypoints = path:GetWaypoints()
for _, waypoint:PathWaypoint in pairs(waypoints) do
makePart(waypoint.Position)
end
for _, waypoint:PathWaypoint in pairs(waypoints) do
if (blocked(goal) or stuck()) and not positionChanged(goal, part.Position) then
resetNetworkOwner()
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
workspace.Waypoints:ClearAllChildren()
pathfindingTo = false
end
local lastTargetPosition
while true do
local target
repeat
target = getClosestPlayer()
wait()
until target
local targetRoot:Part = target.Character.HumanoidRootPart
raycastParameters.FilterDescendantsInstances = {character, target.Character}
if blocked(targetRoot.Position) then
if not pathfindingTo or (not lastTargetPosition or positionChanged(lastTargetPosition, targetRoot.Position)) then
pathfindTo(targetRoot.Position, targetRoot)
lastTargetPosition = targetRoot.Position
end
else
if not stuck() then
resetNetworkOwner()
humanoid:MoveTo(targetRoot.Position)
else
pathfindTo(targetRoot.Position, targetRoot)
lastTargetPosition = targetRoot.Position
end
end
wait()
end
Alright so, the issue still remains however it is a lot less noticeable.
The biggest thing is that while the player is moving behind a wall, the AI will go to pathfind. Since the player is moving, the AI will be constantly updating the path, and will stop moving while it is computing the path. What I’m thinking about doing is that when the path is being computed, the AI will move in the direction of the last waypoint.
Not really sure, but the script is a lot more optimized! Thanks!
local character = script.Parent
local humanoid = character.Humanoid
local root = character.HumanoidRootPart
local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
raycastParameters.IgnoreWater = true
local pathfindingService = game:GetService("PathfindingService")
local agentParameters = {}
agentParameters.AgentRadius = 4
agentParameters.AgentHeight = 5
agentParameters.AgentCanJump = true
agentParameters.WaypointSpacing = 4
local path:Path = pathfindingService:CreatePath(agentParameters)
local function positionChanged(old:Vector3, current:Vector3)
local distance = (old - current).Magnitude
if distance > 1 then
print("Position Changed")
return true
end
return false
end
local function blocked(target:Vector3)
local origin = root.Position
local direction = target - origin
local raycastResult = workspace:Raycast(origin, direction, raycastParameters)
if raycastResult then
warn("Blocked")
return true
end
return false
end
local function resetNetworkOwner()
for _, child:Instance in pairs(character:GetChildren()) do
if child:IsA("BasePart") and not child.Anchored then
child:SetNetworkOwner(nil)
end
end
end
local function getClosestPlayer()
local closestPlayer:Player, closestPlayerDistance:number
for _, player:Player in pairs(game.Players:GetPlayers()) do
local playerCharacter = player.Character
if playerCharacter then
local playerRoot:Part? = playerCharacter:FindFirstChild("HumanoidRootPart")
local playerHumanoid:Humanoid? = playerCharacter:FindFirstChild("Humanoid")
if playerRoot and playerHumanoid and playerHumanoid.Health > 0 then
local distance = (root.Position - playerRoot.Position).Magnitude
if not closestPlayer or closestPlayerDistance > distance then
closestPlayer = player
closestPlayerDistance = distance
end
end
end
end
return closestPlayer
end
local function stuck()
print("Checking if stuck")
raycastParameters.FilterDescendantsInstances = {character}
local raycastResult = workspace:Raycast(root.Position, root.CFrame.LookVector * 1000, raycastParameters)
if raycastResult then
print("Found something")
if (root.Position - raycastResult.Instance.Position).Magnitude <= 4 then
warn("That something is in range, we're stuck")
return true
else
print("That something is outta range, we ain't stuck")
return false
end
end
print("Didn't find anything, we ain't stuck")
return false
end
local pathfindingTo = false
local pathsCreated = 0
local pathStarted = false
local directFollowing = false
local function pathfindTo(goal:Vector3, part:Part)
pathfindingTo = true
pathStarted = false
pathsCreated += 1
local currentPathNumber = pathsCreated
path:ComputeAsync(root.Position, goal)
local waypoints = path:GetWaypoints()
pathStarted = true
for _, waypoint:PathWaypoint in pairs(waypoints) do
if (pathsCreated == currentPathNumber or not pathStarted) and not directFollowing then
resetNetworkOwner()
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
pathfindingTo = false
pathStarted = false
end
local lastTargetPosition
coroutine.wrap(function()
while true do
local target
repeat
target = getClosestPlayer()
task.wait(0.001)
until target
local targetRoot:Part = target.Character.HumanoidRootPart
raycastParameters.FilterDescendantsInstances = {character, target.Character}
if blocked(targetRoot.Position) then
directFollowing = false
if (not lastTargetPosition or positionChanged(lastTargetPosition, targetRoot.Position)) then
print("Starting new path")
spawn(function()
pathfindTo(targetRoot.Position, targetRoot)
end)
lastTargetPosition = targetRoot.Position
end
else
directFollowing = true
if not stuck() then
resetNetworkOwner()
humanoid:MoveTo(targetRoot.Position)
else
pathfindTo(targetRoot.Position, targetRoot)
lastTargetPosition = targetRoot.Position
end
end
task.wait(0.001)
end
end)()
Feel free to take and use this as you want, I don’t mind!