How do I fix my NPC's choppy movement when following the player?

Edited because I fixed some issues and have better wording.

I have this character that chases and kills the player. However, it has choppy movement when chasing, so the player can easily outrun it. How do I fix it?
(Headphones warning)

I do NOT want the player to outrun this mob. If the player is found, they will be caught.

local char = script.Parent
local humanoid = char.Humanoid
char.PrimaryPart:SetNetworkOwner(nil)
local pathFinder = game:GetService("PathfindingService")
local destinations = workspace.WorkerNightDestinations:GetChildren()
local always = true
local walkinganimation = script:WaitForChild("Walking")
local WalkAnim = humanoid:LoadAnimation(walkinganimation)
local chasingplayer = false
local TS = game:GetService("TweenService")

local clicking = char.HumanoidRootPart.Sound
local scream = char.HumanoidRootPart.Scream
clicking:Play()

WalkAnim:Play()

local fadeout = TS:Create(scream, TweenInfo.new(1.0, Enum.EasingStyle.Linear), {Volume = 0})

local function findNearestPlayer()
	local nearestPlayer = nil
	local shortestDistance = 200

	for _, thing in pairs(workspace:GetChildren()) do
		if thing:IsA("Model") and thing ~= char and thing.IsPatient.Value == true and thing.Humanoid.Health ~= 0 and thing.BreakingRule.Value == true then
			local hrp = thing.HumanoidRootPart
			local distance = (char.HumanoidRootPart.Position - hrp.Position).Magnitude
			if distance <= shortestDistance then
				nearestPlayer = thing
				shortestDistance = distance
			end
		end
	end

	return nearestPlayer
end

humanoid.Touched:Connect(function(part)
	if part.Parent:FindFirstChild("Humanoid") and part.Parent.Name ~= "Worker" and part.Parent.IsPatient.Value == true then
		part.Parent.Humanoid.Health = 0
	end
end)

