Many NPC's start to lag

I have no idea how to fix this. Basically when many NPC’s start to follow you the game begins to lag for everyone.

local larm = script.Parent:FindFirstChild("HumanoidRootPart")
local rarm = script.Parent:FindFirstChild("HumanoidRootPart")

function findNearestTorso(pos)
	local list = game.Workspace:children()
	local torso = nil
	local dist = 40
	local temp = nil
	local human = nil
	local temp2 = nil
	for x = 1, #list do
		temp2 = list[x]
		if (temp2.className == "Model") and (temp2 ~= script.Parent) then
			temp = temp2:findFirstChild("HumanoidRootPart")
			human = temp2:findFirstChild("Humanoid")
			if (temp ~= nil) and (human ~= nil) and (human.Health > 0) then
				if (temp.Position - pos).magnitude < dist then
					torso = temp
					dist = (temp.Position - pos).magnitude
				end
			end
		end
	end
	return torso
end

while true do
	wait(math.random(0.5,0.5))
	local target = findNearestTorso(script.Parent.HumanoidRootPart.Position)
	if target ~= nil then
		script.Parent.FakeHead.Transparency = 0
		script.Parent.Enemy:MoveTo(target.Position, target)
	end
end

That is the follow script for the NPC
I think it is because I’m using separate scripts for every humanoid instead of making one script control their movements. But I’m not really sure. Some said to use custom scripts for humanoids but idk how to do that so would just having one script control them fix this?

Hello!

Just a suggestion: When listing code, please use the formatting for it. In our case, it would be three of these little guys at the start of the code and three at the end: `

Demo

Thanks!

Calling your function each 0.5s is never a great idea in terms of lag. Try augmenting your wait time.

The problem here is the math.random(0.5, 0.5). Math.random only supports whole numbers, so this’ll end up being 0 every time it’s called - giving it nearly no wait time.

If you want something like 0.5, you can do this:
math.random(5,5)/10
Which gets around that. Then that should give you your expected result.

Too many humanoids cause lag, and that’s why you need an alternative method to create a huge amount of NPCs.

Perhaps the solution is found here: https://devforum.roblox.com/t/alternatives-to-humanoids/17151?u=operatik

Or here, more text to read:
https://devforum.roblox.com/t/alternate-solution-for-a-humanoid-to-create-natural-moving-pet/148866?u=operatik

And finally, Luanoid: https://devforum.roblox.com/t/humanoidless-characters/120428/4?u=operatik

Protip: Search for topics in DevForum to find the solution; if not then create a new one.

4 Likes

First of all; your Humanoid list is being fetched by iterating over the entire Workspace. Depending on how many immediate descendants (children) of the Workspace you have, the list that the script needs to work with grows absurdly. That’s added the fact that it performs this operation in a cycle that lasts less than a full second.

You have a few ways of changing the line list = game.Workspace:children() to stop it from polling a massive amount of instances at a time. As well, there are some issues with this code itself, albeit minor.

Issues:

  • game.Workspace is a “legacy” method of indexing the Workspace. You should either use the workspace variable that references it, or game:GetService("Workspace") as the canonical way to fetch a service.
  • Instance:children() was deprecated in favour of Instance:GetChildren() for better naming convention. Try to avoid using deprecated methods, as it is good practice.

Potential Solutions:

  • Place any relevant NPCs in a Folder and change the list to iterate through the children of that folder instead
  • Create a ModuleScript that handles the gathering of any Humanoid figures in the game and polls an update of the list every few seconds (I usually go with 3); replace this line to fetch the Humanoid [1]
  • Use the CollectionService to tag any Humanoids currently in or added to the game, then iterate through any tagged Humanoids via CollectionService:GetTagged("YOUR_TAG_NAME")

[1]:

local Module = {}
local HumanoidList = {}

local function UpdateList()
    while true do
        HumanoidList = {}

        for _, Member in pairs(SOME_LOCATION) do
            if Member:FindFirstChild("Humanoid") then
                table.insert(HumanoidList, Member.Humanoid)
            end
        end

        wait(3)
    end
end

spawn(UpdateList)

function Module.GetList()
    return HumanoidList
end

function Module.GetCharacterModelFromHumanoid(Humanoid)
    return Humanoid:FindFirstAncestorOfClass("Model")
end

return Module

Caveats with the above code:

  • There are no practical application examples; it’s up to you to determine how to use or modify the above code to suit your needs, if you go with a ModuleScript approach
  • I do not do any type checking or argument validation; I simply assume that every use of the ModuleScript is properly done
  • The update function itself still poses the same problem of determining where it’s appropriate to fetch your list from. See my potential solutions above
  • As this is just an example and not something I put effort into, it’s not necessarily the best piece of work. I encourage you to refactor it yourself or apply another system that’s still capable of retrieving characters and/or their humanoids

After all that’s said and done, that’s the main point of concern that is really to be taken up. There’s just a couple more things that I would like to point out.

This is going to evaluate to 0, so your while loop is going to inherit the minimum from wait(N). You can fix this in three ways that I know of:

  1. Use whole numbers and divide. math.random(5, 5)/10
  2. Use the Random API. Random.new():NextNumber(0.5, 0.5)
  3. Just use wait(0.5) and get rid of the random statement altogether.

The fact that your code runs this fast while being expensive is one of the largest sources of lag for your game. If you can fix the function to not iterate through the workspace for NPCs and change this interval, you should be good.

This wouldn’t cause lag. The main issue is the code you’re using itself. So long as you aren’t running any expensive functions frequently or using code that prompts red flags for lag, you’ll be fine. A singular control script is usually nice for certain things but if you can’t do it, don’t worry about it yet. To me, this kind of thing just serves as a way to streamline behaviour and update one thing if I have to update multiple; it still requires extra work to accommodate for specialised settings per NPC or whatever.

I’m not sure what they meant by “custom scripts”, since there’s literally only LuaSourceContainers available to you. This advice is… well, fairly useless, especially if they can’t give a reasoning or explanation behind what they’ve said, much less if they even know what they’re talking about.


It depends. There is nothing inherently bad with setting a low interval for a while loop. What causes a problem is if you’re running expensive functions within those intervals, which is the main produce of lag. In the case that you have lag for small loops, you either have to think about the efficiency of the functions you’re running or a longer interval (which is typically just a band-aid solution, since the expensive function would still exist).

In the instance that a zero value is passed to wait(N), it’ll use the minimum. The interval isn’t the only concern, though having a decent interval is surely appreciated when it comes to looping code (depending on the use case, it changes). The main problem is a combination of both an extremely low interval between each loop and the function being called in it.

Humanoids themselves are expensive but that’s in terms of the coding department and a majority of that expense is handled in the backend. There’s another underlying issue and that stems from the script being used itself, which is from a legacy era of NPCs.

11 Likes

I would really suggest numerically calculating each humanoids position and position to walk to, and then sending these coordinates to the client. Then have your NPC models/humanoids/whatever rendered client side. Save your server memory and resources.

I really suggest using LOD or a type of render distance. NPC’s from far-far away have little to no animations or decreasing details. Disabling some unnecessary elements in the humanoid element itself may also help to prevent lag.

You should also have a limit of how much and how many NPC’s can render and or load into the game to prevent any problems.

(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)

In that case, it is completely irrelevant to the OP to talk about how he is accessing the workspace, especially since there are no significant differences between how one accesses it besides personal preference.

I’m sorry for dragging this thread a bit off-the-rails.

1 Like

If you still need information: You can change the loop for the workspace to looping through the players

To be more specific: game.Players:GetPlayers() do

it isn’t as crazy as looping through a workspace, and will definitely lag less