Stop camera from moving sideways?

For a while now, I’ve been stuck attempting to create a camera script.
I’ve made it go sideways, but whenever I try to make it go up and down, the Z axis also changes. How can i only make it go up and down, whilst having constant Y axis changes?

local Camera = workspace:WaitForChild("Camera")
local VX = 0
local VZ = 0
local LastPos = Vector2.new()
local MousePos = Vector2.new()
local MoveSpeedX = MousePos.X - LastPos.X
local MoveSpeedY = MousePos.Y - LastPos.Y
local Cursor = game.Players.LocalPlayer:GetMouse()
local CX = 0
local CY = 0
local function ClearChar(Char)
	for i, obj in pairs(Char:GetDescendants()) do
		if (obj:IsA("BasePart") or obj:IsA("MeshPart")) and not obj.Parent:IsA("Accessory") then
			obj.LocalTransparencyModifier = 0
		end
	end
end
--[[game.Players.LocalPlayer.CharacterAdded:Connect(function(Char)
	ClearChar(Char)
end)]]
game.Players.LocalPlayer.CharacterAppearanceLoaded:Connect(function(Char)
	ClearChar(Char)
end)
game["Run Service"].PreRender:Connect(function()
	ClearChar(game.Players.LocalPlayer.Character)
	--Camera.CameraType = Enum.CameraType.Scriptable
	MousePos = Vector2.new(((Cursor.X - (Cursor.ViewSizeX / 2)) / Cursor.ViewSizeX), ((Cursor.Y - (Cursor.ViewSizeY / 2)) / Cursor.ViewSizeY))
	MoveSpeedX = game.UserInputService:GetMouseDelta().X / 300
	MoveSpeedY = game.UserInputService:GetMouseDelta().Y / 500
	VX += MoveSpeedX * 0.3
	VX /= 1.05
	if VX > 5 then
		VX = 5
	end
	if VX < -5 then
		VX = -5
	end
	VZ -= MoveSpeedY * 0.3
	VZ /= 1.1
	if VZ > 5 then
		VZ = 5
	end
	if VZ < -5 then
		VZ = -5
	end
	CX -= (VX /3)
	CY -= (VZ /3)
	if CY > 1 then
		CY = 1
	end
	if CY < -0.85 then
		CY = -0.85
	end
	if game.Players.LocalPlayer.Character then
		local CameraV = game.Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid").RootPart.Position + game.Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid").CameraOffset + Vector3.new(0, 1.5, 0)
		Camera.CFrame = CFrame.Angles(CY, CX, 0) + CameraV
		print(workspace.CurrentCamera.CFrame)
	end
	LastPos = Vector2.new(((Cursor.X - (Cursor.ViewSizeX / 2)) / Cursor.ViewSizeX), ((Cursor.Y - (Cursor.ViewSizeY / 2)) / Cursor.ViewSizeY))
end)

I tested your script and I see what you mean. I believe I have found the sollution. Basically you want to rotate around the Y axis for horizontal movement, and rotate around the X axis for vertical movement but apply them in the correct world-relative order.

This is best done with CFrame.lookAt

local Camera = workspace:WaitForChild("Camera")
Camera.CameraType = Enum.CameraType.Scriptable

local VX = 0
local VZ = 0
local LastPos = Vector2.new()
local MousePos = Vector2.new()

local MoveSpeedX = 0
local MoveSpeedY = 0

local Cursor = game.Players.LocalPlayer:GetMouse()
local CX = 0
local CY = 0

local function ClearChar(Char)
	for i, obj in pairs(Char:GetDescendants()) do
		if (obj:IsA("BasePart") or obj:IsA("MeshPart")) and not obj.Parent:IsA("Accessory") then
			obj.LocalTransparencyModifier = 0
		end
	end
end

game.Players.LocalPlayer.CharacterAppearanceLoaded:Connect(function(Char)
	ClearChar(Char)
end)

game:GetService("RunService").PreRender:Connect(function()
	ClearChar(game.Players.LocalPlayer.Character)

	MousePos = Vector2.new(((Cursor.X - (Cursor.ViewSizeX / 2)) / Cursor.ViewSizeX), ((Cursor.Y - (Cursor.ViewSizeY / 2)) / Cursor.ViewSizeY))
	MoveSpeedX = game.UserInputService:GetMouseDelta().X / 300
	MoveSpeedY = game.UserInputService:GetMouseDelta().Y / 500

	VX += MoveSpeedX * 0.3
	VX /= 1.05
	VX = math.clamp(VX, -5, 5)

	VZ -= MoveSpeedY * 0.3
	VZ /= 1.1
	VZ = math.clamp(VZ, -5, 5)

	CX -= (VX / 3)
	CY -= (VZ / 3)
	CY = math.clamp(CY, -0.85, 1)

	local character = game.Players.LocalPlayer.Character
	if character then
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		if humanoid then
			local hrp = humanoid.RootPart
			local camOffset = humanoid.CameraOffset
			local basePos = hrp.Position + camOffset + Vector3.new(0, 1.5, 0)

			-- Use proper rotation to avoid Z tilting
			local direction = CFrame.fromEulerAnglesYXZ(CY, CX, 0).LookVector
			Camera.CFrame = CFrame.lookAt(basePos, basePos + direction)


		end
	end

	LastPos = Vector2.new(((Cursor.X - (Cursor.ViewSizeX / 2)) / Cursor.ViewSizeX), ((Cursor.Y - (Cursor.ViewSizeY / 2)) / Cursor.ViewSizeY))
end)

CFrame.fromEulerAnglesYXZ(pitch, yaw, 0) rotates first on X (pitch), then Y (yaw), avoiding the Z-axis roll.

Camera.CFrame = CFrame.lookAt(origin, origin + direction) keeps camera upright and looking correctly.

math.clamp(pitch, -80°, 80°) prevents over-rotation (you can change it if you want)

If there’s any issues let me know.