-- MAIN LOOP
while always do
	--if chasingplayer == false then
	--	WalkAnim:Stop()
	--end

	local target = findNearestPlayer()

	if not target then
		target = destinations[math.random(1, #destinations)]
	end

	--if chasingplayer == false then
	--	WalkAnim:Play()
	--end

	local path = pathFinder:CreatePath()

	if target:IsA("Model") and target.Humanoid.Health ~= 0 then
		path:ComputeAsync(char.HumanoidRootPart.Position, target.HumanoidRootPart.Position)
		if chasingplayer == false then
			scream.Volume = 7.797
			scream:Play()
		end
		chasingplayer = true
	else
		path:ComputeAsync(char.HumanoidRootPart.Position, target.Position)
		if chasingplayer == true then
			fadeout:Play()
			chasingplayer = false
		end
	end

	for _, wayPoint in pairs(path:GetWaypoints()) do
		char.Humanoid:MoveTo(wayPoint.Position)
		if wayPoint.Action == Enum.PathWaypointAction.Jump then
			char.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		end
		char.Humanoid.MoveToFinished:Wait()

		if target:IsA("Model") and target ~= char and target.IsPatient.Value == true and (char.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude <= 2 then
			print("Player found.")
			char.Humanoid:MoveTo(target.HumanoidRootPart.Position)
			char.Humanoid.MoveToFinished:Wait()
			chasingplayer = false
			break
		end
	end
end

I’m new to coding, so sorry if my code makes no sense or is hard to read.

2 Likes

Poke!!!

Bro i’m still having this issue, pls help

If you are truly new to coding, I am proud of you, because it’s a lot nicer than some of what I see on here. Anyway, I think the main issue you are having is complicated. path:ComputeAsync() takes time (emphasis on Async). Whenever the NPC is recalculating the path, it is staying still, allowing the character to get away, such that where it is calculating a path to is no longer relevant, AND it is staying still. Does this make sense?

You can get around this with a more complicated AI. Have multiple while loops, one of them always moving to waypoints, the other one deciding when to recalculate and reset the waypoints. You can use task.spawn() to run multiple while loops at once (in a general sense, they don’t actually run at the same time).

Though, an easier solution that might help is calculating to where the character will be. Perhaps by adding character.PrimaryPart.AssemblyLinearVelocity or maybe there is a more accurate property of Humanoid. You could also move to the next waypoint WHILE recalculating the path (move the MoveTo to be right before the break. Things like this might help, but this is hard to solve.

I should’ve specified that I am new to complicated Roblox scripting, not coding in general, my mistake ^^’ I have been doing other types of coding for years, and I have worked on a Roblox game before (but nothing as complicated as this current project). (My script looks nice because it is a Frankenstein of other tutorials I’ve seen) But thanks anyway!!

I’ll try out your solutions and get back to you! thanks for helping out!

I’m gonna be real with you chief I have no idea what im doing

local char = script.Parent
local humanoid = char.Humanoid
char.PrimaryPart:SetNetworkOwner(nil)
local pathFinder = game:GetService("PathfindingService")
local destinations = workspace.WorkerNightDestinations:GetChildren()
local always = true
local walkinganimation = script:WaitForChild("Walking")
local WalkAnim = humanoid:LoadAnimation(walkinganimation)
local chasingplayer = false
local TS = game:GetService("TweenService")
local currenttarget = nil

--local RepS = game:GetService("ReplicatedStorage")
--local Players = game:GetService("Players")
--local LE = RepS:WaitForChild("LightEvent")
--local SLE = RepS:WaitForChild("StopLightEvent")

local clicking = char.HumanoidRootPart.Sound
local scream = char.HumanoidRootPart.Scream
clicking:Play()

WalkAnim:Play()

local fadeout = TS:Create(scream, TweenInfo.new(1.0, Enum.EasingStyle.Linear), {Volume = 0})

local function findNearestPlayer()
	local nearestPlayer = nil
	local shortestDistance = 200

	for _, thing in pairs(workspace:GetChildren()) do
		if thing:IsA("Model") and thing ~= char and thing:FindFirstChild("Humanoid") and thing.IsPatient.Value == true and thing.Humanoid.Health ~= 0 and thing.BreakingRule.Value == true then
			thing.BeingChased.Value = true
			currenttarget = thing
			local hrp = thing.HumanoidRootPart
			local distance = (char.HumanoidRootPart.Position - hrp.Position).Magnitude
			if distance <= shortestDistance then
				nearestPlayer = thing
				shortestDistance = distance
			end
		end
	end

	return nearestPlayer
end

humanoid.Touched:Connect(function(part)
	if part.Parent:FindFirstChild("Humanoid") and part.Parent.Name ~= "Worker" and part.Parent.IsPatient.Value == true then
		part.Parent.Humanoid.Health = 0
	end
end)

local target = nil

local path = nil

task.spawn(function()
	while always do
		--if chasingplayer == false then
		--	WalkAnim:Stop()
		--end

		target = findNearestPlayer()

		if not target then
			target = destinations[math.random(1, #destinations)]
		end

		--if chasingplayer == false then
		--	WalkAnim:Play()
		--end

		path = pathFinder:CreatePath()

		if target:IsA("Model") and target.Humanoid.Health ~= 0 then
			path:ComputeAsync(char.HumanoidRootPart.Position, target.HumanoidRootPart.Position)
			if chasingplayer == false then
				scream.Volume = 7.797
				scream:Play()
			end
			--local hitPlayer = Players:GetPlayerFromCharacter(target)
			--LE:FireClient(hitPlayer)
			chasingplayer = true
		else
			path:ComputeAsync(char.HumanoidRootPart.Position, target.Position)
			if chasingplayer == true then
				fadeout:Play()
				chasingplayer = false
				if currenttarget ~= nil then
					currenttarget.BeingChased.Value = false
					--local hitPlayer = Players:GetPlayerFromCharacter(currenttarget)
					--SLE:FireClient(hitPlayer)
					currenttarget = nil
				end
			end
		end
		wait(0.1)
	end
end)
task.spawn(function()
	while always do
		wait(0.1)
		for _, wayPoint in pairs(path:GetWaypoints()) do
			char.Humanoid:MoveTo(wayPoint.Position)
			if wayPoint.Action == Enum.PathWaypointAction.Jump then
				char.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end

			if target:IsA("Model") and target ~= char and target.IsPatient.Value == true and (char.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude <= 2 then
				print("Player found.")
				chasingplayer = false
				target.BeingChased.Value = false
				char.Humanoid:MoveTo(target.HumanoidRootPart.Position)
				break
			end
		end
	end
end
)

I tried doing loops at the same time like you suggested, but when the game runs, the script throws a time out error, forcing me to add wait(0.1) to both loops to get it to work. Obviously, adding wait(0.1) to the second loop wouldn’t help the freezing issue.
I also dont know what **character.PrimaryPart.AssemblyLinearVelocity ** is and don’t know how to incorporate it, char.PrimaryPart:SetNetworkOwner(nil) is only in there because it was in the tutorial i read to create a wandering ai