Game has EXTREMELY high recieve sometimes and very high ping the longer you play. can you help me optimize lag?

I’m making a game about battling many npcs in close combat, and as soon as i play test the game, the receive starts at a crazy 80kb!

it decreases 30 sec into the game, but increases to almost 600kb at some points. the ping also slowly increases, as i got around 600 ping.

i’m constantly rendering all npc’s hitboxes on the server, and the attack script for the player creates hitboxes on the server, too.

all the npcs have humanoids, and there aren’t very many of them at a time, (10-40 at a time usually) and i STILL get so much lag and ping! can someone please help me?

1 Like

i was thinking of replicating all the npcs and their hitboxes on the client and taking weight off of the server but idk at all how to do that :skull:

Hold on, are you checking for collision for EACH NPC or for the player’s weapon only?

what do you mean?

worrddsssssssssssss

Like are you checking if each NPC hit the player’s weapon, or is the player’s weapon responsible for checking which NPC it hits?

the player’s weapon creates 15 hitboxes and creates a table for npcs hit. if an npc gets hit ittl get added to the table and all of the hitboxes cannot damage that npc anymore. it checks for any body part of the npc

the hitbox of the weapon checks for the npc

honestly for me to help you, I’d need a rbxl file or something to look at the scripts.

local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath()
local reachedConnection
local modelFolder = game.ReplicatedStorage.ModelFolder

local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local HitboxCooldown = false

local npcmodule = {}




local function ClosestPlayer(pos)
	local target
	local closest
	local lastdist = 99999999999999


	for i,z in ipairs(game.Players:GetChildren()) do
		local current = z.Character
		if current then

			local dist = (current.HumanoidRootPart.Position - pos).Magnitude
			if dist < lastdist then
				lastdist = dist
				closest = current
			end
		end
	end


	target = closest
	return target
end

--local function walkTo(targetPosition, yieldable, character, humanoidRootPart, humanoid)
--	local RETRY_NUM = 0 
--	local success, errorMessage

--	repeat
--		RETRY_NUM += 1 -- add one retry
--		success, errorMessage = pcall(path.ComputeAsync, path, humanoidRootPart.Position, targetPosition)
--		if not success then -- if it fails, warn the message
--			warn("Pathfind compute path error: "..errorMessage)
--			task.wait(RETRY_COOLDOWN)
--		end
--	until success == true or RETRY_NUM > MAX_RETRIES

--	-- if computing the path has no issues

--	if success then
--		if path.Status == Enum.PathStatus.Success then
--			local waypoints = path:GetWaypoints()
--			local currentWaypointIndex = 2

--			if not reachedConnection then
--				reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
--					if reached and currentWaypointIndex < #waypoints then
--						currentWaypointIndex += 1

--						humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
--						if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
--							humanoid.Jump = true
--						end
--					end
--				end)
--			end

--			humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
--			if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
--				humanoid.Jump = true


--			else
--				return 
--			end
--		else -- if the path can't be computed between two points, do nothing!
--			return 
--		end
--	else -- this only runs IF the function has problems computing the path in its backend, NOT if a path can't be created between two points.
--		warn("Pathfind compute retry maxed out, error: "..errorMessage)

--		return
--	end
--end

local function PlayAnimation(character,id)
	local animator = character:FindFirstChild("Humanoid") and character.Humanoid:FindFirstChild("Animator")
	if animator then
		local animation = Instance.new("Animation")
		animation.AnimationId = "rbxassetid://"..id
		local animationTrack = animator:LoadAnimation(animation)
		animationTrack:Play()
		return animationTrack
	end
end


---- Npc Abilities ----

