NPC Killer lags/stutters behind player despite having faster walkspeed

I have an NPC Killer in my game, and for some reason, even though it has 3x faster walkspeed than the player, seems to sometimes lag behind the player and not catch up, and only catches up once the player stops walking.


Here is the script im using:

local myHuman = script.Parent:WaitForChild("Humanoid")
local myRoot = script.Parent:WaitForChild("HumanoidRootPart")
local head = script.Parent:WaitForChild("Head")
local lowerTorso = script.Parent:WaitForChild("LowerTorso")

local clone = script.Parent:Clone()

script.Parent.PrimaryPart:SetNetworkOwner(nil)

function walkRandomly()
	local xRand = math.random(-50,50)
	local zRand = math.random(-50,50)
	local goal = myRoot.Position + Vector3.new(xRand,0,zRand)
	
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(myRoot.Position, goal)
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				myHuman.Jump = true
			end
			myHuman:MoveTo(waypoint.Position)
			local timeOut = myHuman.MoveToFinished:Wait(1)
			if not timeOut then
				--print("Got stuck")
				myHuman.Jump = true
				walkRandomly()
			end
		end
	else
		--print("Path failed")
		wait(1)
		walkRandomly()
	end
end

function findPath(target)
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(myRoot.Position,target.Position)
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				myHuman.Jump = true
			end
			myHuman:MoveTo(waypoint.Position)
			local timeOut = myHuman.MoveToFinished:Wait(1)
			if not timeOut then
				myHuman.Jump = true
				--print("Path too long!")
				findPath(target)
				break
			end
			if checkSight(target) then
				repeat
					--print("Moving directly to the target")
					myHuman:MoveTo(target.Position)
					attack(target)
					wait(0.1)
					if target == nil then
						break
					elseif target.Parent == nil then
						break
					end
				until checkSight(target) == false or myHuman.Health < 1 or target.Parent.Humanoid.Health < 1
				break
			end
			if (myRoot.Position - waypoints[1].Position).magnitude > 20 then
				--print("Target has moved, generating new path")
				findPath(target)
				break
			end
		end
	end
end

function checkSight(target)
	local ray = Ray.new(myRoot.Position, (target.Position - myRoot.Position).Unit * 40)
	local hit,position = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})
	if hit then
		if hit:IsDescendantOf(target.Parent) and math.abs(hit.Position.Y - myRoot.Position.Y) < 3 then
			--print("I can see the target")
			return true
		end
	end
	return false
end

function findTarget()
	local dist = 50000
	local target = nil
	local potentialTargets = {}
	local seeTargets = {}
	for i,v in ipairs(workspace:GetChildren()) do
		local human = v:FindFirstChild("Humanoid")
		local torso = v:FindFirstChild("Torso") or v:FindFirstChild("HumanoidRootPart")
		if human and torso and v.Name ~= script.Parent.Name then
			if (myRoot.Position - torso.Position).magnitude < dist and human.Health > 0 then
				table.insert(potentialTargets,torso)
			end
		end
	end
	if #potentialTargets > 0 then
		for i,v in ipairs(potentialTargets) do
			if checkSight(v) then
				table.insert(seeTargets, v)
			elseif #seeTargets == 0 and (myRoot.Position - v.Position).magnitude < dist then
				target = v
				dist = (myRoot.Position - v.Position).magnitude
			end
		end
	end
	if #seeTargets > 0 then
		dist = 200
		for i,v in ipairs(seeTargets) do
			if (myRoot.Position - v.Position).magnitude < dist then
				target = v
				dist = (myRoot.Position - v.Position).magnitude
			end
		end
	end
	return target
end

function attack(target)
		if target.Parent ~= nil then
			target.Parent.Humanoid:TakeDamage(100)
		end
		wait(0.4)
	end

function died()
	wait(5)
	clone.Parent = workspace
	game:GetService("Debris"):AddItem(script.Parent,0.1)
end

myHuman.Died:Connect(died)

lowerTorso.Touched:Connect(function(obj)
	if not obj.Parent:FindFirstChild("Humanoid") then
		myHuman.Jump = true
	end
end)

