• Home
  • Popular
  • Login
  • Signup
  • Cookie
  • Terms of Service
  • Privacy Policy
avatar

Posted by User Bot


11 Jan, 2025

Updated at 16 Jan, 2025

How do i optimize npcs on the server side?

I have a script that can control multiple npcs at once, and i manage to get over 100 npcs with a little lag, the console shows about 18~20% rate on the scripts tab. But how do i optimize this even more?

I did read some articles about the optimizing npcs by disable some unused states, remove some humanoid properties thats not needed, but it still not reduce that much lags from many npcs.

Script:

local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local stunmodule = require(game.ReplicatedStorage.EffectsDebuff)
local AI = require(game.ReplicatedStorage.SimplePath)
local muchachohitbox = require(game.ReplicatedStorage.MuchachoHitbox)
local dmggoal = require(game.ReplicatedStorage.DamageCalculator)
local fastskill = require(game.ReplicatedStorage.Skill)

local ZOMBIE_TAG = "zombie"
local ATTACK_RADIUS = 5
local DAMAGE = 5.5
local WALKER_ATTACK_COOLDOWN = 1.25
local FAST_ATTACK_COOLDOWN = 3

local function getPlayersInWorkspace()
	local playersInWorkspace = {}
	for _, player in ipairs(Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChild("HumanoidRootPart") then
			table.insert(playersInWorkspace, character)
		end
	end
	return playersInWorkspace
end

local function getNearestPlayer(zombie)
	local root = zombie:FindFirstChild("HumanoidRootPart")
	if not root then return nil end

	local closestPlayer, closestDistance = nil, 800
	for _, character in ipairs(getPlayersInWorkspace()) do
		local targetRoot = character:FindFirstChild("HumanoidRootPart")
		local targetHumanoid = character:FindFirstChild("Humanoid")
		if targetRoot and targetHumanoid and targetHumanoid.Health > 0 then
			local distance = (root.Position - targetRoot.Position).Magnitude
			if distance < closestDistance then
				closestPlayer, closestDistance = targetRoot, distance
			end
		end
	end
	return closestPlayer
end

local function cloneStuffs(instance, base)
	local clonedInstance = instance:Clone()
	clonedInstance.Parent = base
	if clonedInstance:IsA("Sound") then
		clonedInstance:Play()
		game.Debris:AddItem(clonedInstance, clonedInstance.TimeLength)
	end
end

local function attackStunZombie(zombie, target)
	local canAttack = zombie:GetAttribute("CanAttack")
	local lastAttackTime = zombie:GetAttribute("LastAttackTime") or 0

	local root = zombie:FindFirstChild("HumanoidRootPart")
	local humanoid = zombie:FindFirstChild("Humanoid")
	if not root or not humanoid or humanoid.Health <= 0 or not canAttack then return end

	if os.clock() - lastAttackTime < FAST_ATTACK_COOLDOWN then return end

	local attackAnim = root:FindFirstChild("punch")
	local attackTrack = attackAnim and humanoid:LoadAnimation(attackAnim)

	zombie:SetAttribute("LastAttackTime", os.clock())

	if attackTrack then attackTrack:Play() end
	
	local pushForce = Instance.new("LinearVelocity")
	local attachment = Instance.new("Attachment",root)
	pushForce.Attachment0 = attachment
	pushForce.MaxForce = 10000
	pushForce.VectorVelocity = (target.Position - root.Position).Unit * 70
	pushForce.Parent = root
	game.Debris:AddItem(pushForce, 0.15)
	game.Debris:AddItem(attachment,0.3)
	
	local fasthitbox = muchachohitbox.CreateHitbox()
	fasthitbox.Size = zombie:FindFirstChild("Head").Size
	fasthitbox.CFrame = zombie:FindFirstChild("Head")
	fasthitbox.Visualizer = true
	fasthitbox.OverlapParams = OverlapParams.new({
		FilterType = Enum.RaycastFilterType.Exclude,
		FilterDescendantsInstances = CollectionService:GetTagged(ZOMBIE_TAG)
	})

	local debounceTable, hitboxActive = {}, false
	
	task.spawn(function()
		fasthitbox.Touched:Connect(function(hit, hum)
			print(hit,hum,hit.Parent)
			if hitboxActive then return end
			local hitParent = hit:FindFirstAncestorOfClass("Model")
			if hitParent and not hitParent:GetAttribute("zombie") and hum and hum.Health > 0 and not hitParent:GetAttribute("Tackling") then
				if not debounceTable[hitParent] then
					hitboxActive = true
					debounceTable[hitParent] = true
					fastskill:Maul(zombie, hitParent)
					task.delay(0.65, function()
						debounceTable[hitParent], hitboxActive = nil, false
					end)
				end
			end
		end)
	end)
	
	task.delay(0.3, function()
		fasthitbox:Start()
		task.wait(0.35)
		if getmetatable(fasthitbox) then fasthitbox:Stop() end
	end)
end

local function attackZombie(zombie, target)
	local root, humanoid = zombie:FindFirstChild("HumanoidRootPart"), zombie:FindFirstChild("Humanoid")
	if not root or not humanoid then return end

	local lastAttackTime = zombie:GetAttribute("LastAttackTime") or 0
	local canAttack = zombie:GetAttribute("CanAttack")
	if os.clock() - lastAttackTime < WALKER_ATTACK_COOLDOWN or not canAttack then return end

	zombie:SetAttribute("LastAttackTime", os.clock())

	local attackAnim = root:FindFirstChild("punch")
	local attackAnim2 = root:FindFirstChild("punch2")
	local attackTrack = attackAnim and humanoid:LoadAnimation(attackAnim)
	local attackTrack2 = attackAnim2 and humanoid:LoadAnimation(attackAnim2)

	local randomizeattack = math.random(1,2)
	if randomizeattack == 1 then
		if attackTrack then attackTrack:Play() end
	elseif randomizeattack == 2 then
		if attackTrack2 then attackTrack2:Play() end
	end

	local defaulthitbox = muchachohitbox.CreateHitbox()
	defaulthitbox.Size = Vector3.new(5,6,5)
	defaulthitbox.Offset = CFrame.new(0,0,-1)
	defaulthitbox.CFrame = root
	defaulthitbox.Visualizer = false
	
	local debounceTable, hitdb = {}, false

	task.spawn(function()
		defaulthitbox.Touched:Connect(function(target, hum)
			if hitdb then return end
			local targetCharacter, targetHumanoid = target.Parent, target.Parent:FindFirstChild("Humanoid")

			local victimstats = targetCharacter:FindFirstChild("Stats")
			local defensive = victimstats and victimstats.Defensive.Value

			if targetHumanoid and targetHumanoid.Health > 0 and humanoid.Health > 0 and not targetCharacter:GetAttribute("zombie") then
				if not debounceTable[targetCharacter] then
					hitdb = true
					debounceTable[targetCharacter] = true
					local finaldamage = dmggoal:Calculate(DAMAGE,1,defensive,nil,targetCharacter)
					targetHumanoid:TakeDamage(finaldamage)
					cloneStuffs(root:FindFirstChild("hit"), targetHumanoid)
					targetCharacter:SetAttribute("infectedrate", (targetCharacter:GetAttribute("infectedrate") or 0) + 1.5)
					task.wait(0.5)
					debounceTable[targetCharacter], hitdb = nil, false
				end
			end
		end)
	end)

	defaulthitbox:Start()
	task.delay(0.35, function()
		if getmetatable(defaulthitbox) then defaulthitbox:Stop() end
	end)
end

local function updateZombie(zombie)
	local humanoid, root = zombie:FindFirstChildOfClass("Humanoid"), zombie:FindFirstChild("HumanoidRootPart")
	if not humanoid or not root then return end

	local path = AI.new(zombie, {
		AgentRadius = 3,
		AgentHeight = 6,
		AgentCanJump = true,
		AgentJumpHeight = 7,
		AgentMaxSlope = 45,
		WaypointSpacing = 50,
		Costs = { Water = 1, Neon = 1 }
	})

	humanoid.Died:Connect(function()
		if CollectionService:HasTag(zombie, ZOMBIE_TAG) then
			CollectionService:RemoveTag(zombie, ZOMBIE_TAG)
		end
		if path.Status == AI.StatusType.Active then
			path:Stop() 
		end
	end)
	
	humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing,false)
	humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming,false)
	humanoid:SetStateEnabled(Enum.HumanoidStateType.Seated,false)

	while task.wait(0.1) do
		if humanoid.Health <= 0 then break end

		local target = getNearestPlayer(zombie)
		if not target then continue end

		local distance = (root.Position - target.Position).Magnitude

		task.spawn(function()
			if distance <= ATTACK_RADIUS and zombie:GetAttribute("walker") then
				attackZombie(zombie, target)
			elseif distance <= 15 and zombie:GetAttribute("fast") then
				attackStunZombie(zombie, target)
			end
		end)

		task.spawn(function()
			if distance < 20 then
				humanoid:MoveTo(target.Position)
				if path.Blocked then
					path:Run(target)
				end
			else
				path:Run(target)
			end
		end)
	end
end

CollectionService:GetInstanceAddedSignal(ZOMBIE_TAG):Connect(function(zombie)
	zombie:SetAttribute("LastAttackTime", os.clock())
	task.spawn(function()
		updateZombie(zombie)
	end)
	
	print("spawned")
end)

for _, zombie in ipairs(CollectionService:GetTagged(ZOMBIE_TAG)) do
	zombie:SetAttribute("LastAttackTime", os.clock())
	task.spawn(function()
		updateZombie(zombie)
	end)
end

1 post - 1 participant

Read full topic