function npcmodule.Ask(character)
	if not character.PrimaryPart then
		return
	end
	local ask = modelFolder:FindFirstChild("None000Ask"):Clone()
	local id = 77050504380973
	local still = false
	
	ask.Parent = workspace
	ask.Position = character.PrimaryPart.Position + Vector3.new(0,7,0)
	game.Debris:AddItem(ask,4)
	character.PrimaryPart.Anchored = true
	local animation = PlayAnimation(character, id)
	character.PrimaryPart.Orientation = Vector3.new(character.PrimaryPart.Orientation.X,character.PrimaryPart.Orientation.Y,0)
	still = true
	character.Humanoid:GetPropertyChangedSignal("Health"):Connect(function()
		if still == true and character.Humanoid.Health > 0 then
			ask:Destroy()
			still = false
			animation:Stop()
			animation:Destroy()
			local rageAnim = PlayAnimation(character, 77924139036595)
			
			ask:Destroy()
			character.PrimaryPart.Anchored = true
			character.PrimaryPart:FindFirstChild("Angry"):Play()
			character.Humanoid.WalkSpeed += 8
			character["Body Colors"].HeadColor = BrickColor.new("Really red")
			character["Body Colors"].RightLegColor = BrickColor.new("Really red")
			character["Body Colors"].LeftLegColor = BrickColor.new("Really red")
			character["Body Colors"].LeftArmColor = BrickColor.new("Really red")
			character["Body Colors"].RightArmColor = BrickColor.new("Really red")
			local fire = Instance.new("Fire")
			fire.Parent = character.Head
			rageAnim.Ended:Wait()
			rageAnim:Destroy()
			local explosion = Instance.new("Explosion")
			explosion.Parent = character.PrimaryPart
			explosion.Position = character.PrimaryPart.Position
			explosion.BlastPressure = 0
			explosion.BlastRadius = 0
			character.PrimaryPart.Explode:Play()
			character.Humanoid.Health += 30
			game.Debris:AddItem(explosion, 3)
			character.PrimaryPart.Anchored = false
			return
		end
	end)
	wait(3)
	animation:Stop()
	animation:Destroy()
	if still == true then
		local healclone = modelFolder.Heal:Clone()
		healclone.Parent = character.PrimaryPart
		healclone:Emit(30)
		character.PrimaryPart.Heal:Play()
		character.Humanoid.Health += 20
		game.Debris:AddItem(healclone,4)
	end
	still = false
	wait(0)
	ask:Destroy()
	character.PrimaryPart.Anchored = false
end

------

