How could I make these scripts use less memory?

I am making a game with a lot of characters, and I need them all to use the least amount of memory as possible.

Vid:


(Left is the stats, Consumable is melee, gun script is range, and NPC handler is the NPC… handler)

any help is appreciated, even the smallest amount

Melee Script

function module:CreateMelee(T)
	local Tool = T::Tool

	local Character
	local Player
	local Humanoid

	local AttackAnimation
	
	local SoundsFolder = Tool:FindFirstChild("Sounds")
	
	local SwingSound = SoundsFolder:FindFirstChild("Swing")
	local HitSound = SoundsFolder:FindFirstChild("Hit")

	local Settings = Tool:FindFirstChild("Settings")
	local WeaponModule = require(Tool:FindFirstChild("WeaponSettings"))
	local WeaponSettings = WeaponModule:Get()
	
	local Cooldown = Settings:FindFirstChild("Cooldown"):: BoolValue
	
	Tool:SetAttribute("Attacking", false)
	
	local Connections = {}
	
	local function hit(EnemyCharacter, Hitbox, Func)
		HitSound:Play()
		
		local EnemyHumanoid = EnemyCharacter:FindFirstChild("Humanoid")
		EnemyHumanoid:SetAttribute("MeleeCancel", true)
		
		task.delay(WeaponSettings.WEAPON_COOLDOWN, function()
			EnemyHumanoid:SetAttribute("MeleeCancel", false)
		end)
				
		if EnemyHumanoid.Health > 0 then
			
			EnemyHumanoid:TakeDamage(WeaponSettings.DAMAGE)
			
			if WeaponSettings.MULTIPLE_TARGETS == false then
				Hitbox:Destroy()
				Func:Disconnect()
			end
		end
	end
	
	local function Check(Part:Part, Hitbox, Func)
		local EnemyCharacter = Part.Parent
		if EnemyCharacter and EnemyCharacter.Parent ~= Character.Parent then
			local EnemyHumanoid = EnemyCharacter:FindFirstChild("Humanoid")  
			
			if EnemyHumanoid and EnemyHumanoid:GetAttribute("MeleeCancel") ~= true then

				hit(EnemyCharacter, Hitbox, Func)
			end
			
		end
	end
	
	Connections[1] = Tool.Equipped:Connect(function()
		local OldCharacter = nil
		if Character then
			OldCharacter = Character
		end
		Character = Tool.Parent
		Player = game.Players:GetPlayerFromCharacter(Character)
		Humanoid = Character:FindFirstChild("Humanoid")
		
		if OldCharacter == nil or OldCharacter ~= Character then
			AttackAnimation = Humanoid:LoadAnimation(Settings.Attack)
			AttackAnimation.Priority = Enum.AnimationPriority.Action3
		end
		
		SwingSound.Parent = Humanoid.RootPart
		HitSound.Parent = Humanoid.RootPart
	end)
	
	Connections[2] = Tool.Unequipped:Connect(function()
		
	end)
	
	Connections[3] = Tool.Activated:Connect(function()
		if Cooldown.Value == false then
			AttackAnimation:Play()
			SwingSound:Play()
			
			local Func
			task.spawn(function()
				task.wait(WeaponSettings.HITBOX_WAITTIME)
				local Hitbox = CreateHitbox(WeaponSettings.HITBOX_SIZE,Humanoid.RootPart) -- creates a fresh part to act as a hitbox
				Hitbox.CFrame = CFrame.new(Humanoid.RootPart.Position + (Humanoid.RootPart.CFrame.LookVector * WeaponSettings.HITBOX_DISTANCE),Humanoid.RootPart.Position)
				Hitbox.Parent = workspace.DebrisHolder
				
				Func = Hitbox.Touched:Connect(function(Part)
					Check(Part, Hitbox, Func)
				end)
				
				for _, Part in pairs(Hitbox:GetTouchingParts()) do
					Check(Part, Hitbox, Func)
				end
				
				task.wait(WeaponSettings.HITBOX_LIFETIME)
				
				if Hitbox then
					Hitbox:Destroy()
				end
				
				if Func then
					Func:Disconnect()
				end
			end)
			Cooldown.Value = true
			task.wait(WeaponSettings.WEAPON_COOLDOWN)
			Cooldown.Value = false
			if Tool:GetAttribute("Attacking") == true then
				Tool:Activate()
			end
		end
	end)
	
	Connections[4] = Tool.AttributeChanged:Connect(function(Attribute: string)
		if Tool:GetAttribute("Attacking") == true then
			Tool:Activate()
		end
	end)

	Connections["Final"] = Tool.Destroying:Connect(function()
		for _, connection in Connections do
			connection:Disconnect()
		end
	end)
