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
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