function npcmodule.Run(character)
	if not character.PrimaryPart then
		return
	end
	local closestPlayer = ClosestPlayer(character.PrimaryPart.Position)
	local createdNPC = workspace.CreatedNPCFolder
	local closestNpc = nil
	local random = math.random(1, #createdNPC:GetChildren())
	for i,child in ipairs(createdNPC:GetChildren()) do
		if i == random then
			closestNpc = child
		end
	end
	if character.Humanoid.Health == 1 then
		if not closestNpc.PrimaryPart then
			return
		end
		character.Humanoid:MoveTo(closestNpc.PrimaryPart.Position)
		character.Humanoid.MoveToFinished:Wait()
	else
		return
	end
end

---- Npc Abilities ----

local function npcAbilityHandler(character)
	local i = 0
	local config = character:FindFirstChild("Configuration")
	local ability1
	local cooldown1
	if config then
		if config:FindFirstChild("Ability1") then
			ability1 = config:FindFirstChild("Ability1")
			cooldown1 = ability1:FindFirstChild("Cooldown")
		end
	end
	while true do
		if character.Humanoid.Health < 1 then
			return
		end
		i += 1
		wait(1)
		if ability1 then
			if i % cooldown1.Value == 0  then
				local connection = npcmodule[ability1.Value](character)
				character.Humanoid.Died:Connect(function()
					if connection then
						connection:Disconnect()
					end
					return
				end)
			end
		end
	end
end


function npcmodule.npcMain(character)
	
	local hitbox = Instance.new("Part")
	local hitboxTouched = false
	hitbox.Size = Vector3.new(3.5*character:GetScale(),4*character:GetScale(),3.5*character:GetScale())
	hitbox.Anchored = true
	hitbox.CanCollide = false
	hitbox.Transparency = 1
	hitbox.Material = Enum.Material.ForceField
	hitbox.Parent = workspace
	hitbox.Color = Color3.new(1, 0, 0)
	
	local npcabilityConnection = coroutine.create(npcAbilityHandler)
	while true do
		game["Run Service"].Heartbeat:Wait()
		if character and character:FindFirstChild("HumanoidRootPart") and character.PrimaryPart and character.Humanoid.Health > 0 then
			if character.Humanoid.Health < 1 then
				hitbox:Destroy()
				npcabilityConnection:Disconnect()
				return
			end
			local configuration = character:FindFirstChild("Configuration")
			hitbox.Position = character.PrimaryPart.Position
			local target = ClosestPlayer(character.PrimaryPart.Position)
			if target then
				character.Humanoid:MoveTo(target.PrimaryPart.Position)
				for i,z in ipairs(character:GetChildren()) do
					if z:IsA("BasePart") then
						z:SetNetworkOwner(game.Players:GetPlayerFromCharacter(target))
					end
				end
				hitbox.Touched:Connect(function(hit)
					if hit.Parent == target and HitboxCooldown == false and hit.Parent.Parent ~= workspace.CreatedNPCFolder then
						HitboxCooldown = true
						hit.Parent.Humanoid.Health -= configuration.Damage.Value
						wait(configuration.DamageCooldown.Value)
						HitboxCooldown = false
					end
				end)
			end
		else
			hitbox:Destroy()
			coroutine.yield(npcAbilityHandler)
			return
		end
	end
end

return npcmodule

here’s the npcs module

yap yap yappity yap

don’t mind the commented part, that’s just a failed pathfinding attempt that broke the game.

Why 15 hitboxes? I assume from your original post this is being done from the server right?
You can probably just send the player’s position when they use the weapon (and maybe if it’s a gun, the player’s look direction and position) and have the server only VALIDATE the data.

You can use workspace:GetPartBoundsInBox

ok bro why do you have a value thats just a really big number

what? do you mean the number for ability checking

local Cooldown = false
local cooldownTime = 0.25

local Flip = false

script.Parent.Activated:Connect(function()
	local Char = script.Parent.Parent
	if Cooldown == false then
		Cooldown = true
		local alrHit = {}
		local hitsoundPlayed = false
		if Flip == false then
			local animTrack = Char.Humanoid.Animator:LoadAnimation(script.SlashAnimation)
			animTrack:Play()
			Flip = true
		else
			local animTrack = Char.Humanoid.Animator:LoadAnimation(script.MirrorSlashAnimation)
			animTrack:Play()
			Flip = false
		end
		task.wait(0.3)
		local swing = script.Swing:Clone()
		swing.Parent = Char.PrimaryPart
		swing:Play()
		game.Debris:AddItem(swing,2)
		
		for i=1,15 do
			local hitbox = Instance.new("Part")
			local hitboxTouched = false
			hitbox.CFrame = Char:GetPivot() * CFrame.new(0,0,-3)
			hitbox.Size = Vector3.new(4,5,4)
			hitbox.Anchored = true
			hitbox.CanCollide = false
			hitbox.Material = Enum.Material.ForceField
			hitbox.Parent = workspace
			hitbox.Color = Color3.new(1, 0, 0)


			--get parts in the hitbox
			local parts = workspace:GetPartsInPart(hitbox)
			--check if the part is a humanoid
			for _, part in pairs(parts) do
				if part.Parent:FindFirstChild("Humanoid") and hitboxTouched == false and part.Parent ~= Char and not table.find(alrHit, part.Parent) then
					part.Parent.Humanoid.Health -= 20
					if hitsoundPlayed == false then
						local hit = script.Hit:Clone()
						if part.Parent and part.Parent:FindFirstChild("Torso") then
							hit.Parent = part.Parent.Torso
						else
							hit.Parent = Char.PrimaryPart
						end
						hit:Play()
						game.Debris:AddItem(hit,2)
					end
					hitsoundPlayed = true
					local Speed = 40
					local Force = 80000

					local TotalForce = Force

					local KnockBack = Instance.new("BodyVelocity")--part is the target of the knockback/ the opponent

					KnockBack.MaxForce = Vector3.new(TotalForce,TotalForce,TotalForce)
					KnockBack.Velocity = Char:FindFirstChild("HumanoidRootPart").CFrame.LookVector * Speed -- based on the direction YOUR character is facing.
					if part.Parent:FindFirstChild("HumanoidRootPart") then
						KnockBack.Parent = part.Parent.HumanoidRootPart
					end
					game.Debris:AddItem(KnockBack,0.1)
					table.insert(alrHit, part.Parent)
					hitboxTouched = true
					hitbox.Color = Color3.new(0.0741741, 1, 0.000198367)
				end
			end
			game.Debris:AddItem(hitbox,1)
			wait(0.0001)
		end
	
	wait(cooldownTime)
	Cooldown = false
	end
end)

here's punch

this is the punch script
it’s kinda messy

Why do you have 15 hitboxes that both take a second to decay?? Why not just use 1 singular hitbox? That’s throwing extra load for nothing.

before you tell me, i’m pretty sure games like die of death use many hitboxes at a time created on the server

i saw this coming from your original comment.
THAT’S BECUSE THEY USE A MODULE.

I don’t know what Forsaken or Die of Death use, but I assume it would be something like this.

The reason why your game is lagging is because your entire code is a loop that loops other loops that create connections with loops.

I’d highly, HIGHLY, recommend reconsidering your code.

Small things would just be to use task.wait rather than wait, but thats not going to fix the major problems:

Creation (anything instance.new related):
You’re creating parts of the hitbox on demand, then destroying them right after, over and over. Consider utilizing object pooling rather than creating, connecting, destroying. This also goes for other instances that you may need to create again, considering finding ways to re-utilize the instance instead of creating a new one every time.

Connections inside loops inside connections:

while true do
		if character.Humanoid.Health < 1 then
			return
		end
		i += 1
		wait(1)
		if ability1 then
			if i % cooldown1.Value == 0  then
				local connection = npcmodule[ability1.Value](character)
				character.Humanoid.Died:Connect(function()
					if connection then
						connection:Disconnect()
					end
					return
				end)
			end
		end
	end

This is a red flag, you have a connection loop inside a while true do loop that is constantly checking the health AND creating a connection that checks when the player dies.

function npcmodule.Run(character)
	if not character.PrimaryPart then
		return
	end
	local closestPlayer = ClosestPlayer(character.PrimaryPart.Position)
	local createdNPC = workspace.CreatedNPCFolder
	local closestNpc = nil
	local random = math.random(1, #createdNPC:GetChildren())
	for i,child in ipairs(createdNPC:GetChildren()) do
		if i == random then
			closestNpc = child
		end
	end
	if character.Humanoid.Health == 1 then
		if not closestNpc.PrimaryPart then
			return
		end
		character.Humanoid:MoveTo(closestNpc.PrimaryPart.Position)
		character.Humanoid.MoveToFinished:Wait()
	else
		return
	end
end

For loops can be laggy (depending on context)
You are calling this function somewhere (idk where, Im not looking to closely) and considering the name of the function, im assuming you’re calling this on 10+ npcs as you mentioned.

function npcmodule.Ask(character)
	if not character.PrimaryPart then
		return
	end
	local ask = modelFolder:FindFirstChild("None000Ask"):Clone()
	local id = 77050504380973
	local still = false
	
	ask.Parent = workspace
	ask.Position = character.PrimaryPart.Position + Vector3.new(0,7,0)
	game.Debris:AddItem(ask,4)
	character.PrimaryPart.Anchored = true
	local animation = PlayAnimation(character, id)
	character.PrimaryPart.Orientation = Vector3.new(character.PrimaryPart.Orientation.X,character.PrimaryPart.Orientation.Y,0)
	still = true
	character.Humanoid:GetPropertyChangedSignal("Health"):Connect(function()
		if still == true and character.Humanoid.Health > 0 then
			ask:Destroy()
			still = false
			animation:Stop()
			animation:Destroy()
			local rageAnim = PlayAnimation(character, 77924139036595)
			
			ask:Destroy()
			character.PrimaryPart.Anchored = true
			character.PrimaryPart:FindFirstChild("Angry"):Play()
			character.Humanoid.WalkSpeed += 8
			character["Body Colors"].HeadColor = BrickColor.new("Really red")
			character["Body Colors"].RightLegColor = BrickColor.new("Really red")
			character["Body Colors"].LeftLegColor = BrickColor.new("Really red")
			character["Body Colors"].LeftArmColor = BrickColor.new("Really red")
			character["Body Colors"].RightArmColor = BrickColor.new("Really red")
			local fire = Instance.new("Fire")
			fire.Parent = character.Head
			rageAnim.Ended:Wait()
			rageAnim:Destroy()
			local explosion = Instance.new("Explosion")
			explosion.Parent = character.PrimaryPart
			explosion.Position = character.PrimaryPart.Position
			explosion.BlastPressure = 0
			explosion.BlastRadius = 0
			character.PrimaryPart.Explode:Play()
			character.Humanoid.Health += 30
			game.Debris:AddItem(explosion, 3)
			character.PrimaryPart.Anchored = false
			return
		end
	end)
	wait(3)
	animation:Stop()
	animation:Destroy()
	if still == true then
		local healclone = modelFolder.Heal:Clone()
		healclone.Parent = character.PrimaryPart
		healclone:Emit(30)
		character.PrimaryPart.Heal:Play()
		character.Humanoid.Health += 20
		game.Debris:AddItem(healclone,4)
	end
	still = false
	wait(0)
	ask:Destroy()
	character.PrimaryPart.Anchored = false
end

For this function, you’re checking every time if the health changes to 0, couldnt you just check if the humanoid dies?

function npcmodule.npcMain(character)
	
	local hitbox = Instance.new("Part")
	local hitboxTouched = false
	hitbox.Size = Vector3.new(3.5*character:GetScale(),4*character:GetScale(),3.5*character:GetScale())
	hitbox.Anchored = true
	hitbox.CanCollide = false
	hitbox.Transparency = 1
	hitbox.Material = Enum.Material.ForceField
	hitbox.Parent = workspace
	hitbox.Color = Color3.new(1, 0, 0)
	
	local npcabilityConnection = coroutine.create(npcAbilityHandler)
	while true do
		game["Run Service"].Heartbeat:Wait()
		if character and character:FindFirstChild("HumanoidRootPart") and character.PrimaryPart and character.Humanoid.Health > 0 then
			if character.Humanoid.Health < 1 then
				hitbox:Destroy()
				npcabilityConnection:Disconnect()
				return
			end
			local configuration = character:FindFirstChild("Configuration")
			hitbox.Position = character.PrimaryPart.Position
			local target = ClosestPlayer(character.PrimaryPart.Position)
			if target then
				character.Humanoid:MoveTo(target.PrimaryPart.Position)
				for i,z in ipairs(character:GetChildren()) do
					if z:IsA("BasePart") then
						z:SetNetworkOwner(game.Players:GetPlayerFromCharacter(target))
					end
				end
				hitbox.Touched:Connect(function(hit)
					if hit.Parent == target and HitboxCooldown == false and hit.Parent.Parent ~= workspace.CreatedNPCFolder then
						HitboxCooldown = true
						hit.Parent.Humanoid.Health -= configuration.Damage.Value
						wait(configuration.DamageCooldown.Value)
						HitboxCooldown = false
					end
				end)
			end
		else
			hitbox:Destroy()
			coroutine.yield(npcAbilityHandler)
			return
		end
	end
end

This is by far the worst offender:
the while true do loop does all the following:
does a wait
checks humanoid/character
checks health
FindFirwstChild config
changes hitbox pos
checks target
if has target, do the following, create connection and for loop
set network owner

And all of this code is being done on presumably 10-40 npcs?

1 Like