NPC movement being jittery/choppy

I’ve been working on a system for my game where NPCs will be following a specific routine, however after completing the first task, some NPCs start having jittery movement.

I’ve already tried the SetNetworkOwner method and it doesnt seem to solve my issue.
There are 30 NPCs.

Edit: Also, once the MoveNPC function is fired, it doesn’t fire again until the NPC reaches its destination and finishes the task.

Code for handling movement:

function module:MoveNPC(Destination: Vector3, SetCosts: {any}?)
	local npc = script.Parent
	local humanoid = npc:FindFirstChildOfClass("Humanoid")
	local rootPart = npc.PrimaryPart or npc:FindFirstChild("HumanoidRootPart")

	if not humanoid or not rootPart then return end
	SetCosts = SetCosts or {}
	

	local path = PathfindingService:CreatePath({
		AgentRadius = 1.6,
		AgentHeight = 5,
		AgentCanJump = false,
		AgentJumpHeight = 0, -- Allows jumping over obstacles -- 7.2
		AgentCanClimb = false,
		AgentClimbHeight = 0, -- Allows stepping over small obstacles -- 2
		AgentJumpClusters = false, -- Can use platforms for jumps
		Costs = SetCosts,
	})

	path:ComputeAsync(rootPart.Position, Destination)

	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()

		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid.Jump = true -- Forces NPC to jump over obstacles
			end

			humanoid:MoveTo(waypoint.Position)
			local reached = humanoid.MoveToFinished:Wait(2)

			if not reached then
				print("not reached")
				module:MoveNPC(Destination, SetCosts) -- Recalculate if NPC gets stuck
				break
			end
		end
		
		return true -- Succeded
	else
		return false -- Failed
	end
end

1 Like

The issue probably lies in the fact you’re using humanoid.MoveToFinished:Wait(2). If you visualize all the waypoints, and see that the NPC is jittering when it reaches a waypoint, that’s why.

You could either try reducing the waypoints by removing those by only using the waypoints that are needed for moving across gaps, jumping, or for turning corners. The other option is to use rapid magnitude checks to see if the NPC is close enough to the waypoint to start moving to the next one.

Also, would like to add that you don’t need to create a new path each time you call the move function, you can have multiple NPCs use the same path object.

The reason it’s always called is because the movements are meant to look somewhat realistic, using pathfinding modifiers to never take the same route, and since they are randomized, after a while they dont ever go the same way. About the rapid magnitude checks, do you mean to essentially just add a loop to see if the distance is below 2 studs or something?

For the magnitude check, yeah that’s what I reccomend.

If there’s still a lot of jittering, you may want to consider using client-sided NPCs. They’re kind of a headache to work with but they’re far smoother.

After some more research and some help from @TheCraftNCreator, I figured that the code below would work best for most situations

Edit: to explain this, I used Move instead of MoveTo function, Move function simply needs you to input the movement direction and is a lot smoother, I also included an arrival threshold to avoid stutters.

function module:MoveNPC(Destination: Vector3)
	local npc = script.Parent
	local humanoid = npc and npc:FindFirstChildOfClass("Humanoid")
	local rootPart = npc and npc:FindFirstChild("HumanoidRootPart")

	if not humanoid or not rootPart then
		return false
	end

	rootPart.Anchored = false

	local path = PathfindingService:CreatePath({
		AgentRadius = 1.6,
		AgentHeight = 5,
		AgentCanJump = false,
		AgentJumpHeight = 2.577,
		AgentCanClimb = true,
		AgentClimbHeight = 2,
		AgentJumpClusters = false,
		Costs = FullCosts,
	})

	path:ComputeAsync(rootPart.Position, Destination)

	if path.Status ~= Enum.PathStatus.Success then
		return false
	end

	local waypoints = path:GetWaypoints()
	local currentWaypointIndex = 1
	local moveDirection = Vector3.new(0, 0, 0)
	local lastPosition = rootPart.Position
	local lastUpdate = os.clock()

	local arrivalThreshold = 2
	local stuckThreshold = 0.5
	local moveSpeed = humanoid.WalkSpeed
	local rotationSpeed = 12

	while currentWaypointIndex <= #waypoints do
		local currentWaypoint = waypoints[currentWaypointIndex]
		local currentPosition = rootPart.Position

		if currentWaypoint.Action == Enum.PathWaypointAction.Jump or humanoid.Sit == true then
			humanoid.Jump = true
			task.wait(0.5)
		end

		local toWaypoint = (currentWaypoint.Position - currentPosition) * Vector3.new(1, 0, 1)
		local distance = toWaypoint.Magnitude

		if distance <= arrivalThreshold then
			currentWaypointIndex += 1
			if currentWaypointIndex > #waypoints then break end
			continue
		end

		if os.clock() - lastUpdate > stuckThreshold then
			if (currentPosition - lastPosition).Magnitude < 0.1 then
				return module:MoveNPC(Destination)
			end
			lastPosition = currentPosition
			lastUpdate = os.clock()
		end

		moveDirection = toWaypoint.Unit
		humanoid:Move(moveDirection)
		
		task.wait()
	end

	humanoid:Move(Vector3.new(0, 0, 0))
	return true
end

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