Hello, Cartoon_Corpze here.
So I managed to get about 1000 - 1500 NPCs running in a game, mostly without lag.
Though, I’m looking for more ways to optimize my NPCs.
Script activity stays mostly below 4%, I actually haven’t had many perfomance issues, I have a pretty decent gaming laptop and I even tested the game in a real server with someone else, there wasn’t a lot of lag, sometimes we had a ping ranging from 60 to 120 ms but that’s it pretty much.
Here is a gif.
https://gyazo.com/f2412779af094dda63771b08737cc8d2
I tried to make something looking a little like Dead Rising.
All NPCs are zombies and there is about 1500 of them currently.
Here is the code.
local targetradius = game:GetService("ReplicatedStorage").npcs.targetradius
local npcs = {}
local activeais = 0
local maxactiveais = 150
local airange = 60
local cf = CFrame.new
local v3 = Vector3.new
local random = math.random
--Functions.
--[[Main AI function]]--
local function createai(npc)
npcs[npc].main = function()
--local npctable = npcs[npc] --Used for breaking while loop.
local npc = npc
local human = npc.Humanoid
local root = npc.Torso
local target = npc.target
local active = npc.active
local wandering = false
local chasing = false
human.WalkSpeed = random(8, 15)
wait(random(1, 5))
local targetevent = target.Changed:Connect(function()
local targethuman = target.Value.Parent.Humanoid
local targetroot = target.Value --.Parent.HumanoidRootPart
if (not chasing) and targethuman.Health > 0 and activeais < maxactiveais then
chasing = true
wandering = false
human:MoveTo(targetroot.Position, targetroot)
active.Value = true
end
end)
local mtf = human.MoveToFinished:Connect(function()
if (not target.Value) and wandering then
active.Value = false
wandering = false
chasing = false
return
elseif chasing and target.Value and (target.Value.Position - root.Position).magnitude < airange and activeais <= maxactiveais then
active.Value = true
root.Anchored = false
human:MoveTo(target.Value.Position, target.Value)
end
wandering = false
end)
local activateevent = active.Changed:Connect(function()
if active.Value == true then
activeais = activeais + 1
root.Anchored = false
elseif active.Value == false then
activeais = activeais - 1
delay(1, function()
repeat wait(1) until root.Velocity.magnitude < 0.1
root.Anchored = true
end)
end
--print("Active AIs:", activeais, "/", maxactiveais)
end)
--Start of while loop.
while npc.Parent ~= nil and human.Health > 0 do
if activeais < maxactiveais / 1.5 and not chasing then
active.Value = true
root.Anchored = false
human:MoveTo(root.Position + v3(random(-20, 20), 0, random(-20, 20)))
wandering = true
end
wait(random(5, 10))
if npc.Torso.Anchored or npc.Torso.Velocity.magnitude < 0.05 then
active.Value = false
npc.Torso.Anchored = true
end
end
--End of while loop.
--Code below will run when NPC despawns.
print("NPC despawned.")
mtf:Disconnect()
activateevent:Disconnect()
targetevent:Disconnect()
print("Events disconnected.")
return
end
spawn(npcs[npc].main)
end
--[[Secondary AI function]]--
local function createai2(obj)
obj.Touched:Connect(function(hit)
wait(random(1, 10) / 10)
if hit.Parent ~= nil and hit.Parent.Parent == workspace.npcs then
local npc = hit.Parent
if not npc.target.Value then
npc.target.Value = obj.Parent.Parent.HumanoidRootPart
elseif (obj.Position - hit.Position).magnitude < (npc.target.Value.Position - hit.Position).magnitude then
npc.target.Value = obj.Parent.Parent.HumanoidRootPart
end
if npc.target.Value and (npc.target.Value.Position - hit.Position).magnitude < 4 and (not npc.attack.Value) then
npc.attack.Value = true
obj.Parent.Parent.Humanoid:TakeDamage(30)
delay(1.5, function()
npc.attack.Value = false
end)
end
end
end)
end
------------------------
--Events.
--NPC removed.
workspace.npcs.ChildRemoved:Connect(function(npc)
if npc.active.Value == true and activeais > 0 then
activeais = activeais - 1
end
for i = 1, #npcs do
if npcs[i].root == npc.Torso then
table.remove(npcs, i)
print("NPC removed.")
end
end
end)
--NPC added.
workspace.npcs.ChildAdded:Connect(function(obj)
if obj:FindFirstChild("Torso") then
npcs[obj] = {root = obj.Torso}
spawn(function()
--[[Humanoid states.]]--
--Disabled, unused.
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.FallingDown, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Flying, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Freefall, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
--?
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.PlatformStanding, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Landed, false)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false) --?
--Enabled.
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, true)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Running, true)
obj.Humanoid:SetStateEnabled(Enum.HumanoidStateType.RunningNoPhysics, true)
wait(5)
local t = tick() + 20
repeat
wait()
until obj.Torso.Velocity.magnitude < 0.01 or tick() > t
obj.Torso.Anchored = true
end)
createai(obj)
--print("NPC added.")
end
end)
--Player added.
workspace.players.ChildAdded:Connect(function(plr)
spawn(function()
local root = plr:WaitForChild("HumanoidRootPart", 60)
if not root then return end
local range = targetradius:Clone()
range.Parent = root
range.CFrame = root.CFrame
local weld = Instance.new("Motor6D")
weld.Part0 = root
weld.Part1 = range
weld.Parent = range
createai2(range)
end)
end)
while true do
wait(2)
print("Active AIs:", activeais, "/", maxactiveais)
end
Surprisingly the script activity is pretty low.
The zombies aren’t incredibly smart or do a lot of thinking, I haven’t really done anything with pathfinding.
I was purely experimenting how much humanoid NPCs I could have in a game having it run still pretty decent.
If you have any hints or tips for me on how to improve my work I’d gladly listen to it.
You may notice I haven’t scripted a way to kill the NPCs yet but that may come later.
Right now I’m focusing on perfomance and high NPC counts.
For so far the game can handle 1500 NPCs. (They chase you as well when nearby and damage.)
Edit: Forgot to mention that there also is a clientside version of the script but that’s mostly for visual effects and animations.