Help optimizing npc

So I created a pretty good npc system but its still quite laggy with 100 npcs fighting eachother. Each NPC has about 3 actors, each doing collision checking, finding nearest target, and handling other stuff. The movement system is custom made with Roblox’s constraints including, AlignPosition (used to keep it on the ground), LinearVelocity (moving to a location), and AlignOrientation (facing the target). Everything inside the NPC has CanCollide, CanQuery, and CanTouch to false and since its a mesh, I set the collision to box. There is a cylinder inside the NPC which is the hitbox. When testing it, i’ve noticed lag coming from numbervalues and getting the nearest target. When they attack, they subtract the value from the numbervalue to their damage. I tried using Attributes but they are even laggier since they aren’t good for constant changing values. As for getting the nearest target, I loop through every NPC in a folder to get the nearest target. I tried using a module script inside ServerScriptService so that one script will update data about each NPC’s position but since I’m using actors, requiring that module would create seperate ones.

In studio, I get an average FPS of 50 or lower, but in-game, I get an average FPS of 100 or higher with about 50 ping. There are 100 NPC’s in the game that spawn which 50 are on each side fighting eachother. I was hoping to get more so I need help optimizing it with any suggestions.

Script stats:

NPC things:
image

Game:

So, the current problem that you’ve found is that changing the values of NumberValues very frequently is causing lag, correct?

1 Like

And you’re telling me that you can not use ModuleScripts?

I am using some but when having a module script holding all NPC’s position and having a script handle all the positions, requiring it from any scripts inside the actors creates a new seperate module so updating it with one script wouldn’t show it for other scripts that required it and are inside actors.

image

Like it would look something like this:

local RunService = game:GetService("RunService")

local ServerScriptService = game:GetService("ServerScriptService")

local PositionsModule = require(ServerScriptService:WaitForChild("PositionsModule"))

local NpcContainer = workspace:WaitForChild("NpcContainer")

RunService.Heartbeat:Connect(function()
	for i,v in ipairs(NpcContainer:GetChildren()) do
		if v.ClassName == "Model" and v.PrimaryPart then
			PositionsModule.Positions[v] = v.PrimaryPart.Position
		end
	end
end)

And when requiring from scripts inside actors, it gets required multiple times because it creates seperate modules since it runs independently:

image

local module = {}
module.Positions = {}

print("run")

return module

The code itself gets run multiple times, however, if multiple scripts require the same module, the data is preserved between all of them.
For example, if my module script looks like this:

local module = {}

module.num = 5

return module

And then I have two scripts, the first of them being:

local myModule = require(...)
myModule.num = 7

And the other one is:

local myModule = require(...)

task.wait(1)

print(myModule.num)

Then the output would show ‘7’, even tho the second script did not modify the module. A separate module isn’t created every time it is required

I don’t have any pathfinding in my script which reduces lag but I couldn’t find any better way to getting the nearest target as this was the fastest and less laggy way:

local module = {}

function module.GetNearestTarget(origin, availableTargets, character)
	task.desynchronize()

	local nearestTarget = nil
	local nearestDistance = math.huge

	local newOrigin = origin * Vector3.new(1, 0, 1)

	for i,v in ipairs(availableTargets) do
		if v.ClassName == "Model" and v.PrimaryPart and v ~= character then
			local targetPos = v.PrimaryPart.Position * Vector3.new(1, 0, 1)
			local distance = (targetPos - origin).Magnitude

			if distance < nearestDistance then
				nearestTarget = v
				nearestDistance = distance
			end
		end
	end

	task.synchronize()
	return nearestTarget
end

return module

I store available targets so I don’t keep having to get all targets again:

local function UpdateTargets()
	for i,v in ipairs(targetsContainer:GetChildren()) do
		if v.ClassName == "Model" and not table.find(availableTargets, v) then
			table.insert(availableTargets, v)
		end
	end
end

UpdateTargets()

targetsContainer.ChildAdded:Connect(function(child)
	if not table.find(availableTargets, child) then
		table.insert(availableTargets, child)
	end
end)

targetsContainer.ChildRemoved:Connect(function(child)
	if table.find(availableTargets, child) then
		table.remove(availableTargets, table.find(availableTargets, child))
	end
end)

I handle animations on the server because I need to detect if a keyframe is reached to deal damage:

connections["AttackCompleted"] = track.KeyframeReached:Connect(function(keyframeName)
										if keyframeName == "Attack" and target then
											if connections["AttackCompleted"] then
												connections["AttackCompleted"]:Disconnect()
											end

											Health.Value -= damage
										end
									end)

Doesn’t seem like it:

image

local module = {}

module.Number = math.random(1, 100)

return module
local test = require(game.ReplicatedStorage.ModuleScript)

print(test.Number)

Every time the module is required, the code gets run again, but if you have some data in it that doesn’t get overwritten every time the module is required, it’ll be preserved.

For example you could have one main script that sets all the tables and overhauls the entire module, and then other scripts that just require that module to access the data, then it would work.

Seems like it works fine on 2 normal scripts without an actor:

image

What about something like this just to demonstrate the effect.

local myModule = {}

myModule.num = 5

function myModule.Init()
    myModule.num = 7
end

return myModule

One script would call the Init function after every script has required the module and then the myModule.num value should change for every script

It wouldn’t work because actors are independent and so it has to require a new module so that syncronizing works.

Okay, to be honest, I’ve never worked with actors myself before, so I don’t really know how much they do and don’t change about things that do work with normal scripts. If you tried the example I gave with actors and are certain you did it right, by calling the Init() function after every other script has required it, and it didn’t work, then I unfortunately can’t help much.

If I followed along correctly, you appear to have placed all code under each individual enemy, thus 100 individual actors with all sorts of modules and scripts. I myself would never place scripts under anything that would be duplicated, let alone 100x’d, so why not just refer to the enemy in the thread that creates it and apply needed functions taken from one single copy of the needed modules?

Correct me if I’m wrong but you’re making things very complicated and not optimized. Say you add multiple enemy types, after a while you rewrite one of the 5 modules found in them, and have to apply the changes to every single enemy…

Well, okay, about finding the nearest target. This might be a little overkill, but if the amount of enemies on both sides can get a lot bigger, then you might want to look into more complex algorithms and data structures for finding the closest enemy for each npc. For example, a few are mentioned here c++ - Finding nearest point in an efficient way - Stack Overflow. I believe that if the amount of enemies starts getting higher, then the time complexity of generating a better data structure and finding the closest enemy to each npc every frame would be much faster than O(N^2) (aka for every npc, go through every enemy) every frame.