Help with Performance of this script

Help optimizing this script? It uses 33% script activity with 1500 npcs, is there a best way to do it?

local partCache = require(game:GetService("ReplicatedStorage"):WaitForChild("PartCache"))

local bodyParts = {} -- npc model --> {head, arm, arm, leg, leg}

local animT = 0
local animR = 0
local animCf = CFrame.Angles(0,0,0)
local animCfInv = CFrame.Angles(0,0,0) --                                                    * 1.57  == 180
game["Run Service"].PostSimulation:Connect(function(d) animT += d*40 animR = math.sin(animT) * .32 animCf = CFrame.Angles(animR,0,0) animCfInv = CFrame.Angles(-animR,0,0)  end)

local caches = {
	head = partCache.new(script.head,200,workspace.NPCS),
	rightarm = partCache.new(script.rightarm,200,workspace.NPCS),
	leftarm = partCache.new(script.leftarm,200,workspace.NPCS),
	rightleg= partCache.new(script.rightleg,200,workspace.NPCS),
	leftleg = partCache.new(script.leftleg,200,workspace.NPCS),
}

local function getBodyPart(bodyPartName:string)
	return caches[bodyPartName]:GetPart()
end

local function newNPC(model)
	local hum = model:WaitForChild("Humanoid")
	local root = model:WaitForChild("HumanoidRootPart")

	root.Transparency = 0
	local head = getBodyPart("head")
	local rightarm = getBodyPart("rightarm")
	local leftarm = getBodyPart("leftarm")
	local rightleg = getBodyPart("rightleg")
	local leftleg = getBodyPart("leftleg")

	head.Anchored = false
	rightarm.Anchored = false
	leftarm.Anchored = false
	rightleg.Anchored = false
	leftleg.Anchored = false
	
	head.Weld.Part0 = root
	rightarm.Weld.Part0 = root
	leftarm.Weld.Part0 = root
	rightleg.Weld.Part0 = root
	leftleg.Weld.Part0 = root
	if math.random(0,1)== 0 then
		local connection = game["Run Service"].PreSimulation:Connect(function(delta)
			--head.Weld.C0 = CFrame.new(0, 1, 0) * animCf
			rightarm.Weld.C0 =  CFrame.new(-1.5, 1, 0) * animCf 
			leftarm.Weld.C0 = CFrame.new(1.5, 1, 0) * animCfInv
			rightleg.Weld.C0 =  CFrame.new(-0.5, -1, 0) * animCf
			leftleg.Weld.C0 =  CFrame.new(0.5, -1, 0) * animCfInv
		end)
		bodyParts[model] = {head,rightarm,leftarm,rightleg,leftleg,connection}
	else
		local connection = game["Run Service"].PreSimulation:Connect(function(delta)
			--head.Weld.C0 = CFrame.new(0, 1, 0) * animCf
			rightarm.Weld.C0 =  CFrame.new(-1.5, 1, 0) * animCfInv 
			leftarm.Weld.C0 = CFrame.new(1.5, 1, 0) * animCf
			rightleg.Weld.C0 = CFrame.new(-0.5, -1, 0) * animCfInv
			leftleg.Weld.C0 =  CFrame.new(0.5, -1, 0) * animCf
		end)
		bodyParts[model] = {head,rightarm,leftarm,rightleg,leftleg,connection}
	end
	
end

local function removedNPC(model)
	if not bodyParts[model] then return end
	for i = 1,#bodyParts[model] do
		if typeof(bodyParts[model][i]) == "RBXScriptConnection" then
			bodyParts[model][i]:Disconnect()
		else
			caches[bodyParts[model][i].Name]:ReturnPart(bodyParts[model][i])
		end
		
	end
	bodyParts[model] = nil
end

workspace.NPCS.ChildAdded:Connect(function(npc)
	if not npc:IsA("Model") then return end
	newNPC(npc)
end)

workspace.NPCS.ChildRemoved:Connect(function(npc)
	if not npc:IsA("Model") then return end
	removedNPC(npc)
end)
1 Like

There’s a lot of small readability improvements you could do, but for performance, it would probably be good to reduce the number of connections, so that there is only one PreSimulation connection that updates all the NPCs instead of each NPC having it’s own connection. Something like:

local NPCs = {}

local function newNPC(model)
     
    -- replaces the whole `if math.random(0, 1) == 0 then` block
    NPCs[model] = {
        type = math.random(0, 1)
    }

    bodyParts[model] = {
        head = head,
        rightArm = rightarm,
        leftArm = leftarm,
        rightLeg = rightleg,
        leftLeg = leftleg
    }
end

local function removedNPC(model)
    -- after everything else
    NPCs[model] = nil
end

-- You should define the services you use in a variable instead of 
-- writing game["Run Service"] every time. Also, 
-- it is officialy recommended to use game:GetService, so 
-- game:GetService("RunService"), which I think looks better and
-- more readable
RunService.PreSimulation:Connect(function(DeltaTime)
    for model, npcData in NPCs do
        local npcParts = bodyParts[model]
        if npcData.type == 0 then
            npcParts.rightarm.Weld.C0 =  CFrame.new(-1.5, 1, 0) * animCf 
            npcParts.leftarm.Weld.C0 = CFrame.new(1.5, 1, 0) * animCfInv
            npcParts.rightleg.Weld.C0 =  CFrame.new(-0.5, -1, 0) * animCf
            npcParts.leftleg.Weld.C0 =  CFrame.new(0.5, -1, 0) * animCfInv
        else
            npcParts.rightarm.Weld.C0 =  CFrame.new(-1.5, 1, 0) * animCfInv 
            npcParts.leftarm.Weld.C0 = CFrame.new(1.5, 1, 0) * animCf
            npcParts.rightleg.Weld.C0 = CFrame.new(-0.5, -1, 0) * animCfInv
            npcParts.leftleg.Weld.C0 =  CFrame.new(0.5, -1, 0) * animCf
        end
    end
end)

You could even limit how many times per second the NPCs are updated by only letting the connection do anything when a certain amount of time has passed, like so:

-- the number after the `/` is how many times you want it to run per second
local AnimUpdateDelta = 1/10
local timeToUpdate = AnimUpdateDelta

RunService.PreSimulation:Connect(function(DeltaTime)
    timeToUpdate -= DeltaTime
    if timeToUpdate > 0 then
        return
    end
    
    timeToUpdate = AnimUpdateDelta
    
    -- code that only runs 10 times every second
    ...
end)

This should provide a significant performance improvement.

2 Likes