I have a problem where when the AI gets close enough to the player, it starts to stutter and never reaches the player. My only guess as to why this happens is due to the parameter requirements required to restart the function (thus the loop) don’t meet for one reason or another, ending the loop. And so, the only way for the AI to work again is if I force restart it, which is why it stutters for a bit.
Here is the code (Minus a few unrelated functions):
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local path = PathfindingService:CreatePath()
local dummyCharacter = script.Parent
local humanoid = dummyCharacter:WaitForChild("Humanoid")
local humRootPart = dummyCharacter.HumanoidRootPart
local waypoints
local blockedConnection
local finishedConnection
local objDistance
local currentWaypointIndex = 0
local function followPath(object)
-- Compute the path
local success, errorMessage = pcall(function()
path:ComputeAsync(dummyCharacter.PrimaryPart.Position, object.HumanoidRootPart.Position)
end)
if success and path.Status == Enum.PathStatus.Success then
-- Get the path waypoints
waypoints = path:GetWaypoints()
local objectHumRoot = object.HumanoidRootPart
local objectHum = object.Humanoid
objDistance = (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude
-- Detect if path becomes blocked
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
if blockedWaypointIndex >= currentWaypointIndex + 1 then
blockedConnection:Disconnect()
followPath(object)
end
end)
-- Runs for every waypoint, telling dummy to move to them
for index, waypoint in pairs(waypoints) do
-- Skips the starting waypoint
if index ~= 1 then
-- Detect if dummy has reached target
if (objectHumRoot.Position - humRootPart.Position).Magnitude <= 2 then
attackPlayer(objectHum)
followPath(object)
return
end
--Move to the next waypoint
currentWaypointIndex = index
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
-- If dummy has lost track of target
if (humRootPart.Position - objectHumRoot.Position).Magnitude > maxDistance or objectHum.Health <= 0 then
-- Check if there's a closer target nearby
if findTarget() then
-- Follow target
followPath(findTarget())
return
else
-- Wander until a target is found
repeat
wanderAround()
until findTarget()
-- Follow target
followPath(findTarget())
return
end
end
-- Detect if target changes position
if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (objectHumRoot.Position - humRootPart.Position).Magnitude > 3 then
followPath(object)
return
end
end
end
print("Loop has finished running (Shouldn't ever happen)")
followPath(object) -- I force restart it
return
else
warn("Path not computed!", errorMessage)
if findTarget() then
-- Follow target
followPath(findTarget())
return
else
-- Wander until a target is found
repeat
wanderAround()
until findTarget()
-- Follow target
followPath(findTarget())
return
end
end
end
repeat
wanderAround()
until findTarget()
followPath(findTarget())
Here is the video showcasing it:
If anyone can inform me of how I can solve the stuttering (or prevent the loop from exiting), it will be very appreciated.
I assume that the lag here is actually the AI getting to the ‘last known position’ of your character according to the server. The server has a slight lag in data regarding where you’re moving your character.
You could use some delta math to project where the player is in real-time and then make the AI chase that projected position rather than the current position.
No, it’s for the pathfinding AI. The AI is programmed by the server right? So it makes sense to set the ownership to the server. I’m not sure what you mean by dodgy UX but it has fixed stuttering for me in my own pathfinding AI. In fact, the SimplePath module does the HumanoidRootPart ownership code line.
When the server has ownership of your character, you get input lag which is less than ideal for gameplay. I have quickly composed a comparison on what setting the NetworkOwnership to server vs client looks like for UX.
What you can do, however, is set the AI’s body network ownership to the server. As you mentioned, the AI is programmed on the server. It only makes sense for its body to be owned by the server, too.
Yes, that’s my point. Don’t change your own character’s network ownership. It happens to be for some reason that the server doesn’t give the humanoid NPC server network ownership.
In which case I fully agree with your advise. I misunderstood your original post, apologies. Setting the network ownership to the server will indeed vastly improve AI behaviour.
I think I found the real problem here, for anyone who still cares.
The AI stutters because of the force restart caused by the loop ending. The target reaches the player early which causes the force restart when near the player. The target reaches early because the conditions for the path to recompute to the actual player are not met, basically saying that the player hasn’t moved far away enough for the pathfinding to want to recompute. I wanted to set this bar lower but it just causes the pathfinding to be stuck eternally calculating because the player keeps moving.
If there’s any solution to that, it’d be appreciated.
I just needed to make it so that instead of having it all in the for loop, I enact it so that exiting the loop means that they’ve reached it.
local function followPath(object)
-- Compute the path
local success, errorMessage = pcall(function()
path:ComputeAsync(dummyCharacter.PrimaryPart.Position, object.HumanoidRootPart.Position)
end)
if success and path.Status == Enum.PathStatus.Success then
-- Get the path waypoints
waypoints = path:GetWaypoints()
local objectHumRoot = object.HumanoidRootPart
local objectHum = object.Humanoid
objDistance = (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude
-- Detect if path becomes blocked
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
if blockedWaypointIndex >= currentWaypointIndex + 1 then
blockedConnection:Disconnect()
followPath(object)
end
end)
-- Runs for every waypoint, telling dummy to move to them
table.remove(waypoints, 1)
for index, waypoint in pairs(waypoints) do
--Move to the next waypoint
currentWaypointIndex = index
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
-- If dummy has lost track of target
if (humRootPart.Position - objectHumRoot.Position).Magnitude > maxDistance or objectHum.Health <= 0 then
-- Check if there's a closer target nearby
if findTarget() then
-- Follow target
followPath(findTarget())
return
else
-- Wander until a target is found
repeat
wanderAround()
until findTarget()
-- Follow target
followPath(findTarget())
return
end
end
-- Detect if target changes position
if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (humRootPart.Position - objectHumRoot.Position).Magnitude >= 4 then
followPath(object)
return
end
end
attackPlayer(objectHum)
-- After dummy reaches target, wait for target to start moving again
while wait() do
if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (humRootPart.Position - objectHumRoot.Position).Magnitude >= 4 then
followPath(object)
return
else
attackPlayer(objectHum)
end
end
else
warn("Path not computed!", errorMessage)
if findTarget() then
-- Follow target
followPath(findTarget())
return
else
-- Wander until a target is found
repeat
wanderAround()
until findTarget()
-- Follow target
followPath(findTarget())
return
end
end
end