Error with my zombie npc script

Hello, I have been coding a Roblox script about a zombie npc for my game but when there are several instances of the zombie it starts to have strange movement like it walks looking to the side and with very unstable movement, here is the script I am having issues:

local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local target = nil
local lastJumpTime = 0
local jumpCooldown = 2 -- Time in seconds between jumps
local lastAttackTime = 0
local attackCooldown = 3 -- Time in seconds between attacks
local attackRange = 5 -- Attack range in studs
local attackAnimations = script:WaitForChild("AttackAnimations"):GetChildren()

-- Function to find the closest player
local function findClosestPlayer()
	local closestPlayer = nil
	local shortestDistance = math.huge
	for _, player in pairs(game.Players:GetPlayers()) do
		if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
			local distance = (npc.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position).magnitude
			if distance < shortestDistance then
				closestPlayer = player
				shortestDistance = distance
			end
		end
	end
	return closestPlayer
end

-- Function to play a random attack animation
local function playRandomAttackAnimation()
	local randomIndex = math.random(1, #attackAnimations)
	local animation = attackAnimations[randomIndex]
	local animator = humanoid:FindFirstChildOfClass("Animator")
	if animator then
		local animationTrack = animator:LoadAnimation(animation)
		animationTrack:Play()
	end
end

-- Function to attack the player
local function attackPlayer()
	local currentTime = tick()
	if currentTime - lastAttackTime >= attackCooldown then
		if target and target.Character and target.Character:FindFirstChild("Humanoid") then
			local targetHumanoid = target.Character.Humanoid
			local distanceToTarget = (npc.HumanoidRootPart.Position - target.Character.HumanoidRootPart.Position).magnitude
			if distanceToTarget <= attackRange then
				targetHumanoid:TakeDamage(10) -- Damage dealt by the NPC
				playRandomAttackAnimation()
				lastAttackTime = currentTime
			end
		end
	end
end

-- Function to move the NPC towards the player
local function moveToPlayer()
	if target and target.Character and target.Character:FindFirstChild("HumanoidRootPart") then
		local path = PathfindingService:CreatePath({
			AgentRadius = 2,
			AgentHeight = 5,
			AgentCanJump = true,
			AgentJumpHeight = 7,
			AgentMaxSlope = 45,
		})
		path:ComputeAsync(npc.HumanoidRootPart.Position, target.Character.HumanoidRootPart.Position)
		local waypoints = path:GetWaypoints()

		for _, waypoint in pairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
			if waypoint.Action == Enum.PathWaypointAction.Jump and humanoid:GetState() ~= Enum.HumanoidStateType.Jumping then
				local currentTime = tick()
				if currentTime - lastJumpTime >= jumpCooldown and humanoid:GetState() == Enum.HumanoidStateType.Running then
					humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					lastJumpTime = currentTime
				end
			end
		end
	end
end

-- Main loop
RunService.Heartbeat:Connect(function()
	if humanoid.Health > 0 then
		target = findClosestPlayer()
		if target then
			moveToPlayer()
			attackPlayer()
		end
	end
end)

To understand my problem, here are videos
The video of the problem:

Another video but with more instances:

Sometimes I even get the same problem with just one instance.

Is your npc’s Humanoid.AutoRotate set to true?

Yes, the humanoid.autorotate of the npc is set to true

Every heartbeat you’re computing a completely new path to the player and then moving the NPC to the player which I imagine is why the zombie walk animation keeps being reset. Aside from the strange movement, this is just terribly inefficient, a simple while task.wait() loop would work and cost less script peformance.

You could instead just have the zombie move towards the player every set interval of time and then add a check to see if the zombie might be stuck on some obstacle. If they are stuck, then compute a single new path to the player to get them unstuck.

Something along the lines of

coroutine wrap checkNPCStuckFunction  -- have this constantly running in the background
    while true do
        oldPos = NPC old position
        wait(x)      -- wait x seconds , like 0.5 or whatever you like
        currentPos = NPC position after x seconds
        if currentPos - oldPos magnitude < 1     -- NPC has moved less than 1 stud
            compute new path to player
            for waypoint loop
                if Count == (waypoints/2) then break     -- to get the NPC unstuck we only have to move through half the path
                Count += 1

I just realised this might not fix your animation issue lol.
To fix the animation, you’re gonna have to make your own custom animations to play on the NPC. Whenever the NPC is moving, play its walk animation. You could find if the NPC is moving with the MoveDirection property of humanoid, just get its magnitude.

1 Like

Very strange, while your pathfind function’s not the best, it should be turning towards the player if it is set to true, one possible reason this could be occuring may be network-ownership, though this is only a guess, try doing

HumanoidRootPart:SetNetworkOwner(nil)

In your code, if this fixes your issue, that’s great, otherwise, you should still do this as it’ll make the npc’s movement less unstable as you put it, however the no turn thing is still surprising

It looks like the root part is offset by 45 degrees since they are orienting towards the players but with an offset.

Make sure you use .CFrame to teleport your zombies or spawn them in to avoid rotating the Motor6D. I’m guessing the hrp is rotated so you can make it visible by modifying the transparency to debug the issue.

Omg your gonna have write either a 1000 line code script or just copy and paste some code to fix the animation… Btw the code is really buggy, most problems have already been adressed.

maybe also add a check if the player is even moving since if not then dont generate a new path (learned from experience that this is usefull) And i suggest splitting the functions into more smaller functions.

Overal: Dont bother making an npc yourself… there are many great models in the toolbox and in the devforums just waiting to be used!

I mean no offense, but this is terrible advice, and the issue doesn’t seem to be with the animations, that’s the standard Roblox R6 animation, I have to agree with @dthecoolest that the RootPart is possibly rotated, but aside from that, you should always try to challenge yourself and improve as a developer, what you suggest should only be done, and even then, with caution, when your main goal is to push out a working game as fast as possible