My game is laggy and I don't know how to optimize it

My friends have said my game is laggy, and one of the places it seems to lag the most is at a lava chase a bit further into the game. When they get there, their FPS continuously spikes from normal FPS to super low FPS very rapidly. I looked through the code and can’t find anything that would lag the game this bad. Is there anything I could change to make it less laggy?

Code snippet of all relevant code:

---------------------------------------------------------------------------------------------------------------
-- SHAKE

function CamShake(decay,offsetmin,offsetmax,playsound,customsound,volume,subject)
	spawn(function()
		local decay = decay*.1
		local volume = volume*.1
		local snd = Instance.new("Sound")
		local vol = volume
		if customsound == false then
			snd.SoundId = "rbxassetid://153274423"
		else
			snd.SoundId = "rbxassetid://".. tostring(customsound)
		end
		snd.Pitch = math.random(90,110) * .01
		snd.Volume = vol
		snd.Parent = script
		game.Debris:AddItem(snd, 3)
		if playsound == true then
			snd:Play()
		end
		local fin = tick() + decay
		
		local savedpos = nil
		if subject then
			if subject == true then
				savedpos = Camera.CFrame
			else
				savedpos = subject.Position
			end
		end
		
		while game:GetService("RunService").RenderStepped:wait() and (fin-tick() > 0) and hum.Health > 0 do
			snd.Volume = vol * (fin-tick())
			if subject then
				if subject == true then
					Camera.CFrame = Camera.CFrame + Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
					)
				else
					subject.Position = subject.Position + Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
					)
				end
			else
				hum.CameraOffset = Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
					((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
					((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
				)
			end
		end
		
		if subject then
			if subject == true then
				Camera.CFrame = savedpos
			else
				subject.Position = savedpos
			end
		end
	end)
end

hum.Died:connect(function() hum.CameraOffset = Vector3.new() end)

---------------------------------------------------------------------------------------------------------------
-- EVENTS+

function Recheck()
	---------------------------------------------------------------------------------------------------------------
	-- EVENT 2

	spawn(function()

		task.wait()

		local debounce2 = true
		local enddebounce = true
		local Event = workspace:WaitForChild("Event2")
		local Trigger = Event:WaitForChild("Trigger2")
		local Barrier = Event:WaitForChild("Barrier")
		local Lava = Event:WaitForChild("Lava")
		local CamPart = Event:WaitForChild("CamPart")
		local LavaLight = Lava:WaitForChild("FrontLight")

		local EndTrigger = Event:WaitForChild("EndTrigger")
		local EndBarrier = Event:WaitForChild("EndBarrier")
		local CloseDoor = Event:WaitForChild("CloseDoor")
		local CloseDoorSound = CloseDoor:WaitForChild("MetalPipe")

		local ChaseMusic = script.ChaseMusic
		local ChaseMusicChoice = 1

		local Tween1 = TS:Create(Lava, TweenInfo.new(0.5), {Position = Vector3.new(-0, 25.5, 56.75)}) -- 58.5 studs up
		local Tween2 = TS:Create(Lava, TweenInfo.new(60, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, false, 0), {Position = Vector3.new(-0, 25.5, -343.25)}) -- 300 studs forward
		local Tween3 = TS:Create(ChaseMusic, TweenInfo.new(2.5), {Volume = 1})
		local Tween4 = TS:Create(ChaseMusic, TweenInfo.new(3), {Volume = 0})
		local Tween5 = TS:Create(CloseDoor, TweenInfo.new(2, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out), {Position = CloseDoor.Position + Vector3.new(0, -49, 0)})
		local Event2Tweens = {Tween1, Tween2, Tween3, Tween4, Tween5}

		local function TriggerFunc()
			print("hi")
			Barrier.CanCollide = true

			for i, v in pairs(Music:GetChildren()) do
				if v.ClassName == "Sound" then
					local Tween0 = TS:Create(v, TweenInfo.new(3), {Volume = 0})
					table.insert(Event2Tweens, Tween0)
					Tween0:Play()
				end
			end


			CamShake(20,-5, 5, true, false, 20)
			task.wait(1)
			Camera.CameraType = Enum.CameraType.Scriptable
			Camera.CFrame = CamPart.CFrame
			task.wait(2)
			script.LavaExplosion:Play()
			CamShake(20,-5, 5, true, false, 8, true)
			Tween1:Play()
			task.wait(2)
			Tween2:Play()
			task.wait(0.5)
			Camera.CameraType = Enum.CameraType.Custom
			Barrier.Transparency = 0.9
			ChaseMusic.Volume = 0

			if ChaseMusicChoice == 1 then
				ChaseMusic.SoundId = "rbxassetid://1836136972"
				ChaseMusic.TimePosition = 6
				ChaseMusic.PlaybackSpeed = 1.2

			else
				ChaseMusic.SoundId = "rbxassetid://1837301393"
				ChaseMusic.TimePosition = 5
				ChaseMusic.PlaybackSpeed = 1
			end

			ChaseMusic:Play()
			Tween3:Play()
			task.wait(2.3)
			Barrier.CanCollide = false
			Barrier.Transparency = 1
		end

		Trigger.Touched:Connect(function(hit)
			local h = hit.Parent:FindFirstChild("Humanoid")
			if h and h == hum and debounce2 then
				debounce2 = false
				EventNum = 2
				local thread = task.spawn(TriggerFunc)

				hum.Died:Connect(function()
					task.cancel(thread)
				end)
			end
		end)

		local function EndTriggerFunc()
			EndBarrier.CanCollide = true
			Tween4:Play()
			task.wait(0.2)
			Tween5:Play()
			task.wait(0.6)
			CloseDoorSound:Play()
			local Tween6 = TS:Create(LavaLight, TweenInfo.new(1), {Brightness = 0})
			table.insert(Event2Tweens, Tween6)
			Tween6:Play()
			task.wait(4)

			for i, v in pairs(Music:GetChildren()) do
				if v.ClassName == "Sound" then
					local Tween7 = TS:Create(v, TweenInfo.new(2), {Volume = MusicVol})
					table.insert(Event2Tweens, Tween7)
					Tween7:Play()
				end
			end

			while task.wait() do
				if localplayer:GetAttribute("CheckpointNumber") > 4 then
					EventNum = 0
					print("EventNum: ".. EventNum)
					break
				end
			end
		end

		EndTrigger.Touched:Connect(function(hit)
			local h = hit.Parent:FindFirstChild("Humanoid")
			if h and h == hum and enddebounce then
				enddebounce = false
				local thread = task.spawn(EndTriggerFunc)

				hum.Died:Connect(function()
					task.cancel(thread)
				end)
			end
		end)

		localplayer.CharacterAdded:Connect(function(ch)
			if EventNum == 2 then
				ChaseMusicChoice = math.random(1, 2)
				if localplayer:GetAttribute("CheckpointNumber") < 5 then
					for i, v in pairs(Event2Tweens) do
						v:Cancel()
					end
					Lava.Position = Vector3.new(0, -62.25, 56.75)
					Barrier.CanCollide = false
					Barrier.Transparency = 1
					EndBarrier.CanCollide = false
					LavaLight.Brightness = 1
					CloseDoor.Position = Vector3.new(0, 74.5, -571.75)
					ChaseMusic.Volume = 0
					ChaseMusic:Stop()
					debounce2 = true
					enddebounce = true
				end

				EventNum = 0
			end
		end)

	end)

end

---------------------------------------------------------------------------------------------------------------
-- RECHECK

local eventthread = task.spawn(Recheck)

game:GetService("Players").PlayerAdded:Connect(function(Player)

	Player.Chatted:Connect(function(Message) -- RECHECK COMMAND
		if Player.Name == localplayer.Name and Message == "/recheck" then

			task.cancel(eventthread)
			
			eventthread = task.spawn(Recheck)

		end
	end)

end)
3 Likes

I don’t know much about scripting, but maybe it has to do with the tweens?
I really don’t know, but that is my guess.

Edit:Also, it may be the camera shakes? Or both combined? No idea.

1 Like

Well then why you here?

Kqsdnjb

1 Like

I edited the message to include the camshake function and recheck function, cuz they’re relevant in the code

Your game is laggy due to many scripts, loops and threads. Those three things usually cause it.

Here are some tips:

  • Whenever you loop through something, specifically if you are using :GetChildren() or :GetDescendants(), store that value somewhere, as calling it multiple times can be very taxxing on the script.
  • For threads, try not to use so many, they can slow your code down.
  • In the real game, objects that aren’t touch listening, turn .CanTouch to false, and objects that won’t be affected by it, turn .CastShadow to false.
  • Try to anchor as many things as possible (for the engine)
  • Try to combine things into one script (if necessary)
2 Likes

I believe this belongs in the #help-and-feedback:code-review category, however, you cannot post almost entire scripts in there.

And, to address your problem, well, uh, since you posted what seems to be 150+ lines, I can’t really read all of that and troubleshoot it. The best I could do is skipping some Instance.new("") 's, and instead just making a static object and/or just cloning said object.

I noticed you said,

		while game:GetService("RunService").RenderStepped:wait() and (fin-tick() > 0) and hum.Health > 0 do
			snd.Volume = vol * (fin-tick())
			if subject then
				if subject == true then
					Camera.CFrame = Camera.CFrame + Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
					)
				else
					subject.Position = subject.Position + Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
						((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
					)
				end
			else
				hum.CameraOffset = Vector3.new( ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
					((math.random(offsetmin,offsetmax)*.05) * (fin-tick())),
					((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))
				)
			end
		end

So, instead if that, why not replace the RunService loop with a connection? Example:

local connection
connection = game:GetService("RunService").RenderStepped:Connect(function()
   -- code

   local function yeah() -- function that fires when you want the loop to end, i'm assuming it ends when (fun-tick() <= 0)
      connection:Disconnect()
   end
   if (fn-tick() <= 0) then yeah() end
end)

-- rest of code here

And, for the function itself, you are using the statement ((math.random(offsetmin,offsetmax)*.05) * (fin-tick())) a LOT,
why don;t you turn it into a variable?

local lorem_ipsum = ((math.random(offsetmin,offsetmax)*.05) * (fin-tick()))

Now, this is the simplified version of that one portion of code.

function CamShake(decay,offsetmin,offsetmax,playsound,customsound,volume,subject)
	spawn(function()
		local decay = decay*.1
		local volume = volume*.1
		local snd = script.Sound
		snd = snd:Clone()

		if customsound == false then
			snd.SoundId = "rbxassetid://153274423"
		else
			snd.SoundId = "rbxassetid://".. tostring(customsound)
		end
		snd.Pitch = math.random(90,110) * .01
		snd.Volume = volume
		snd.Parent = script
		
		if playsound == true then
			snd:Play()
		end 
		
		game.Debris:AddItem(snd, snd.TimeLength)
		
		local fin = tick() + decay
		local savedpos = nil
		
		if subject then -- "if [boolean] then" == "if [boolean] == true then"
			
			savedpos = Camera.CFrame
			
		else

			savedpos = subject.Position
		end
		
		local connection
		connection = game:GetService("RunService").RenderStepped:Connect(function()
			local tick_ = (fin-tick())
			if hum.Health <= 0 then connection:Disconnect() end
			if (tick_ <= 0) then connection:Disconnect() end
			
			local lorem_ipsum = ((math.random(offsetmin,offsetmax)*.05) * tick_) -- not sure what you wanted to call this variable, so i named it 'lorem ipsum'.
			snd.Volume = vol * tick_
			
			if subject then
				Camera.CFrame = Camera.CFrame + Vector3.new(lorem_ipsum, lorem_ipsum, lorem_ipsum)
			else
				Camera.CFrame = Camera.CFrame + Vector3.new(lorem_ipsum, lorem_ipsum, lorem_ipsum)
			end
		end)
		
		repeat task.wait() until (fin-tick() <= 0) or hum.Health <= 0 -- waits for the same statements that the loop ends at

		if subject then
			Camera.CFrame = savedpos
		else
			subject.Position = savedpos
		end
	end)
end

I can help more later.

1 Like

Wouldn’t saving GetChildren and GetDescendants as variables be a bad idea if certain children don’t load in time?

This is presuming that your game is already loaded. Don’t do this at the start of the game, of course.

should i add smth at the beginning of my script that waits for everything to load?

also spawn functions are threads right?

1 Like

Yeah they are, they are similar to coroutine I believe, which you could do by writing

coroutine.wrap(function()
	-- stuff to do in the function
end)()

If youre wondering specifically where and at what line of code the game lags, I suggest adding prints after some lines you think might be influential in the lag, or in certain functions which might run alot, this might show you where your lag is.

Im pretty sure that doing workspace:WaitForChild(thing) Will wait until everything inside thing loads before returning anything to you.

Someone correct me if im wrong though

that’s not true… but it should be

1 Like

Actually checking for myself I see this is the case, I might make a custom module specifically for this, a :WaitForDescendants or something.

that sounds cool! :3

function module:WaitForDescendants(child:Instance)
	local descendants = child:GetDescendants()
	local descendantCount = #descendants
	--print(descendantCount)
	if descendantCount == 0 then
		return child
	end
	local function onDescendantAdded()
		descendantCount -= 1
		if descendantCount == 0 then
			
		end
	end
	local connection = child.DescendantAdded:Connect(onDescendantAdded)
	for _, descendant in ipairs(descendants) do
		onDescendantAdded(descendant)
	end
	
	connection:Disconnect()

	return child
end

From my limited testing this seems to work, enjoy!

I realized after making this that actually getting the REAL descendants even if they havnt loaded yet COULD be an issue, although spawning in nearly 7.5K parts, the script detected each one just fine and took 0.00251 seconds to run so id say thats good enough.

re-reading this script after some delirium it literally does like nothing
DO NOT USE THIS SCRIPT

1 Like

tf :sob: this went from “help me optimize my game!” to “yeah i just made a :WaitForDescendants() module script yk nothing too fancy” LMFAO

2 Likes

Would setting more models to ModelStreamingMode = Persistent be more laggy by a lot or would it be more negligible? I wanted to have maybe 5-6 models to be set to this but it seems like a bit much.