High performance loss with custom camera script

Hello, I have made a custom camera and it has huge performance issues, I have no idea what causes the huge performance loss. I would highly appreciate any help, thanks.

function CameraModule:Update(deltaTime)
	local character = self.player.Character
	if not character then return end
	
	local delta = Vector2.zero
	
	if UserInputService.KeyboardEnabled then
		delta = UserInputService:GetMouseDelta()
	elseif UserInputService.TouchEnabled then
		delta = self.touchDelta
	end
	if UserInputService.GamepadEnabled and UserInputService:GetLastInputType() == Enum.UserInputType.Gamepad1 then
		local gamepadInput = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
		for _, input in ipairs(gamepadInput) do
			if input.KeyCode == Enum.KeyCode.Thumbstick2 then
				local newDelta = Vector2.new(math.round(input.Position.X * 2.5), math.round(-input.Position.Y * 2.5))
				delta = newDelta / 3 * (self.sensitivity.Value / 100) * 25
			end
		end
	end
	
	if delta.X and delta.Y then
		self.rotationX = math.clamp(self.rotationX - delta.Y * (self.sensitivity.Value / 100), -80, 80)
		self.rotationY = self.rotationY - delta.X * (self.sensitivity.Value / 100)
		self.rotationZ = lerp(self.rotationZ, math.clamp(delta.X * 2 * (self.sensitivity.Value / 100), -6, 6), 6 * deltaTime)
	end
	
	self.waistC1Y = lerp(self.waistC1Y, math.clamp(self.player.Character.HumanoidRootPart.CFrame:VectorToObjectSpace(self.player.Character.Humanoid.MoveDirection).X * 30, -30, 30), 6 * deltaTime)
	
	local rootPosition = self.player.Character.HumanoidRootPart.Position
	local mappedFov = math.map(self.player.Character.Humanoid.WalkSpeed, 10, 45, 60, 130)
	local transparent = (self.camera.CFrame.Position - self.cameraPart.Position).Magnitude < 3
	
	local cameraCFrame = CFrame.new(self.cameraPart.Position) *
		CFrame.Angles(0, math.rad(self.rotationY), 0) *
		CFrame.Angles(math.rad(self.rotationX), 0, 0) *
		CFrame.Angles(0, 0, math.rad(self.rotationZ))
	
	local lookDirection = cameraCFrame.LookVector
	local flatLookDirection = Vector3.new(lookDirection.X, 0, lookDirection.Z).Unit
	
	local isSwimming = character:GetAttribute("Swimming")
	if self.lastSwimmingState ~= isSwimming then
		self.transitionStartTime = tick()
		self.lastSwimmingState = isSwimming
	end
	
	local transitionProgress = math.clamp((tick() - self.transitionStartTime) / 0.5, 0, 1)
	print(transitionProgress)
	
	if isSwimming then
		local smoothAngle = lerp(0, -90, transitionProgress)
		character.HumanoidRootPart.CFrame = character.HumanoidRootPart.CFrame:Lerp(CFrame.new(rootPosition, rootPosition + lookDirection) * CFrame.Angles(math.rad(smoothAngle), 0, 0), deltaTime * 15)
	else
		local smoothAngle = lerp(-90, 0, transitionProgress)
		character.HumanoidRootPart.CFrame = character.HumanoidRootPart.CFrame:Lerp(CFrame.new(rootPosition, rootPosition + flatLookDirection) * CFrame.Angles(math.rad(smoothAngle), 0, 0), deltaTime * 15)
	end

	for _, part in CollectionService:GetTagged("LightPart") do
		local magnitude = (Vector2.new(part.Position.X, part.Position.Z) - Vector2.new(self.camera.CFrame.Position.X, self.camera.CFrame.Position.Z)).Magnitude
		local fogDetail: Beam = part.Parent:FindFirstChild("FogDetail", true)
		if not fogDetail then continue end
		if magnitude <= self.closeDistance then
			fogDetail.Transparency = NumberSequence.new{
				NumberSequenceKeypoint.new(0, 1),
				NumberSequenceKeypoint.new(0.1, 0.8 + math.clamp(0.2 - ((magnitude - 1) / self.closeDistance), 0, 0.2)),
				NumberSequenceKeypoint.new(0.2, 0.5 + math.clamp(0.5 - ((magnitude - 1) / self.closeDistance), 0, .5)),
				NumberSequenceKeypoint.new(0.5, 0.75 + math.clamp(0.25 - ((magnitude - 1) / self.closeDistance), 0, .25)),
				NumberSequenceKeypoint.new(1, 1)
			}
		elseif magnitude >= self.farDistance then
			fogDetail.Transparency = NumberSequence.new{
				NumberSequenceKeypoint.new(0, 1),
				NumberSequenceKeypoint.new(1, 1),
			}
		else
			fogDetail.Transparency = NumberSequence.new{
				NumberSequenceKeypoint.new(0, 1),
				NumberSequenceKeypoint.new(0.1, 0.8),
				NumberSequenceKeypoint.new(0.2, 0.5),
				NumberSequenceKeypoint.new(0.5, 0.75),
				NumberSequenceKeypoint.new(1, 1)
			}
		end
	end

	if self.inLocker.Value then
		local maxRadius = 50
		local magnitude = math.sqrt(self.rotationX^2 + self.rotationY^2)
		if magnitude > maxRadius then
			local scale = maxRadius / magnitude
			self.rotationX = self.rotationX * scale
			self.rotationY = self.rotationY * scale
		end

		cameraCFrame = self.cameraPart.CFrame *
			CFrame.Angles(0, math.rad(self.rotationY), 0) *
			CFrame.Angles(math.rad(self.rotationX), 0, 0)
	end

	self.camera.CFrame = cameraCFrame

	if isSwimming or self.inLocker.Value then
		character.UpperTorso.Waist.C1 = self.waistC1
		character.LowerTorso.Root.C0 = self.lowerTorsoC0
		--character.Head.Neck.C0 = self.neckC0
	else
		character.UpperTorso.Waist.C1 = self.waistC1 * CFrame.Angles(math.rad(math.map(math.deg(-self.camera.CFrame:ToOrientation()), -80, 80, -80, 80)),
			math.rad(-self.waistC1Y),
			0)
		character.LowerTorso.Root.C0 = self.lowerTorsoC0 * CFrame.Angles(0, math.rad(-self.waistC1Y), 0)
		--character.Head.Neck.C0 = self.neckC0 * CFrame.Angles(math.rad(math.clamp(math.deg(self.camera.CFrame:ToOrientation()), -15, 15)), 0, 0)
	end

	if tick() - self.lastRemote >= .222 then
		self.lastRemote = tick()
		ReplicatedStorage.Remotes.Events.ReplicateTorso:FireServer(character.UpperTorso.Waist.C1, character.Head.Neck.C0)
	end

	self.camera.FieldOfView = mappedFov
	for _, part in self.player.Character:GetDescendants() do
		if part:IsA("BasePart") and not table.find(self.whitelist, part.Name) and not part:FindFirstAncestorWhichIsA("Tool") then
			part.Transparency = transparent and 1 or 0
		end
	end
	if self.player.Character.Head:FindFirstChild("face") then
		self.player.Character.Head.face.Transparency = transparent and 1 or 0
	end

	if self.lockMouse.Value then
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	end
end

