NPCs starts to stutter when having more than 6 NPCs

It’s pretty much self explanatory. I am making a Piggy like game and I tried removing SetNetworkOwner and adding it onto the client, pathfinding NPCs will not try to use the pathfinding at all.

1 Like

Use SetNetworkOwner?? Or change to the new pathfinding algorithm

3 Likes

I literally stated I tried both, adding and removing the function, does the same thing. And also with both, old and new pathfinding algorithms.
image

2 Likes

:SetNetworkOwner only works on the server. You don’t need to manually update the network owner to the nearest player, the engine already does that for you, or you could explicitly call :SetNetworkOwnershipAuto to let the server decide.

2 Likes

From what I can tell, the AIs are using :MoveTo, but are not waiting for the MoveToFinished signal/event of the humanoid to occur. The AI would keep trying to move to the destination, but would ultimately have a seizure lmao.

2 Likes

They indeed use MoveToFinished, but spams it.

1 Like

:SetNetworkOwner IS set on the server. What I am thinking is how do I make the bots calculate good enough to not slow themselves down (Yes, I am using Heartbeat).

1 Like


Yep, the more bots there is, less calculating happens.

2 Likes

Are you using pathfinding? If so, more NPCs = more pathfinding calls,. which can create yields in pathfinding calculations.

Oh boy… I tri ed to make task.wait(5) on Heartbeat but got avoided.

Here’s a part of a script.

local function fetchPossibleTargets(preferredBP)
	local targetCharacters = workspace:GetChildren()
	local closestTarget = nil
	local closestDistance = targetDistance
	local closestHumanoid = nil
	local closestRootPart = nil
	local potentialTargets = {}
	local seeTargets = {}

	for _, playerTarget in ipairs(targetCharacters) do
		if playerTarget:IsA("Model") and playerTarget ~= script.Parent then
			local targetHumanoid = playerTarget:FindFirstChildOfClass("Humanoid")
			local targetRootPart = playerTarget:FindFirstChild("HumanoidRootPart")

			if targetHumanoid and targetRootPart and targetHumanoid.Health > 0 then
				local targetParent = targetRootPart.Parent
				if not targetParent:FindFirstChild("Enemy") 
					and not targetParent:FindFirstChild("Helper") 
					and not targetParent:FindFirstChild("IsStunned")
					and not targetParent:FindFirstChild("SoldierNPC") 
					and enemyRootPart.Anchored == false then
					
					if targetRootPart then
						local distance = (targetRootPart.Position - preferredBP).Magnitude

						if distance < closestDistance then
							closestDistance = distance
							closestTarget = playerTarget
							closestHumanoid = targetHumanoid
							closestRootPart = targetRootPart

							table.insert(potentialTargets, targetRootPart)
							local NETWORK_OWNER = Players:GetPlayerFromCharacter(closestTarget)
							if NETWORK_OWNER then
								enemyRootPart:SetNetworkOwner(NETWORK_OWNER)
							end

							if #potentialTargets > 0 then
								for i, v in ipairs(potentialTargets) do
									if checkTarget(v) then
										table.insert(seeTargets, v)
									end
								end
							end
						end
					end
				end
			end
		end
	end
	return closestRootPart, closestTarget
end

RunService.Heartbeat:Connect(function()
	if isChasing == true and enemyRootPart.Anchored == false then
		local MainTorso = fetchPossibleTargets(enemyRootPart.Position)

		if MainTorso ~= nil then
			currentlyWandering = 1

			local path = PathfindingService:CreatePath({
				AgentCanJump = true,
				Costs = {
					Water = 20
				},
				PathSettings = {
					SupportPartialPath = true
				}
			})

			destinedPath = PathfindingService:FindPathAsync(enemyRootPart.Position, MainTorso.Position)
			coordinatedWaypoints = destinedPath:GetWaypoints()
			discontinuedPoints = coordinatedWaypoints

			local function checkCurrentPath(t)
				local movementI = 3
				if movementI > #t then
					movementI = 3
				end
				if t[movementI] == nil and movementI < #t then
					repeat
						movementI = movementI + 1
						task.wait()
					until t[movementI] ~= nil
					return Vector3.new(1, 0, 0) + t[movementI]
				else
					movementI = 3
					return t[movementI]
				end
			end

			if destinedPath and coordinatedWaypoints or checkCurrentPath(coordinatedWaypoints) then
				if checkCurrentPath(coordinatedWaypoints) ~= nil and checkCurrentPath(coordinatedWaypoints).Action == Enum.PathWaypointAction.Walk then
					enemyHumanoid:MoveTo(checkCurrentPath(coordinatedWaypoints).Position)
				end

				if checkCurrentPath(coordinatedWaypoints) ~= nil and checkCurrentPath(coordinatedWaypoints).Action == Enum.PathWaypointAction.Jump then
					if isJumping == false then
						isJumping = true
						enemyHumanoid.Jump = true
						task.wait(1.6)
						isJumping = false
					end
					if checkCurrentPath(coordinatedWaypoints) then
						enemyHumanoid:MoveTo(checkCurrentPath(coordinatedWaypoints).Position)
					end
				end
			else
				for i = 3, #discontinuedPoints do
					enemyHumanoid:MoveTo(discontinuedPoints[i].Position)
				end
			end
		elseif MainTorso == nil then
			currentlyWandering = 0
			destinedPath = nil
			coordinatedWaypoints = nil
                        enemyHumanoid.MoveToFinished:Wait()
		end
	end
end)
1 Like

