Pathfinding Stuttering / Lagging

So basically, I’ve been working on a NPC that paths between different points then, once he reaches it’s final destination it basically disappears.
The script works pretty much well, but after like 20-30 seconds the NPCs start to move as if they had a lot of lag (typical pathfinding lag)
Here’s the code:

-- // Client AI made by DegVelopment

-- // Variables
local character = script.Parent.Parent.Parent
local humanoid = character.Humanoid
local root_part = character.PrimaryPart

local character_size = character:GetExtentsSize()
local character_radius = character_size.X

local module_folder = script.Parent.Parent.ModuleScripts
local queue_folder = workspace.TargetParts.Queue
local target_folder = workspace.TargetParts.ParkingSpots
local seat_folder = workspace.Seats

local order = script.Parent.Parent.Assets.Order

local queue_number = 0
local queue_part = nil

--// Services
local pathfindingService = game:GetService("PathfindingService")
local collectionService = game:GetService("CollectionService")

--// Tables
local pathArgs = {
	AgentRadius = character_radius,
	AgentHeight = character_size.Y,
	AgentCanJump = true,
	Costs = {
		AntiCustomer = math.huge
	}
}

-- // Modules
local assets = require(module_folder.NPC_Assets)

--// Events
local returnEvent = game.ServerScriptService.QueueHandler.QueueReturn
local queueEvent = game.ServerScriptService.QueueHandler.QueueFire

-- // Network Owner to the Server, avoiding Client lag
for _, base_part in pairs(character:GetChildren()) do
	if base_part:IsA("BasePart") then
		base_part:SetNetworkOwner(nil)
	end
end

