How can I make a follower AI not jittery

I can’t send a video for some reason. It says “failed to upload”.


Is it behaving like this? Because im also having the same issue

Not a bit like that. It doesn’t turn around or anything, it just kind of stops and then starts moving again.

The question is still open. I would appreciate any help.

whats your code? where is this being run on, server or client. its hard to figure out the problem without more detail

that stuffs PERSONAL!!1!1! i can’t show u anything >:c

The code is parented to the model and the script is this.

local Players = game:GetService("Players")
local Pathfinding = game:GetService("PathfindingService")

local model = script.Parent
local primPart = model.PrimaryPart
local humanoid = model:WaitForChild("Humanoid")
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None

for _, v:Instance in ipairs(model:GetDescendants()) do
	if v:IsA("BasePart") then
		v:SetNetworkOwner(nil)
	end
end

local lastPosition = Vector3.new(math.huge, math.huge, math.huge)
local isStuck = false
local function Follow(target:Model, distance:number)
	local root = target.PrimaryPart
	if root == nil then return end
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {model, target}
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	local rayResult = workspace:Raycast(primPart.Position, root.Position, raycastParams)
	--i had an idea of maybe checking if the path between player and npc is obstructed.
	--but, it ended up working incorrectly, serving no use.
	if true then --currently a placeholder
		local path:Path = Pathfinding:CreatePath({
			AgentCanClimb = true,
			Costs = {
				Climb = 2
			}
		})
		if path then
			local destination = root.Position
			local success, err = pcall(function()
				path:ComputeAsync(primPart.Position, root.Position)
			end)
			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				if #waypoints < 2 then return end

				if not isStuck then
					local startWP:PathWaypoint = waypoints[1]
					local waypointsToGoThrough = {waypoints[2]}
					for i, wp:PathWaypoint in ipairs(waypoints) do
						if i <= 2 then continue end
						if (wp.Position - startWP.Position).Magnitude < 1 then
							table.insert(waypointsToGoThrough, wp)
						else
							break
						end
					end
					for i, wp in ipairs(waypointsToGoThrough) do
						humanoid:MoveTo(wp.Position)
					end
				else
					for i, wp:PathWaypoint in ipairs(waypoints) do
						if i == 1 then continue end
						
						humanoid:MoveTo(wp.Position)
						local moved = false
						local conn = humanoid.MoveToFinished:Connect(function(reached)
							if reached then
								moved = true
							end
						end)
						local startedMoving = tick()
						repeat wait() until tick()-startedMoving >= 0.2 or moved
						if not moved then
							model:PivotTo(CFrame.new(wp.Position))
						end
						conn:Disconnect()
					end
					isStuck = false
				end

				print((lastPosition-primPart.Position).Magnitude, isStuck)
				if (lastPosition-primPart.Position).Magnitude < 0.1 then
					isStuck = true
				else
					isStuck = false
				end
				lastPosition = primPart.Position
			else
				warn(err)
			end
		end
	end
end

local function GetClosest(maxDist:number)
	local closest = nil
	local dist = maxDist
	for i, v in ipairs(workspace:GetChildren()) do
		if v:IsA("Model") then
			local rp:Part = v.PrimaryPart
			local plr = Players:GetPlayerFromCharacter(v)
			if rp and plr then
				local distance = (rp.Position - primPart.Position).Magnitude
				if distance < dist then
					dist = distance
					closest = v
				end
			end
		end
	end
	return closest, dist
end

while wait() do
	local targ:Model, dist:number = GetClosest(200)
	if dist > 20 then
		humanoid.WalkSpeed = 8
	else
		humanoid.WalkSpeed = 20
	end
	if targ then
		Follow(targ, dist)
	end
end

I had an idea of maybe trying to check if the target.PrimaryPart and primPart are obstructed using workspace:Raycast. It ended up making false detections, and being useless overall.

when you are not stuck you are looping through all of the waypoints and moving to each one without waiting
make sure to wait until move to is finished before moving to the next waypoint


If you will keep reading the code, you will actually see that it waits 0.3 secs after moving to each waypoint. In that case, I can check if the NPC has reached the waypoint and teleport it if not.

I want to stick with one of my ideas. I want to check if the path between target and model is obstructed, if not, I will use Pathfinding. Otherwise I will just move to the model.
But I can’t seem to figure out a way to check that. Raycasting failed greatly.

theres no wait(.3) in the script anywhere… but ok

could you send a video so i can understand more of your problem
use streamable (its a website) to send the video
(upload to streamable, copy link, paste)

@blorbee this is the waiting sequence. I will try sending video.

but that isnt the area i was talking about, im saying when you are NOT stuck

for i, wp in ipairs(waypointsToGoThrough) do
						humanoid:MoveTo(wp.Position)
					end

you are never waiting for the move to to be finished, so it justs keeps going through the loop changing the move to position until it hits the last one THEN it moves on with the rest of the function

1 Like

.MoveToFinished:Wait() makes it even worse. Trust me.

Try this tutorial. I’m not exactly sure why you chose to handle your pathfinding the way you did, but the method in this tutorial seems to be the straightforward way to go about it. There are also video examples showing that the movement is pretty smooth while using the MoveToFinished event.

My path is being updated constantly. This tutorial doesn’t seem to have anything about it.
Plus this was made in 2020 so the entire script is now using deprecated methods.
(my mistake, not 2020, 2022, yet still)

I just finished reading it, quite long. The part I’m referring to is under the “Bot AI Chasing” section. I suggest reading the whole thing but here’s the relevant bit:

