I made a pathfinding system. However, since computing path takes time, it is possible to see the AI stop every time it reaches the end of its waypoint to compute a new path before moving to a new target again. I tried making it compute path in advance (specifically, compute path at the 2nd waypoint and break the loop/run that new path at the 3rd waypoint), but all that does is make the AI stutters even more when I thought it is supposed to have already computed a new path.
The actual AI logic is in activateAI function, always found at the end of the script.
Original AI script that does not update path til it reached destination
local players = game:GetService('Players')
local runService = game:GetService('RunService')
local pathfindingService = game:GetService('PathfindingService')
local npc = script.Parent
local humanoid = npc.Humanoid
local rootPart = npc.HumanoidRootPart
rootPart:SetNetworkOwner(nil)
local function addPartsFromParentToTable(parent, toTable)
for _, child in pairs(parent:GetChildren()) do
if child:IsA('BasePart') then
table.insert(toTable, child)
end
end
end
local function computePath(path, origin, destination)
local success, err
local retries = 0
repeat
retries += 1
success, err = pcall(function()
path:ComputeAsync(origin, destination)
end)
until success and path.Status == Enum.PathStatus.Success or retries == 3
end
local function createPath(origin, destination)
local path = pathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = false,
Costs = {
Snow = math.huge,
Metal = math.huge,
},
})
computePath(path, origin, destination)
return path
end
local function moveToDestination(path)
for _, waypoint in pairs(path:GetWaypoints()) do
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
local function checkProximity(targetRoot)
if (targetRoot.Position - rootPart.Position).Magnitude < 10 then
local targetCharacter = targetRoot.Parent
local excludeParts = {}
addPartsFromParentToTable(targetCharacter, excludeParts)
addPartsFromParentToTable(npc, excludeParts)
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = excludeParts
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local rayDirection = targetRoot.Position - rootPart.Position
local result = workspace:Raycast(rootPart.Position, rayDirection, rayParams)
if not result then
targetRoot.Parent.Humanoid:TakeDamage(100)
return true
end
end
end
local function findNearestPlayer()
local nearestDistance, nearestPlayer
for _, player in pairs(players:GetPlayers()) do
if player.Character then
local char = player.Character
if char:FindFirstChild('HumanoidRootPart') and char.Humanoid.Health > 0 then
local charPos = char.HumanoidRootPart.Position
if not nearestDistance or (charPos - rootPart.Position).Magnitude < nearestDistance then
nearestDistance = (charPos - rootPart.Position).Magnitude
nearestPlayer = player
end
end
end
end
return nearestPlayer
end
for _, bodyPart in pairs(npc:GetChildren()) do
if bodyPart:IsA('BasePart') then
bodyPart.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild('Humanoid') then
hit.Parent.Humanoid:TakeDamage(100)
end
end)
end
end
local targetKilled
local function activateAI()
while true do
task.wait()
local player = findNearestPlayer()
if player then
local character = player.Character
local playerRoot = character.HumanoidRootPart
local origin = rootPart.Position
local destination = playerRoot.Position + playerRoot.Velocity
local path = createPath(origin, destination)
local heartbeatConnection
heartbeatConnection = runService.Heartbeat:Connect(function()
local result = checkProximity(playerRoot)
if result then
humanoid:MoveTo(rootPart.Position)
targetKilled = true
heartbeatConnection:Disconnect()
end
end)
for _, waypoint in pairs(path:GetWaypoints()) do
if targetKilled then
targetKilled = nil
break
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
heartbeatConnection:Disconnect()
end
end
end
activateAI()
Here is the version that I tried to make the AI update its waypoints constantly
local players = game:GetService('Players')
local runService = game:GetService('RunService')
local pathfindingService = game:GetService('PathfindingService')
local npc = script.Parent
local humanoid = npc.Humanoid
local rootPart = npc.HumanoidRootPart
rootPart:SetNetworkOwner(nil)
local function addPartFromParentToTable(parent, toTable)
for _, child in pairs(parent:GetChildren()) do
if child:IsA('BasePart') then
table.insert(toTable, child)
end
end
end
local function computeNewPath(path, origin, destination)
local success, err
local count = 0
repeat
count += 1
success, err = pcall(function()
path:ComputeAsync(origin, destination)
end)
until success and path.Status == Enum.PathStatus.Success or count == 3
end
local function createNewPath(origin, destination)
local path = pathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = false,
Costs = {
Snow = math.huge,
Metal = math.huge,
},
})
computeNewPath(path, origin, destination)
return path
end
local function moveTowardsDestination(path)
for _, waypoint in path:GetWaypoints() do
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
local function checkProximity(targetRoot)
if (targetRoot.Position - rootPart.Position).Magnitude < 10 then
local targetCharacter = targetRoot.Parent
local bodiesExempt = {}
addPartFromParentToTable(targetCharacter, bodiesExempt)
addPartFromParentToTable(npc, bodiesExempt)
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = bodiesExempt
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local rayDirection = targetRoot.Position - rootPart.Position
local result = workspace:Raycast(rootPart.Position, rayDirection, rayParams)
if not result then
targetRoot.Parent.Humanoid:TakeDamage(100)
return true
end
end
end
local function findNearestPlayer()
local nearestDistance, nearestTarget
for _, player in pairs(players:GetPlayers()) do
if player.Character then
local character = player.Character
if character:FindFirstChild('HumanoidRootPart') and character.Humanoid.Health ~= 0 then
local charPos = character.HumanoidRootPart.Position
if nearestDistance == nil or (charPos - rootPart.Position).Magnitude < nearestDistance then
nearestDistance = (charPos - rootPart.Position).Magnitude
nearestTarget = player
end
end
end
end
return nearestTarget
end
for _, bodyPart in pairs(npc:GetChildren()) do
if bodyPart:IsA('BasePart') then
bodyPart.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild('Humanoid') then
hit.Parent.Humanoid:TakeDamage(100)
end
end)
end
end
local targetKilled
local newWaypoints
local autoPlayer
local function activateAI()
while true do
task.wait()
local player = findNearestPlayer()
if newWaypoints and autoPlayer == player then
print('Planned')
local character = player.Character
local playerRoot = character.HumanoidRootPart
local runInstance
runInstance = runService.Heartbeat:Connect(function()
local result = checkProximity(playerRoot)
if result then
humanoid:MoveTo(rootPart.Position)
targetKilled = true
runInstance:Disconnect()
end
end)
local allWaypoints = newWaypoints:GetWaypoints()
newWaypoints, autoPlayer = nil, nil
for i, waypoint in pairs(allWaypoints) do
if targetKilled or player ~= findNearestPlayer() then
targetKilled = nil
break
elseif i == 2 then
if player == findNearestPlayer() then
autoPlayer = player
task.spawn(function() newWaypoints = createNewPath(allWaypoints[3], playerRoot.Position + playerRoot.Velocity) end)
end
elseif i == 3 and newWaypoints then
break
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
runInstance:Disconnect()
elseif player then
print('Created new')
local character = player.Character
local playerRoot = character.HumanoidRootPart
local origin = rootPart.Position
local destination = playerRoot.Position + playerRoot.Velocity
local newPath = createNewPath(origin, destination)
local runInstance
runInstance = runService.Heartbeat:Connect(function()
local result = checkProximity(playerRoot)
if result then
humanoid:MoveTo(rootPart.Position)
targetKilled = true
runInstance:Disconnect()
end
end)
local allWaypoints = newPath:GetWaypoints()
newWaypoints, autoPlayer = nil, nil
for i, waypoint in pairs(allWaypoints) do
if targetKilled or player ~= findNearestPlayer() then
targetKilled = nil
break
elseif i == 2 then
if player == findNearestPlayer() then
autoPlayer = player
task.spawn(function() newWaypoints = createNewPath(allWaypoints[3], playerRoot.Position + playerRoot.Velocity) end)
end
elseif i == 3 and newWaypoints then
break
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
runInstance:Disconnect()
end
end
end
activateAI()