After reading through your code I found some things…

CollectionService:GetTagged("LightPart")
for _, part in self.player.Character:GetDescendants() do
		if part:IsA("BasePart") and not table.find(self.whitelist, part.Name) and not part:FindFirstAncestorWhichIsA("Tool") then
			part.Transparency = transparent and 1 or 0
		end
	end

This right here, is very slow, every frame you’re creating an entire array of objects which forces a lot of memory allocations in a single frame.

Extremely slow.

self.player.Character.Head:FindFirstChild("face")

Second, FindFirstChild() is very slow to use, you should only use that once in your code to see if something exists.

Cache / save your values so you don’t have to do this constantly.
Have a function check if things exist outside of your render loop.

if UserInputService.GamepadEnabled and UserInputService:GetLastInputType() == Enum.UserInputType.Gamepad1 then
		local gamepadInput = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
		for _, input in ipairs(gamepadInput) do
			if input.KeyCode == Enum.KeyCode.Thumbstick2 then
				local newDelta = Vector2.new(math.round(input.Position.X * 2.5), math.round(-input.Position.Y * 2.5))
				delta = newDelta / 3 * (self.sensitivity.Value / 100) * 25
			end
		end
	end

Multiple ipairs() loops inside your render logic, wayyyyy too slow.
To be honest, half your camera script needs to be rewritten because the design is a nightmare.

Handle user input and mouse movement in a different script or module and just have one script/module that purely focuses on moving the camera itself.

Most of your logic should happen outside of your render function because a lot of things don’t need to happen every single frame.

The only thing you should do every frame is move the camera itself and do a bunch of raycasts to check if nothing is obscuring the view towards the player.

1 Like

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