Need help with optimizing my code

Hi! I’m here to ask for your help, more advanced programmers than me, with the following problem:

I’m making a tower defense type game, but there is a problem… when you reach Wave 4, until the last wave, the NPCs that spawn already start to lag, and move slowly, and get stuck… How could I optimize the code so that this does not happen again?

Handler(Script:serverscriptservice)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local Pathfinding = require(game.ServerScriptService.Pathfinding)
local RunService = game:GetService("RunService")
local BindableEvent = ReplicatedStorage:WaitForChild("NPCReachedEnd")
local RemoteEvent = ReplicatedStorage:WaitForChild('WaveUpdate')


local waypointsFolder = Workspace:WaitForChild("Waypoints")
local enemiesFolder = ReplicatedStorage:WaitForChild("Enemies")

local waypoints = {
     waypointsFolder:WaitForChild("StartPart"),
     waypointsFolder:WaitForChild("W2"),
     waypointsFolder:WaitForChild("W3"),
     waypointsFolder:WaitForChild("W4"),
     waypointsFolder:WaitForChild("EndPart")
}

local waves = {
     { BasicZoombie = 3 },
     { BasicZoombie = 5 },
     { BasicZoombie = 6, Zoombie = 1 },
     { BasicZoombie = 5, Zoombie = 3 },
     { Zoombie = 5, RareZoombie = 2 },
     { Zoombie = 6, RareZoombie = 5, GoldZoombie = 2 },
     { RareZoombie = 8, GoldZoombie = 6 },
     { RareZoombie = 9, GoldZoombie = 8, BasicZoombie = 12, Zoombie = 4 },
     { StrongZoombie = 1, GoldZoombie = 2 },
     { StrongZoombie = 2, Zoombie = 9 }
}

local activeNPCs = 0
local currentWave = 1

local function spawnNPC(npcType)
     local npcTemplate = enemiesFolder:FindFirstChild(npcType)
     if npcTemplate then
          local clonedZombie = npcTemplate:Clone()
          clonedZombie.Parent = Workspace
          clonedZombie:SetPrimaryPartCFrame(waypoints[1].CFrame)

          task.spawn(function()
               for j = 2, #waypoints do
                    local path = Pathfinding:CreatePath(waypoints[j - 1], waypoints[j])
                    Pathfinding:MoveNPC(clonedZombie, path)
               end
               BindableEvent:Fire()
               clonedZombie:Destroy()
          end)
     end
end

local function startWave(waveNumber)
     RemoteEvent:FireAllClients(waveNumber)
     local waveData = waves[waveNumber]
     activeNPCs = 0

     for npcType, count in pairs(waveData) do
          for i = 1, count do
               activeNPCs = activeNPCs + 1
               spawnNPC(npcType)
               wait(2)
          end
     end
end

BindableEvent.Event:Connect(function()
     activeNPCs = activeNPCs - 1
     if activeNPCs == 0 then
          currentWave = currentWave + 1
          if currentWave <= #waves then
               wait(4)
               startWave(currentWave)
          end
     end
end)


task.wait(.5)

startWave(currentWave)

Pathfinding(ModuleScript;serverscriptservice)

local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local BindableEvent = ReplicatedStorage:WaitForChild('NPCReachedEnd')

local Pathfinding = {}

local waypointsFolder = Workspace:WaitForChild("Waypoints")
local waypoints = {
     waypointsFolder:WaitForChild("StartPart"),
     waypointsFolder:WaitForChild("W2"),
     waypointsFolder:WaitForChild("W3"),
     waypointsFolder:WaitForChild("W4"),
     waypointsFolder:WaitForChild("EndPart")
}

function Pathfinding:CreatePath(startPart, endPart)
     local path = PathfindingService:CreatePath({
          AgentRadius = 2,
          AgentHeight = 5,
          AgentCanJump = false,
          AgentMaxSlope = 45
     })

     path:ComputeAsync(startPart.Position, endPart.Position)
     return path
end