end

Gun script (the main things that eat memory):

function module:MakeFireFX(Attachment)
	for _, Effect: ParticleEmitter in pairs(Attachment:GetChildren()) do
		Effect.Parent = Attachment
		Effect:Emit(Effect:GetAttribute("EmitCount"))
	end
end

function module:PlayFolderSound(Folder,Location)
	for _, Sound in pairs(Folder:GetChildren()) do
		local NewSound = Sound:Clone():: Sound
		NewSound.Parent = Location

		local OriginalSpeed = NewSound.PlaybackSpeed * 10
		NewSound.PlaybackSpeed = math.random(OriginalSpeed - 1,OriginalSpeed + 1) * .1

		NewSound:Play()
		Debris:AddItem(NewSound, NewSound.TimeLength)
	end
end

function module:OnRayHit(cast, raycastResult)
	-- This function will be connected to the Caster's "RayHit" event.
	local hitPart = raycastResult.Instance
	local hitPoint = raycastResult.Position
	local normal = raycastResult.Normal

	if hitPart ~= nil and hitPart.Parent ~= nil then -- Test if we hit something
		
		local HitCharacter = hitPart.Parent
		local humanoid = HitCharacter:FindFirstChild("Humanoid")
		
		if humanoid then
			
			if HitCharacter.Parent == self.Character.Parent then
				return
			end
			
			local Health = humanoid.Health
			if hitPart.Name == "Head" then
				humanoid:TakeDamage(self.Settings.DAMAGE * self.Settings.HEADSHOT_MULTIPLIER)
			else
				humanoid:TakeDamage(self.Settings.DAMAGE)
			end
			
		end
		
		--self:explodeBullet(hitPoint)
		--module.MakeParticleFX(hitPoint, normal) -- Particle FX
	end
end

NPC script (This Fires every .25 seconds):

for PosTable, Table in ListOfNPCS do
		task.spawn(function()
			local Character: Model = Table[1]
			local CharacterPathfinding = Table[2]
			local OtherThings = Table[3]
			
			local Tool = Character:FindFirstChildWhichIsA("Tool")

			local Humanoid: Humanoid = Character:FindFirstChild("Humanoid")
			local PercentageOfHealth = Humanoid.Health / Humanoid.MaxHealth
			local RootPart = Humanoid.RootPart
			
			local AlignOrientation: AlignOrientation = OtherThings.AlignOrientation
			local WeaponSettings = OtherThings.WeaponSettings
			local CharacterInfo = OtherThings.CharacterInfo
			
			local AbilityModule = OtherThings["AbilityModule"]
			
			
			if Humanoid.Health <= 0 then
				table.remove(ListOfNPCS, PosTable)
				CustomClassModule.getClass("NPC").new().Create(Tool.Name, nil, Character.Parent)
				Tool:SetAttribute("Firing", false)
				task.spawn(function()
					task.wait(.5)
					Character:Destroy()
				end)
				return
			end
			
			
			if WeaponSettings == nil then
				OtherThings.WeaponSettings = require(Tool.WeaponSettings)
				WeaponSettings = OtherThings.WeaponSettings
			end
			
			if AbilityModule == nil and Humanoid:FindFirstChild("Ability") then
				OtherThings.AbilityModule = require(Humanoid:FindFirstChild("Ability"))
				AbilityModule = OtherThings["AbilityModule"]
				
				OtherThings.Ability = {
					["Cooldown"] = 0,
					["Info"] = AbilityModule:GetAbilityInfo(),
				}
			elseif Humanoid:FindFirstChild("Ability") == nil then
				AbilityModule = false
			end
			
			if AbilityModule ~= false then
				local AbilityCD = OtherThings.Ability.Cooldown
				
				if AbilityCD > 0 then
					OtherThings.Ability.Cooldown -= .25
					AbilityCD = OtherThings.Ability.Cooldown
				end
				
				if AbilityCD <= 0 and CheckAbility(OtherThings.Ability.Info,Character) then
					AbilityModule:UseAbility()
					OtherThings.Ability.Cooldown = OtherThings.Ability.Info.Cooldown
				end
			end
			
			
			local Target: Model = GetNearestTarget(RootPart, CharacterInfo.Range, Character.Parent)

			--[[Tool:SetAttribute("Firing", true)
			Tool:SetAttribute("AimPos", RootPart.Position + Vector3.new(0,-10,75))]]

			if Target then
				local TargetRootPart = Target:FindFirstChild("HumanoidRootPart")
				
				local Distance = GetDistanceFromPos(RootPart.Position, TargetRootPart.Position)
				if AlignOrientation.Enabled == false then
					AlignOrientation.Enabled = true
				end
				AlignOrientation.CFrame = CFrame.lookAt(RootPart.Position, TargetRootPart.Position)
				
				if CharacterInfo.CombatType == 'Ranged' then	
					if Distance > CharacterInfo.Range then
						Tool:SetAttribute("Attacking", false)
						CharacterPathfinding:Run(TargetRootPart)
					else
						Tool:SetAttribute("Attacking", true)
						Tool:SetAttribute("AimPos", TargetRootPart.Position)
				
						local Retreat = false
						if Distance < CharacterInfo.RetreatZone then
							Retreat = true
						end
					
						if Retreat == true or PercentageOfHealth < .5 then
							Humanoid.WalkToPoint = RootPart.Position + -(RootPart.CFrame.LookVector * 6)
						else
							Humanoid.WalkToPoint = RootPart.Position + Vector3.new(math.random(-3,3),0,math.random(-3,3))
						end
					end
				else
					Humanoid:MoveTo(TargetRootPart.Position)
					
					if Distance < CharacterInfo.MeleeRange then
						Tool:SetAttribute("Attacking", true)
					else
						Tool:SetAttribute("Attacking", false)
					end
				end
			else
				Tool:SetAttribute("Attacking", false)
				AlignOrientation.Enabled = false
				CharacterPathfinding:Run(workspace.MovePart)
			end
		end)
	end
