Why is my pathfinding causing my game to lag?

Hi devs,

I’m currently working on a piggy-inspired game, but I want to know why is my AI so laggy, like, it’s not laggy at first, but when i start playing the game for too much, it starts lagging so much, that if i keep playing, my game may break entirely and crash. Here’s what I have for my script:

local RS = game:GetService("ReplicatedStorage")

local Forbidden = RS:WaitForChild("Forbidden")
local AI = require(Forbidden:WaitForChild("AI"))

local NPC = script.Parent

local function getClosestCharacter(npc)
	local closestCharacter = nil
	local shortestDistance = math.huge

	for _, player in pairs(game.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChild("HumanoidRootPart") then
			local distance = (npc:GetPivot().Position - character.HumanoidRootPart.Position).Magnitude
			if distance < shortestDistance then
				shortestDistance = distance
				closestCharacter = character
			end
		end
	end

	return closestCharacter
end

local ClosestCharacter = getClosestCharacter(NPC)

NPC.HumanoidRootPart:SetNetworkOwner(nil)

while wait() do
	ClosestCharacter = getClosestCharacter(NPC)
	
	AI.SmartPathfind(NPC, ClosestCharacter:WaitForChild("HumanoidRootPart"), false, {Visualize = false, Tracking = true})
end

Any help would be appreciated!

can you show what’s in this function?

My bad, here’s the whole module (for the record, i did NOT make that module, but here it is):

local API = {}

local rs = game:GetService("ReplicatedStorage")
local pfservice = game:GetService("PathfindingService")
local debris = game:GetService("Debris")

local processes = {}
local Forbidden = rs:WaitForChild("Forbidden")
local signals = script:WaitForChild("signals")
local stopAI = signals:WaitForChild("StopAI")
local std = require(Forbidden:WaitForChild("Standard"))

API.Stuck = function(humanoid: Humanoid)
	humanoid:Move(Vector3.new(math.random(-1,1),0,math.random(-1,1)))
	humanoid.Jump = true
end

local g_id = 0

local function reset(NPC)
	local currentWaypoint = NPC:FindFirstChild("CurrentWaypoint")
	if currentWaypoint == nil then return end
	if currentWaypoint.Value == nil then return end
	

	if currentWaypoint then
		local waypointPosition = currentWaypoint.Value
		NPC.Humanoid:MoveTo(waypointPosition)
	else
		warn("Unable to reset AI's position. Current waypoint not found.")
	end
end

API.SmartPathfind = function(NPC: any, Target: any, Yields: boolean, Settings: "Table; StandardPathfindSettings, Visualize, Tracking, SwapMovementMethodDistance")
	
	--TODO: add positional / tracking options.
	local anti = script.antilag:Clone()
	anti.Parent = NPC
	anti.Enabled = true
	
	if NPC == nil or Target == nil then return false end
	
	local pfSettings = {
		StandardPathfindSettings = {
			AgentRadius = 3,
			AgentHeight = 6,
			AgentCanJump = true,
			AgentCanClimb = true,
			Cost = {}
			},
		Visualize = false,
		Tracking = false, -- continues till AI:Stop(NPC) or different pathfind is started.
		SwapMovementMethodDistance = 10,
	}
	
	if Settings then
		for setting, v in pairs(Settings) do
			pfSettings[setting] = v
		end
	end

	local i = 0
	
	local enemyRoot = nil
	local enemyHuman = nil
	local targetRoot = nil
	
	local function updateBasedOnType(obj,type) -- if you're trying to understand this script collapse this function and IGNORE it.
		
		i+=1
		
		local function updateVars(char)
			
			if i == 1 then -- for tracker
				
				
				enemyRoot = char:FindFirstChild("HumanoidRootPart")
				if enemyRoot == nil then error("Could not find HRT. change to waitforchild to bypass") return end
				enemyHuman = char:FindFirstChild("Humanoid")
				if enemyHuman == nil then error("Could not find Humanoid. change to waitforchild to bypass") return end
				
			end
			
			if i == 2 then -- for target
				
				targetRoot = char:FindFirstChild("HumanoidRootPart")
				if targetRoot == nil then error("Could not find HRT. change to waitforchild to bypass") return end
			end
		end
		
		if (typeof(obj)) == "userdata" then -- checks for Humanoid
			
			if obj:IsA("Humanoid") then
				
				updateVars(obj.Parent)
			end
		end
		
		if type == "Model" then -- checks to see if it is a char
			
			if i == 1 then
				
				if obj:FindFirstChild("Humanoid") then
					
					updateVars(obj)
				end
			end
			
			if i == 2 then
				
				targetRoot = obj:GetChildren()[1]
			end
		end
		
		if type == "Player" then -- checks to see if it is a player
			
			if obj.Character ~= nil then updateVars(obj.Character) end
			if obj.Character == nil then return "char not found" end -- protects against Players:GetChildren() loop errors
		end
		
		if type == "Part" then -- finds humanoid from part, if humanoid then send.
			
			if obj.Parent:FindFirstChild("Humanoid") then
				
				updateVars(obj.Parent)
			end
			
			if obj.Parent.Parent:FindFirstChild("Humanoid") then
				
				updateVars(obj.Parent.Parent)
			end
			
			if i == 1 then error("Are you sure you passed in the right part for the character, could not find a Humanoid") return end
			
			if i == 2 then -- for normality
				
				targetRoot = obj
			end
		end
	end
	
	if NPC ~= nil then updateBasedOnType(NPC,std.basic.GetType(NPC)) else error("Enemy/Tracker does not exist.") end
	if Target ~= nil then updateBasedOnType(Target,std.basic.GetType(Target)) else return "target not found" end
	
	
	-- Pathfinding
	local pathCount = 1
	
	local path = pfservice:CreatePath(Settings.StandardPathfindSettings)

	--print(waypoints)
	local function losCheck()

		local result = std.math.LineOfSight(NPC, Target, {Range = 50, SeeThroughTransparentParts = false, filterTable = {}})
		if result then
			return true
		end
		
		return false
	end
	
	local function destroyWP()
		
		for i, v in pairs(NPC:GetChildren()) do
			
			if v.Name == "Waypoints" then
				
				debris:AddItem(v,0)
			end
		end
	end
	
	local function moveTo()
		enemyHuman:MoveTo(targetRoot.Position)
		
		if not pfSettings.Tracking then
			
			enemyHuman.MoveToFinished:Wait()
		end
	end
	
	local function pathfind()
		
		local result = losCheck()
		if result and (enemyRoot.Position - targetRoot.Position).Magnitude < pfSettings.SwapMovementMethodDistance then 
			moveTo() 
			return 
		end
		
		path:ComputeAsync(enemyRoot.Position,targetRoot.Position)
		local waypoints = path:GetWaypoints()
		
		
		if path.Status == Enum.PathStatus.NoPath then warn("No path could be found. This is an issue with Roblox, not Forbidden.") return Enum.PathStatus.NoPath end -- if no possible path.
		
		if path.Status == Enum.PathStatus.Success then
			local currentPathCount = pathCount
			
			g_id+=1
			local id = g_id
			
			processes[NPC] = id -- KEEP THIS IN MIND WHEN TRYING TO PAUSE, you have to pass the char in when pausing
			
			local VECTOR3VAL_currentWaypoint = NPC:FindFirstChild("CurrentWaypoint")
			if VECTOR3VAL_currentWaypoint == nil then VECTOR3VAL_currentWaypoint = Instance.new("Vector3Value", NPC) VECTOR3VAL_currentWaypoint.Name = "CurrentWaypoint" end
			
			local folder = Instance.new("Folder")
			folder.Parent = NPC
			folder.Name = "Waypoints"

			for i, waypoint in ipairs(waypoints) do

				local part = Instance.new("Part")
				part.Shape = Enum.PartType.Ball
				part.Color = Color3.new(0.384314, 0.341176, 1)
				part.Material = Enum.Material.Neon
				part.CFrame = CFrame.new(waypoint.Position)
				part.Parent = folder
				part.Name = i
				part.Anchored = true
				part.Size = Vector3.new(1,1,1)
				part.CanCollide = false
				
				if not pfSettings.Visualize then
					part.Transparency = 1
				end
				
			end
			
			
			for i, waypoint in ipairs(waypoints) do -- LOOPING THROUGH WAYPOINTS
				
				if i > 1 then
				
					if losCheck() then moveTo() return end
					
					if currentPathCount > #waypoints or processes[NPC] ~= id then
						
						--print(#waypoints)
						--print("stopping")
						reset(NPC)
						
						return
					end
					if waypoint.Action == Enum.PathWaypointAction.Jump then
						enemyHuman.Jump = true
					end
					
					enemyHuman:MoveTo(waypoint.Position)
					VECTOR3VAL_currentWaypoint.Value = waypoint.Position
					
					delay(0.5, function()
						if enemyHuman.WalkToPoint.Y > (targetRoot.Position.Y + 0.5) and (enemyRoot.Position - targetRoot.Position).Magnitude < 5 then
							enemyHuman.Jump = true
						end
					end)
					
					local moveSuccess = enemyHuman.MoveToFinished:Wait()
					if not moveSuccess then
						break
					end
				end
				
				currentPathCount+=1
			end
		else
			API:Stuck(enemyHuman)
		end
	end
	
	if Yields or Yields == nil then
		
		local lastPos = nil
		processes[NPC] = "starting"
		
		if pfSettings.Tracking == true then
			while processes[NPC] ~= false do
				
				if std.math.Round(targetRoot.Position) ~= lastPos then 
					lastPos = std.math.Round(targetRoot.Position)
					if pfSettings.Visualize then destroyWP() end
					spawn(pathfind)
				end
				wait()
				
			end
		end
		
		if pfSettings.Tracking == nil or pfSettings.Tracking == false then
			if pfSettings.Visualize then destroyWP() end
			pathfind()
		end
	end
	
	if not Yields then
		spawn(function()
			local lastPos = nil
			processes[NPC] = "starting"
			
			if pfSettings.Tracking == true then
				while processes[NPC] ~= false do
					
					if std.math.Round(targetRoot.Position) ~= lastPos then 
						if pfSettings.Visualize then destroyWP() end
						lastPos = std.math.Round(targetRoot.Position)
						spawn(pathfind)
					end
					wait()
				end
			end
			
			if pfSettings.Tracking == nil or pfSettings.Tracking == false then
				if pfSettings.Visualize then destroyWP() end
				spawn(pathfind)
			end
		end)
	end
	
	if pfSettings.Visualize then -- ensures all those wps are gone
		destroyWP()
	end
end

local function updateAll()
	
	
end

local function onStoppage(AI)
	
	if AI == nil then error("AI passed to AI:Stop() is nil.") return end
	
	if processes[AI] ~= nil then
		

		if AI:FindFirstChild("Waypoints") then AI:FindFirstChild("Waypoints"):Destroy() end
		reset(AI)
		
		processes[AI] = false
		return
	end
	
	error("AI is not in active table, please make sure you passed it in correctly.")
end

API.Stop = function(AI: Model)
	
	stopAI:Fire(AI)
	return "stopping"
end

stopAI.Event:Connect(onStoppage)

--function API:SmartActivate(humanoid, continuous): boolean TODO
	
	
--	return false
--end

return API

what is this

1 Like

idk to be honest, the module is not mine so uhh yeah, it just sets it’s humanoid root part to nil from what i see

open the “antilag” script and screenshot / send it here. looks like a backdoor

It just sets it’s humanoid root part network to nil

delete these lines and you should probably be fine, it’s because this script keeps getting copied and put into the npc in a while loop

1 Like

idk to be honest bro maybe that’s not gonna happen but let’s just see

Yeah, it didn’t, for some reason, when i start the game, it does not lag, just because i keep playing the game then it starts lagging, here’s a video of what’s happening for you to understand better what’s happening:


sorry for low quality

You’re using a while loop. In every frame, this will continously call, and call, and call… the SmartPathfind function which will calculate a path for the npc. When you continously call this every frame. The amount of tasks for calculating a valid path will increase EXPONENTIALLY. This is what is called a memory leak.

I’ve looked into the module script and it seems that it tracks the player automatically and you only need to call AI.SmartPathfind() once. To stop tracking, you need to call AI.Stop(). The code in the module is barely readable and I can’t properly trace where the function calls are.

<></>

Now these lines are concerning and this is a sign of a virus in your game. I’m guessing you found this off of the toolbox right? I suggest deleting it and replacing it with a more trusted developer on the forums such as this pathfinding module that is open source.
SimplePath - Pathfinding Module - Resources / Community Resources - Developer Forum | Roblox

FOR SOME REASON, I tried using simple path one time, but my character just bugged out for no reason, after some time chasing me also can you give me a code example for a simple path script?

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SimplePath = require(ReplicatedStorage.SimplePath)

local NPC = script.Parent

local Path = SimplePath.new(NPC)
while true do
	Path:Run(workspace.Part.Position)
	task.wait(2)
end

Simple setup with a part in the workspace. Though, you will have to keep updating the position in a while loop every 2 seconds or so with Path:Run( Vector3 ). Here is the github tutorial on SimplePath
Getting Started - SimplePath (grayzcale.github.io)

Edit1: changed the code to show loop

Cool, i tried reworking my ai script, but it doesn’t work, can you help me?

Btw there’s a error ServerStorage.SimplePath:285: attempt to perform arithmetic (sub) on number and nil

Here’s the code:

local ServerStorage = game:GetService("ServerStorage")
local SimplePath = require(ServerStorage.SimplePath)

local NPC = script.Parent

local Path = SimplePath.new(NPC)

local function getClosestCharacter(npc)
	local closestCharacter = nil
	local shortestDistance = math.huge

	for _, player in pairs(game.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChild("HumanoidRootPart") then
			local distance = (npc:GetPivot().Position - character.HumanoidRootPart.Position).Magnitude
			if distance < shortestDistance then
				shortestDistance = distance
				closestCharacter = character
			end
		end
	end

	return closestCharacter
end

local closestCharacter = getClosestCharacter(NPC)

Path.Blocked:Connect(function()
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

Path.WaypointReached:Connect(function()
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

Path.Error:Connect(function()
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

SimplePath:Run(closestCharacter:WaitForChild("HumanoidRootPart"))

Should be:

Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
1 Like

Oh yeah, i forgot lol i forgot to did that

Yo it works flawselly, thank you! He’s super slow because of the walkspeed i changed, but it works super well, thank you! Cheers!

You should probably change it to this so that every time it can recalculate for the nearest player. Also, to note maybe add a system where when the NPC gets close the the player (20 studs or less), the NPC doesn’t pathfind and just walks straight towards you.

Path.Blocked:Connect(function()
	local closestCharacter = getClosestCharacter(NPC)
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

Path.WaypointReached:Connect(function()
	local closestCharacter = getClosestCharacter(NPC)
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

Path.Error:Connect(function()
	local closestCharacter = getClosestCharacter(NPC)
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)

Path.Reached:Connect(function()
	local closestCharacter = getClosestCharacter(NPC)
	Path:Run(closestCharacter:WaitForChild("HumanoidRootPart"))
end)
1 Like

Thank you bro, you have NO idea of how much that take lol

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