Faster NPC can't catch the player

Hello! I have a basic NPC system I got from a video that I am planning on reworking, simply put it goes to waypoints and if it spots the player it follows the player. The issue however, is that even if the NPC is faster than the player, it will never catch up to them.

https://i.gyazo.com/3c7fd395b6bf1348f259b2c458be6e3d.gif

I know the issue, it is that on the server the NPC is colliding with the player, so on the client it looks like it is lagging behind. I have dived through a bunch of threads and videos and I just can’t seem to find a solution, though I am quite new I wouldn’t be surprised if I am overlooking something.

Here is the code, any help would be very appreciated, thank you so much!


local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local hrp = npc:WaitForChild("HumanoidRootPart")
hrp:SetNetworkOwner(nil)

humanoid.WalkSpeed = 20

--local walkAnim = humanoid.Animator:LoadAnimation(script.Walk)
--local attackAnim = humanoid.Animator:LoadAnimation(script.Attack)

local pathParams = {
	AgentHeight = 5,
	AgentRadius = 3,
	AgentCanJump = true,
}

local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {npc}

local lastPos
local animPlaying = false

local RANGE = 60
local DAMAGE = 30

local function canSeeTarget(target)
	local orgin = hrp.Position
	local direction = (target.HumanoidRootPart.Position - hrp.Position).Unit * RANGE
	local ray = workspace:Raycast(orgin, direction, rayParams)

	if ray and ray.Instance then
		if ray.Instance:IsDescendantOf(target) then
			return true
		else
			return false
		end
	else
		return false
	end
end

local function findTarget()
	local players = game.Players:GetPlayers()
	local maxDistance = RANGE
	local nearestTarget

	for i, player in pairs(players) do
		if player.Character then
			local target = player.Character
			local distance = (hrp.Position - target.HumanoidRootPart.Position).Magnitude

			if distance < maxDistance and canSeeTarget(target) then
				nearestTarget = target
				maxDistance = distance
			end
		end
	end

	return nearestTarget
end

local function getPath(destination)
	local path = PathfindingService:CreatePath(pathParams)

	path:ComputeAsync(hrp.Position, destination.Position)

	return path	
end

local function attack(target)
	local distance = (hrp.Position - target.HumanoidRootPart.Position).Magnitude
	local debounce = false

	if distance > 1 then
		humanoid:MoveTo(target.HumanoidRootPart.Position)
	else
		if debounce == false then
			debounce = true

			--npc.Head.AttackSound:Play()
			--attackAnim:Play()
			target.Humanoid.Health -= DAMAGE
			task.wait(0.5)
			debounce = false
		end
	end
end

local function walkTo(destination)
	local path = getPath(destination)

	if path.Status == Enum.PathStatus.Success then
		for i, waypoint in pairs(path:GetWaypoints()) do
			path.Blocked:Connect(function()
				path:Destroy()
			end)

			if animPlaying == false then
				--walkAnim:Play()
				animPlaying = true
			end

			--attackAnim:Stop()

			local target = findTarget()

			if target and target.Humanoid.Health > 0 then
				lastPos = target.HumanoidRootPart.Position
				attack(target)
				break
			else
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
				end

				if lastPos then
					humanoid:MoveTo(lastPos)
					humanoid.MoveToFinished:Wait()
					lastPos = nil
					break
				else
					humanoid:MoveTo(waypoint.Position)
					humanoid.MoveToFinished:Wait()
				end
			end
		end
	else
		return
	end
end

local function patrol()
	local waypoints = workspace.Waypoints:GetChildren()
	local randomNum = math.random(1, #waypoints)
	walkTo(waypoints[randomNum])
end

while task.wait(0.2) do
	patrol()
	hrp:SetNetworkOwner(nil)
end

Try disabling collisions between the npc and the player, see if thatll work

(Reposted, spelling mistake oops)

Tried with this script using “NPC” as the collision group and setting it to not collide with default, still no luck though D: (They just disappear now? Which I feel like I am missing something, I’m gonna look into collision groups a touch more)

local PhysicsService = game:GetService("PhysicsService")

for _, part in pairs(npc:GetDescendants()) do
	if part:IsA("BasePart") then
		part.CollisionGroup = "NPC"
	end
end

if you make it not collide with default then it probably falls through the map assuming the map’s parts collision groups are default lol

1 Like

Yea I just realized that after I posted it and fixed that up real fast haha! Tt still is unfortunately colliding with the player? Or at the very least lagging behind.

Here is what it is set to for hopefully more context!!

Along with disabling collision with the player, you could try making the NPC try to get to a spot slightly in front of the player (or the player’s position if they aren’t moving).

I don’t know much about network ownership but have a try with setting network ownership of the NPC to the player it’s currently targetting. If I’m right it should only collide when it collides on the player’s screen.

1 Like

I tried setting it up in this portion of the script, so whomever the target is they would have network ownership:

local function canSeeTarget(target)
	script.Parent.HumanoidRootPart:SetNetworkOwner(target)
--continue
end

But no luck once again D:
I ended up getting a gif of both the server-sided view and client-sided view, which are here:
Server side:
Image from Gyazo

Client side:
Image from Gyazo

Still no luck it seems to be colliding with the player, which I wonder if I set up collision groups wrong?

It is lagging behind because of the delay of computing a new path everytime the player moves, and also because it is trying to be right on the player.

You are better off to calculate a few studs ahead of the players direction while they are moving.

2 Likes

Exactly. You always have to calculate speed based upon how fast it is actually moving. Not based upon the value of properties. This is an important concept to understand.

2 Likes

Alright, thank you both! I’ll look into it :D!

1 Like

I recommend testing pathfinding on the client side. You will see how good it actually is. There are plenty of games that calculate movement on the client using fireallclients. It just requires some learning. Movement is visual so as long as the rest of your logic is server sided you should be good. Tower defense games for example they visualize the movement and animations using fireallclients but the tower itself cannot be manipulated because the rest of the logic is server sided.

1 Like

Try Use Raycast x Velocity Check x Ping FPS Check