function Pathfinding:MoveNPC(npc, path)
     local animationController = Instance.new("AnimationController")
     animationController.Parent = npc

     for _, waypoint in ipairs(path:GetWaypoints()) do
       
          npc.Humanoid:MoveTo(waypoint.Position)
          npc.Humanoid.MoveToFinished:Wait()
     end
     if npc.PrimaryPart.Position == waypoints[#waypoints].Position then
          BindableEvent:Fire(npc)
     end
end

return Pathfinding
2 Likes

1st optimization I would suggest that in your Pathfinding module, you have defined function like
function Pathfinding:function(), When we use : to call a function inside a table, it provides the table as the first argument to function, and when we define a function using : in a table like this, the first argument is already inserted into a variable called self, I would say that replace : with . both in defining and calling of functions.

2 Likes

Also if you already have defined waypoints with parts like W2, W3, there is no need to use path finding service to create paths, just simply tween to the waypoint, increment the waypoint number, when it reaches there, move npc to new waypoint.

1 Like

Here, replace this line with this:
npc.Humanoid.MoveToFinished:Once(function(ignored) Pathfinding.MoveNPC(npc, path) end)
This will connect the function to call the MoveTo function again when it reaches end without yielding, Yielding especially in spawned functions with task.spawn causes a lot of overhead on server.
EDIT:
You are creating new threads for all npc that are being yielded, Thats a lot of overhead.

2 Likes

Thanks!:sob:I’ll try to optimize the code(if i get it) im so bad at re-coding the code even if i got the tips​:sob::sob:Appreciate it bro

1 Like

Optimised Handler script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Pathfinding = require(game.ServerScriptService.Pathfinding)
local BindableEvent = ReplicatedStorage:WaitForChild("NPCReachedEnd")
local RemoteEvent = ReplicatedStorage:WaitForChild('WaveUpdate')


local waypointsFolder = workspace:WaitForChild("Waypoints")
local enemiesFolder = ReplicatedStorage:WaitForChild("Enemies")

local waypoints = {
	waypointsFolder:WaitForChild("StartPart"),
	waypointsFolder:WaitForChild("W2"),
	waypointsFolder:WaitForChild("W3"),
	waypointsFolder:WaitForChild("W4"),
	waypointsFolder:WaitForChild("EndPart")
}

local waves = {
	{ BasicZoombie = 3 },
	{ BasicZoombie = 5 },
	{ BasicZoombie = 6, Zoombie = 1 },
	{ BasicZoombie = 5, Zoombie = 3 },
	{ Zoombie = 5, RareZoombie = 2 },
	{ Zoombie = 6, RareZoombie = 5, GoldZoombie = 2 },
	{ RareZoombie = 8, GoldZoombie = 6 },
	{ RareZoombie = 9, GoldZoombie = 8, BasicZoombie = 12, Zoombie = 4 },
	{ StrongZoombie = 1, GoldZoombie = 2 },
	{ StrongZoombie = 2, Zoombie = 9 }
}

local activeNPCs = 0
local currentWave = 1

local function spawnNPC(npcType)
	local npcTemplate = enemiesFolder:FindFirstChild(npcType)
	if npcTemplate then
		local clonedZombie = npcTemplate:Clone()
		clonedZombie.Parent = workspace
		clonedZombie:SetPrimaryPartCFrame(waypoints[1].CFrame)

		task.spawn(function()
			Pathfinding.StartMovingNPC(clonedZombie, waypoints)
			
			clonedZombie:Destroy()
		end)
	end
end

local function startWave(waveNumber)
	RemoteEvent:FireAllClients(waveNumber)
	local waveData = waves[waveNumber]
	activeNPCs = 0

	for npcType, count in pairs(waveData) do
		for i = 1, count do
			activeNPCs = activeNPCs + 1
			spawnNPC(npcType)
			wait(2)
		end
	end
end

BindableEvent.Event:Connect(function()
	activeNPCs = activeNPCs - 1
	if activeNPCs == 0 then
		currentWave = currentWave + 1
		if currentWave <= #waves then
			wait(4)
			startWave(currentWave)
		end
	end
end)


task.wait(.5)

startWave(currentWave)

Optimized Pathfinding:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local BindableEvent = ReplicatedStorage:WaitForChild('NPCReachedEnd')

local Pathfinding = {}

function Pathfinding.StartMovingNPC(npc, path, at: number)
	local animationController = Instance.new("AnimationController")
	animationController.Parent = npc
	
	npc.Humanoid:MoveTo(path[at])
	
	if(at <= #path) then
		npc.Humanoid.MoveToFinished:Once(function(ignored) Pathfinding.StartMovingNPC(npc, path, at+1) end)
	else
		BindableEvent:Fire(npc)
	end
end

return Pathfinding

You may wanna rename Pathfinding to NPC Mover or something like that.

2 Likes

Omg,really appreciate it buddy!Thank you​:sob:you saved 2hours of my life​:sob::sob::sob:

1 Like

uh,there’s an error in modulescript: ‘Argument 1 missing or nil’
at:

npc.Humanoid:MoveTo(path[at])
1 Like

Try this:

npc.Humanoid:MoveTo(path[at].Position)

EDIT:
Also replace Pathfinding.StartMovingNPC(clonedZombie, waypoints) with Pathfinding.StartMovingNPC(clonedZombie, waypoints, 1) in line 42 in task.spawn function in handler script.

error: ServerScriptService.Pathfinding:7: attempt to index nil with ‘Position’

I found a better update for the code in modulescript:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local BindableEvent = ReplicatedStorage:WaitForChild('NPCReachedEnd')

local Pathfinding = {}

function Pathfinding.StartMovingNPC(npc, waypoints, at)
     at = at or 2
     if at <= #waypoints then
          npc.Humanoid:MoveTo(waypoints[at].Position)
          npc.Humanoid.MoveToFinished:Once(function()
               Pathfinding.StartMovingNPC(npc, waypoints, at + 1)
          end)
     else
          BindableEvent:Fire(npc)
          npc:Destroy()
     end
end

return Pathfinding

This works really good

1 Like

Some more fixes to the script. I did some errors too, sry.

1 Like

Try the fixes I told in EDIT of above reply.

1 Like

Don’t worry! :sweat_smile:Is ok!I’m glad that you helped me,not that u made some little mistakes:)

Yeah,it works.Even if I put ‘Pathfinding.StartMovingNPC(clonedZombie, waypoints, 1)’ or i put ‘Pathfinding.StartMovingNPC(clonedZombie, waypoints, 2)’

Then just put 2 instead, will be more optimal solution for this.

@LakshyaK2011 There’s little more problem.From wave 5 above,the NPC when they arrive to a waypoint,they stay on that waypoint like 1 second,and then they move again…Why?

Even when they get destroyed,have a short delay

The code hasn’t been fully optimized yet, we still do task.spawn, I recommend removing that block completely and just call StartMovingNPC and Destroy method as they now don’t yield.

1 Like

actually,if i test the game in ‘run’ this problem doesn’t happen, but if I test the game as ‘play/play here’ then this problem happens