1 Like

Nitpick but don’t use pairs/ipairs here (or anywhere else), it’s useless now and if you’re really scraping for bytes of memory that will help a little. I don’t see anything that should use that much memory, it looks fine to me.

3 Likes

got all that was doing that, thanks!

1 Like

One thing I’ve seen a lot of people recommend is just storing the state, and position, and other values like that on the server, you would then just give those values to the client and then the client renders everything.

From the sound of it, humanoids can create some pretty bad lag if you have a lot of them, so just not using them and finding work arounds for that is a good idea.


Along with that the gun script, dont play any sounds on the server or make any vfx, just tell the client where to put the effect and you should be good to go!

Just my recommendations :D

2 Likes

Nevermind, this is definitely an issue lmao. You are going to explode the task scheduler, please use task.defer.

MoveTo() is also intensive compute-wise, you should really slow down the tick count.

1 Like

what should I use as an alternative?

1 Like

Generally instead you should be using PathfindingService but that would probably explode your memory usage/CPU cycles even more. My suggestion is to just… not do something like pathfinding every quarter of a second especially if you are going to have more than a few NPCs.

1 Like
External Media

so, im making a horde game, and this is by far my most time consuming project, as it took a FULL YEAR to finally make this horde actually run good

im making this game on an smashed potato with a case, and the fps drop is caused by the screen recorder, but it runs steadly at 80 fps (for me, so definitely better to ALL of you).

what i did to optimize? well:

  1. RENDER EVERYTHING TO THE CLIENT: Clothes, sounds, animations, textures, faces are all rendered to the client, by using StateMachine, which is basically a way to tell the server/client what the npc is doing (chasing, freeroaming, attacking…) (i see you are already doing it)
  2. ACTORS: yeah, use task.desynchronize before doing heavy calculations, like my flock system. im not going into details but this will split the task in other cores.
  3. Humanoids are heavy? use Move(): becauseMoveTo() is so heavy, i reccomend to use directly Move(), as it only needs a direction, which for me is a life saver for my project, as i dont have to invent my own custom humanoid.
  4. Test new ideas, no matter how much time it takes: not only you will discover if that could help you or not, but you will learn new things and approaches for future stuff.
1 Like

I already got it working extremely smooth, I am using all of these methods, but I asked copilot and it gave me the best method that turned the usage from %5 - 10% to under 1%.

Recycling NPCs.

When a NPC dies, why destroy it and create a new? When you could just reset its stats and store it, then bring it back out when needed.

Creating new things is by FAR the laggiest thing you can do.

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.