Now that I’ve explained the reasons why, it’s time to create a new script. The flow of the script is simple. Everytime the while loop runs, it will call a function which tells the script to detect any closest player to the bot itself. If it managed to find one, we will tell the script to compute the path to it, and make our bot to only loop/walk through the second or third waypoint of the path before ending the function. Then this whole sequence would be repeated until our bot touches the player and makes the player die. Simple enough.

I personally haven’t really ran into an error where the AI chaser becomes jittery. In the zombie game I’m currently working on, the zombies may pause for a second when the player moves too fast, then just proceed as normal. It uses the same logic here (walk to the 2nd-3rd waypoint, then recalculate, repeat until all players dead). I’m not really sure what your case looks like, a video would be helpful.

Could you try to declare the Follow() function whenever the Closest Target moves? set a listener to the AssemblyLinearVelocity Property.

what

Can you explain better?

I’ve played around a bit, and I am satisfied with this script:

local Players = game:GetService("Players")
local Pathfinding = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")

local model = script.Parent
local primPart = model.PrimaryPart
local humanoid = model:WaitForChild("Humanoid")
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None

for _, v:Instance in ipairs(model:GetDescendants()) do
	if v:IsA("BasePart") then
		v:SetNetworkOwner(nil)
	end
end

local lastPosition = Vector3.new(math.huge, math.huge, math.huge)
local isStuck = false
local path:Path = Pathfinding:CreatePath({
	AgentCanClimb = true,
	Costs = {
		Climb = 2
	}
})
local obstructed = true
local function Follow(target:Model, distance:number)
	local root = target.PrimaryPart
	local hum:Humanoid = target:FindFirstChildWhichIsA("Humanoid")
	if root == nil or hum == nil then return end
	local destination = root.Position
	
	if not obstructed and not isStuck then
		humanoid:MoveTo(Vector3.new(destination.X, primPart.Position.Y, destination.Z))
	else
		local success, err = pcall(function()
			path:ComputeAsync(primPart.Position, destination)
		end)
		if success and path.Status == Enum.PathStatus.Success then
			local waypoints = path:GetWaypoints()
			if #waypoints < 2 then return end

			if not isStuck then
				local startWP:PathWaypoint = waypoints[1]
				local waypointsToGoThrough = {waypoints[2]}
				for i, wp:PathWaypoint in ipairs(waypoints) do
					if i <= 2 then continue end
					if (wp.Position - startWP.Position).Magnitude < 1 then
						table.insert(waypointsToGoThrough, wp)
					else
						break
					end
				end
				for i, wp in ipairs(waypointsToGoThrough) do
					humanoid:MoveTo(wp.Position)
				end
			else
				model:PivotTo(CFrame.new(waypoints[2].Position))
				isStuck = false
			end
		else
			if path.Status ~= Enum.PathStatus.NoPath then
				warn(`Error when trying to construct path between {model.Name} and {target.Name}:\n`..tostring(err))
			end
		end
	end
	
	--print((lastPosition-primPart.Position).Magnitude, isStuck)
	if (lastPosition-primPart.Position).Magnitude < 0.1 then
		isStuck = true
	else
		isStuck = false
	end
	lastPosition = primPart.Position
end

local function GetClosest(maxDist:number)
	local closest = nil
	local dist = maxDist
	for i, v in ipairs(workspace:GetChildren()) do
		if v:IsA("Model") then
			local rp:Part = v.PrimaryPart
			local plr = Players:GetPlayerFromCharacter(v)
			if rp and plr then
				local distance = (rp.Position - primPart.Position).Magnitude
				if distance < dist then
					dist = distance
					closest = v
				end
			end
		end
	end
	return closest, dist
end

RunService.Stepped:Connect(function(t, dt)
	local target:Model, dist = GetClosest(200)
	if target then
		local destination = target.PrimaryPart.Position
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {model, target}
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude
		local direction = destination - primPart.Position
		local rayResult = workspace:Raycast(primPart.Position, direction, raycastParams)
		if rayResult == nil then
			obstructed = false
		else
			obstructed = true
		end
	else
		obstructed = true
	end
end)

task.spawn(function()
	while wait() do
		local targ:Model, dist:number = GetClosest(200)
		if dist > 20 then
			humanoid.WalkSpeed = 8
		else
			humanoid.WalkSpeed = 16
		end
		if not obstructed then
			humanoid.WalkSpeed += 4
		end
		if targ then
			Follow(targ, dist)
			if dist < 3 then
				local hum:Humanoid = targ:FindFirstChildWhichIsA("Humanoid")
				if hum and not targ:FindFirstChildWhichIsA("ForceField") then
					hum:TakeDamage(30)
					task.spawn(function()
						if hum.Health <= 0 then return end
						local speedIncrease = math.floor((hum.MaxHealth - hum.Health)/4 + 5)
						hum.WalkSpeed += speedIncrease
						--print(speedIncrease)
						
						local forcefield = Instance.new("ForceField")
						forcefield.Visible = false
						forcefield.Parent = targ
						forcefield.Destroying:Once(function()
							hum.WalkSpeed -= speedIncrease
						end)
						
						Debris:AddItem(forcefield, 2.5)
					end)
				end
			end
		end
	end
end)

He is so OP now. I regret calling him annoying. I can’t run away from him now. May he forgive me.
(He can’t jump, but in my game you won’t be able to jump either, so it is not necessary. You can add jumping ability if you’d like to.)

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.