Custom first person camera rotating weirdly

So here’s the code that controls the camera’s CFrame

cameraFinalCFrame =*CFrame.Angles(-math.clamp(cameraPosition.X, -math.pi/2, math.pi/2), cameraPosition.Y, 0)

offset is the location of the head
cameraPosition is a Vector2 with X being pitch and Y being yaw
The pitch and yaw are moved with the mouse

My issue is that the rotation doesn’t move with the camera making yaw become roll making it feel more like a flight simulator
How do I get the rotation to apply on the right pivot?

1 Like

It has something to do with your Z vector, you’re setting it to the y of the camera position, do you understand how this works?

1 Like

It’s obviously the Z vector but I do not know how to correct it
cameraPosition is a bit of a misnomer and I should probably change it
As I’ve already stated cameraPosition.X is pitch and cameraPosition.Y is yaw. They are both in radians.

Assuming that the first CFrame doesn’t have any added on angles, we’re good with that. Then you add the first and second arguments, which don’t affect the z, it’s 0.

Are there any other scripts modifying the camera?

No, however I do in fact see the Z orientation changing in the properties viewer for some reason despite being set to 0 (or -pi sometimes for some reason) in the cframe when I print it (and yes the camera mode is correct)

I have a hint it’s related to some along the lines of render priority. Is the default camera module still running? Are you some how overriding it?

Theoretically setting the camera type to Scriptable should disable all default behavior and changing the render priority doesn’t do anything
From what I can tell, when facing directly left or right of the starting position of the camera, moving the camera up and down will result in the camera rolling instead of changing pitch and the axis it does it on is the original axis it started with
My guess is that the rotation axis doesn’t update when the camera rotates so what looks like roll is actually pitch on the wrong axis

Could you send the entire script please?

Here’s a reproduction script (it’s got some weirdness like a wait n stuff that aren’t in the full script)

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local config = UserSettings():GetService("UserGameSettings")

-- The player and their character
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
task.wait(3) -- Let the character properly load (not in original script)
for i,v in pairs(character:GetDescendants()) do
	if v:IsA("BasePart") then v.Transparency = 1 end
local head = character:WaitForChild("Head")
-- The camera and its settings
local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition -- LockCenter doesn't work in studio for some reason
-- The rotation of the camera
local cameraPosition =, 0)

-- Function to move the camera
local function moveCamera(dx, dy)
	local speed = config.MouseSensitivity/100
	local newPosition = cameraPosition + * speed, -dx * speed)

	newPosition =, -math.pi/2, math.pi/2), newPosition.Y)

	cameraPosition = newPosition

	if input.UserInputType == Enum.UserInputType.MouseMovement then
		if true then -- There is supposed to be a condition here but not necessary for repro
			local delta = UserInputService:GetMouseDelta()
			moveCamera(delta.X, delta.Y)

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value,function()
	if character and head then
		camera.CFrame =*CFrame.Angles(cameraPosition.X, cameraPosition.Y, 0)

Oh yeah it’s intended for StarterCharacterScripts btw

Ok, a lot of things interesting about this script. First of all, you’re multiplying the CFrame head position by a CFrame that has it’s parameters as the position, which isn’t going to work, and is going to have buggy behavior.

What do you mean by “has it’s parameters as the position”

Try applying yaw separately, multiply two CFrame.Angles

The CFrame’s Z axis is 0 when it’s applied to the camera that doesn’t have any affect


That applies horizontal rotation rotation, then vertical rotation, rather than in the opposite order.

Not only is that math all wrong (math.clamp(x,0,0)?), I already said it does not work and the Z axis is already 0 on the CFrame but it’s rotating on the wrong pivot

You are right, I used the wrong axis
We’re supposed to switch the x to y.

Well here’s what fixed it

local pitch = CFrame.Angles(-math.clamp(cameraPosition.X, -math.pi/2, math.pi/2), 0, 0)
local yaw = CFrame.Angles(0, cameraPosition.Y, 0)
cameraFinalCFrame = * yaw * pitch

1 Like

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