-- // Functions
local function computePathAsync(target)
	humanoid:Move(root_part.CFrame.LookVector * 100)
	
	local see_target = false
	
	if assets.checkSight(target) then
		see_target = true
	end
	
	if see_target == false then
		local path = pathfindingService:CreatePath(pathArgs)
		local success, errorMsg = pcall(function()
			path:ComputeAsync(root_part.Position, target.Position)
		end)
		
		if success and path.Status == Enum.PathStatus.Success then
			local waypoints = path:GetWaypoints()
			for _, waypoint in pairs(waypoints) do
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					humanoid.Jump = true
				end
				
				humanoid:MoveTo(waypoint.Position)
				humanoid.MoveToFinished:Wait()
			
				if assets.checkSight(target) or assets.checkDist(target, waypoints[#waypoints]) > 30 then
					break
				end
			end
			
		else
			warn("Path coudln't compute:",errorMsg)
		end
	else
		humanoid:MoveTo(target.Position)
	end
end

local function main() -- // The main function will make the Costumer perform the actions a Costumer would perform
	---------------------------------------------------------------------------------------------------------------
	-- // Look for a spot in the queue
	queueEvent:Fire(character, "Enter")
	
	if queue_part then

		while wait() do
			computePathAsync(queue_part)
			
			if assets.checkDist(root_part, queue_part) < 5 then break end
		end
	else
		warn("Couldn't find the queue part.")
	end
	---------------------------------------------------------------------------------------------------------------
	-- // Wait for the order to arrive, while progressing in the queue
	while order.Value ~= true do
		if order.Value == true then break end
		
		computePathAsync(queue_part)
		
		wait()
	end
	---------------------------------------------------------------------------------------------------------------
	-- // Look for a seat to enjoy the meal
	queueEvent:Fire(character, "Leave")
	
	local seats_to_choose = {}
	for _, seat in pairs(seat_folder:GetChildren()) do
		if seat:IsA("Seat") and seat.Occupant == nil then
			table.insert(seats_to_choose, seat)
		end
	end
	
	local random_seat = seats_to_choose[math.random(1, #seats_to_choose)]
	random_seat.Disabled = false
	while humanoid.Sit == false do
		if humanoid.Sit == true then break end
		
		computePathAsync(random_seat)
		
		wait()
	end
	---------------------------------------------------------------------------------------------------------------
	-- // Leave the place
	wait(3)
	
	if humanoid.Sit == true then
		humanoid.Jump = true
		humanoid.Sit = false
	end
	
	random_seat.Disabled = true
	
	spawn(function() wait(15) character:Destroy() end)
	local new_target = target_folder:GetChildren()[math.random(1, #target_folder:GetChildren())]
	while wait() do
		if humanoid.Sit == true then
			humanoid.Jump = true
			humanoid.Sit = false
		end
		
		computePathAsync(new_target)
	end
end

returnEvent.Event:Connect(function()
	if collectionService:HasTag(character, 1) then
		queue_number = 1
	elseif collectionService:HasTag(character, 2) then
		queue_number = 2
	elseif collectionService:HasTag(character, 3) then
		queue_number = 3
	elseif collectionService:HasTag(character, 4) then
		queue_number = 4
	elseif collectionService:HasTag(character, 5) then
		queue_number = 5
	elseif collectionService:HasTag(character, 6) then
		queue_number = 6
	end
	queue_part = queue_folder:FindFirstChild("Queue"..queue_number)
end)

if not game:IsLoaded() then game.Loaded:Wait() end
main()

It looks like the pathfinding is causing lag for the NPC after a certain amount of time. One possible reason for this could be that the NPC is continuously computing new paths to its destination, even when it is already close to the destination.

One way to address this issue is to add a check to see if the NPC is already close enough to the target before computing a new path. You could modify the computePathAsync function to include a distance check between the NPC and the target, like this:

local function computePathAsync(target)
    humanoid:Move(root_part.CFrame.LookVector * 100)
    
    local see_target = false
    
    if assets.checkSight(target) then
        see_target = true
    end
    
    if see_target == false then
        local path = pathfindingService:CreatePath(pathArgs)
        local success, errorMsg = pcall(function()
            path:ComputeAsync(root_part.Position, target.Position)
        end)
        
        if success and path.Status == Enum.PathStatus.Success then
            local waypoints = path:GetWaypoints()
            for _, waypoint in pairs(waypoints) do
                if waypoint.Action == Enum.PathWaypointAction.Jump then
                    humanoid.Jump = true
                end
                
                humanoid:MoveTo(waypoint.Position)
                humanoid.MoveToFinished:Wait()
            
                if assets.checkSight(target) or assets.checkDist(target, waypoints[#waypoints]) > 30 or assets.checkDist(root_part, target) < 5 then
                    break
                end
            end
            
        else
            warn("Path coudln't compute:",errorMsg)
        end
    else
        humanoid:MoveTo(target.Position)
    end
end

In this modified version of the function, we added a new check that breaks out of the loop if the NPC is already within a certain distance of the target. This should help reduce the amount of unnecessary pathfinding computations and improve performance.

Another optimization you could make is to increase the wait() time between pathfinding computations. Currently, the NPC is continuously pathfinding as fast as possible, which could also contribute to lag. You could try increasing the wait time to something like wait(0.5) or even higher to reduce the frequency of pathfinding computations.

I hope these suggestions help improve the performance of your NPC pathfinding script!

1 Like

It’s likely because of network ownership issues. It will bounce between both the server and client, which will cause it to stutter.

Add this line into your code, right after the variables:

character.HumanoidRootPart:SetNetworkOwner(nil)

Setting it to nil with the HumanoidRootPart will prevent it from bouncing between the two, which should prevent the stuttering.

You already do this with every single part, but that is not necessary. I would try just the HumanoidRootPart


Also, I would recommend to just changing this to a repeat loop:

repeat task.wait(1) until game:IsLoaded()
4 Likes

In this case, a loop shouldn’t be used;

if not game:IsLoaded() then
    game.Loaded:Wait()
end

works fine

2 Likes

Thank you so much for your feedback! But somehow the path still stutters like hell… It might be fault of some other script but I don’t know how can I look for the script that it’s causing that much trouble.

Ignore the pathfinding being stupid and how the npc can’t get to the tables, I can easily fix that issue. The only thing that bothers me here is the stuttering.

After adding that, you should remove the for loop that sets network ownership for each part. Don’t know if you got that or not, but I’m telling you in case you didn’t.

I basically replaced the for loop with this:

root_part:SetNetworkOwner()

That is correct.

Character limit

Okay so somehow, the pathfinding seems to stutter when this part of the code is running
I tried changing the wait() to wait(.5), but nothing really changes

It might not do much, but I would recommend changing every wait in the script to task.wait(), as well as changing any spawn() to task.spawn(). They all do the same thing, but run and perform much better.

Try this first while I look for anything that could cause it to stutter.

1 Like

Thank you! If you need anything please ask for it, like for example which scripts I have inside server script service and what do they do.

I would like to ask for the place file if you are willing to send it. It does not have to be here either. You can send the file via dms if you would like. I see nothing wrong with the script itself, so it will make it much easier to find a solution with the place file.

Sure, since it’s a really simple place I don’t mind sharing it with you

Hmm. I have concerns about this:

	spawn(function() wait(15) character:Destroy() end)
	local new_target = target_folder:GetChildren()[math.random(1, #target_folder:GetChildren())]
	while wait() do
		if humanoid.Sit == true then
			humanoid.Jump = true
			humanoid.Sit = false
		end
		
		computePathAsync(new_target)
	end

You are spawning a co-routine which uses the while wait() do loop, which has no break condition meaning it will run indefinitely. Placew a break condition, ie if NPC is no longer alive, then break.

2 Likes

Okay so basically, I noticed that the NPCs lag if there is a player in the game, if you run the game without players they won’t lag. Any idea?

Hello did you manage to fix the issue? And what was your fix?