I do know enemyHumanoid.MoveToFinished here is pretty much useless as it doesn’t execute at all.

Heartbeat is asynchronous, meaning it’ll call again even if the last call was yielded.

This code will continue to run every heartbeat:

local RunService = game:GetService("RunService")

RunService.Heartbeat:Connect(function()
	print("foo")
	task.wait(5)
	print("bar")
end)

Instead, you can create some sort of debounce system. Here are two possible solutions:

local COOLDOWN = 5

local RunService = game:GetService("RunService")

local LastTick = os.clock()

RunService.Heartbeat:Connect(function()
	if os.clock() - LastTick < COOLDOWN then
		return -- If it hasn't been 5 seconds, don't run
	end
	
	LastTick = os.clock()
	
	print("foo")
end)

Or alternatively,

local COOLDOWN = 5

local RunService = game:GetService("RunService")

local Debounce = false

RunService.Heartbeat:Connect(function()
	if Debounce then
		return
	end
	
	Debounce = true
	
	print("foo")
	task.wait(COOLDOWN)
	print("bar")
	
	Debounce = false
end)

I’ll try both ways, if it works, I’ll mark this as a solution. I’ll play around until I get it working fine.

1 Like

for odd reason, if it’s off, it will not walk until it’s on, making a lot of pauses along the way.

doing this completely broke it. It just spams isClosed and isOpened all the time, regardless of its debounce.

RunService.Heartbeat:Connect(function()
	if isChasing == true and enemyRootPart.Anchored == false then
		local MainTorso = fetchPossibleTargets(enemyRootPart.Position)
		if MainTorso ~= nil then
			if heartbeatCooldown == false then
				heartbeatCooldown = false
				print("isOpened")
				currentlyWandering = 1

				local path = PathfindingService:CreatePath({
					AgentCanJump = true,
					Costs = {
						Water = 20
					},
					PathSettings = {
						SupportPartialPath = true
					}
				})

				destinedPath = PathfindingService:FindPathAsync(enemyRootPart.Position, MainTorso.Position)
				coordinatedWaypoints = destinedPath:GetWaypoints()
				discontinuedPoints = coordinatedWaypoints

				local function checkCurrentPath(t)
					local movementI = 3
					if movementI > #t then
						movementI = 3
					end
					if t[movementI] == nil and movementI < #t then
						repeat
							movementI = movementI + 1
							task.wait()
						until t[movementI] ~= nil
						return Vector3.new(1, 0, 0) + t[movementI]
					else
						movementI = 3
						return t[movementI]
					end
				end

				if destinedPath and coordinatedWaypoints or checkCurrentPath(coordinatedWaypoints) then
					if checkCurrentPath(coordinatedWaypoints) ~= nil and checkCurrentPath(coordinatedWaypoints).Action == Enum.PathWaypointAction.Walk then
						enemyHumanoid:MoveTo(checkCurrentPath(coordinatedWaypoints).Position)
					end

					if checkCurrentPath(coordinatedWaypoints) ~= nil and checkCurrentPath(coordinatedWaypoints).Action == Enum.PathWaypointAction.Jump then
						if isJumping == false then
							isJumping = true
							enemyHumanoid.Jump = true
							task.wait(1.6)
							isJumping = false
						end
						if checkCurrentPath(coordinatedWaypoints) then
							enemyHumanoid:MoveTo(checkCurrentPath(coordinatedWaypoints).Position)
						end
					end
				else
					for i = 3, #discontinuedPoints do
						enemyHumanoid:MoveTo(discontinuedPoints[i].Position)
					end
				end
				task.wait(5)
				heartbeatCooldown = true
				print("isClosed")
				heartbeatCooldown = false
			end
		elseif MainTorso == nil then
			currentlyWandering = 0
			destinedPath = nil
			coordinatedWaypoints = nil
		end
	end
end)

even if it’s not breaking, it will still keep calculating, making a lot of lags if other npcs are inside the workspace too, so I don’t see the point of doing this.

I did it chat. Instead of RunService.Heartbeat:Connect(function(), I did while RunService.Heartbeat:Wait() do, and now this works much better with 10+ NPCs. Thanks to everyone who read/replied.

1 Like

You need to check the debounce as the very first part, and rigth after the check, turn on the debounce. Your implementation is incorrect.

1 Like

that’s exactly what I did… even then, what’s the point doing it? Eventually more and more NPCs will spawn and also manipulate with their own debounce.

1 Like

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