function main()
	local target = findTarget()
	if target then
		myHuman.WalkSpeed = 75
		findPath(target)
	else
		myHuman.WalkSpeed = 20
		walkRandomly()
	end
end

while wait(0.1) do
	if myHuman.Health < 1 then
		break
	end
	main()
end

Also, this might not affect anything, but the NPC is being cloned from ReplicatedStorage to workspace.

3 Likes

Hello still need help with this.

Can you tell me what happened? I thought it was fixed?

1 Like

I’m not sure, it was working for a while and then it just started doing it again

Is it the clone that is shuttering?

1 Like

Well, it’s being cloned by another server script (Cloned from ReplicatedStorage), so yes.

It’s being cloned when it dies?

function died()
	wait(5)
	clone.Parent = workspace
	game:GetService("Debris"):AddItem(script.Parent,0.1)
end

I believe so, but if it’s stored in the ReplicatedStorage before the game is started, it shouldn’t die right? (It’s relative location in the workspace is over the void, but I believe models dont have physics while in ReplicatedStorage)

Well, try adding this section of code. It will respawn the character.

while true do
	character = script.Parent
	clone = character:Clone()
	character.Humanoid.Died:Wait()
	task.wait(5) --Respawn time
	clone.Parent = game.Workspace
	script.Parent = clone
	character:Destroy()
end
1 Like

I added this and now the NPC wont move at all

I’m confused what changes you added that caused the shuttering again

All I did was change the size of the NPC (made him bigger) but I used the proper way, (I didn’t just use the resize tool I used the proper humanoid dimensions)

Did you adjust the hipheight of the humanoid? Humanoid.HipHeight
image
Cause when you changed the height, the HipHeight changed as well

1 Like

Uhh I didn’t change it directly, but it is currently set at 4.623, it may be due to me resizing it maybe?

You should set it at it’s approximate HipHeight. The HeightHip is basically how tall a leg is in studs

2 Likes

This happened to me as well when the steps were too small. Since your character is big, try to have small steps, like 3 or 4 studs each, or even bigger.

When creating the path you should give another argument to specify the steps.

example:

local path = PathfindingService:CreatePath({
	AgentRadius = 3,
	AgentHeight = 6,
	AgentCanJump = true
})

Learn more here: Character Pathfinding

Also you can jump over some waypoints if they are at the start, to reduce the stuttering.

2 Likes

Yeah, you should set a dictionary like what @Bloxxy213_DVL said. Here is a code sample

local pathParams = {
	["AgentRadius"] = 3, --Half it's width
	["AgentHeight"] = 6, --The Height of the npc
	["AgentCanJump"] = true --If it can jump
}

local path = PathfindingService:CreatePath(pathParams) --This makes it so that the path will apply to these parameters
3 Likes

Bumping this cause I haven’t yet found a solution

This is happening because that’s where the server thinks the players character is

The client sends the position of there character over the network to the server but this data takes some time to get to the server

So by the time the server gets the players character position it’s out of date and a little behind the character

To fix this problem would be to

Give the client network ownership of the enemy and make the client move the enemy in a localscript


Another way to fix the problem would be to use

https://developer.roblox.com/en-us/api-reference/function/Player/GetNetworkPing

And

https://developer.roblox.com/en-us/api-reference/property/BasePart/AssemblyLinearVelocity

To try to workout the players offset

-- Get the players ping
local ping = player:GetNetworkPing()

-- divide the ping in half because we only want the time it takes from client to server not client to server and back to client
ping /= 2

-- workout how much the player might of moved with this much ping
local offset = character.PrimaryPart.AssemblyLinearVelocity * ping

-- the position the player would be in if they continue with the same velocity
local estimatedPosition = character.PrimaryPart.Position + offset


Option 1 will be 100% accurate

Option 2 will try to guess the players position

4 Likes
local clone = script.Parent:Clone()

script.Parent.PrimaryPart:SetNetworkOwner(nil)

In the script I use option 1 already, but I’m wondering, since the NPC is being Cloned from ReplicatedStorage whenever the game starts, do I have to set network ownership again?

1 Like