How would I adjust the turning on this camera?

Hello! I’ve recently made a custom camera script, and with it there’s custom turning that allows the player to look around in a small area around themselves. Thing is, with this camera, whenever the character looks in diagonal directions, the camera seems to tilt a bit.

robloxapp-20210908-1707444.wmv (1.4 MB)
[Apologies for the low quality video]

As shown in the video, the camera seems to tilt, and I’d like to see if there’s a way to have it replicate the normal roblox camera a bit more. I’m assuming how I’d fix this would be by adding a math equation to the z coordinate of the CFrame.Angles that would accommodate for both the x and y.

--The line that determines the angle of the camera. As you can see the z coordinate isn't changed.
Camera.CFrame = Camera.CFrame * CFrame.Angles(-DistanceY/TurnConstraint,-DistanceX/TurnConstraint,0)

How would I be able to accomplish this? Any help would be appreciated. Below is the full camera script so you can get a better understanding of how the camera works.

--Variables that you can edit to your liking.
local KeyboardKeybind = Enum.KeyCode.X --Standard keybind stuff. Very simple to change.
local ControllerKeybind = Enum.KeyCode.ButtonX --Refer to previous comment.
local EnableMobileButton = true --You can turn off the mobile button if you wanna make your own custom one.
local AllowRotate = true --Enable/Disable the camera rotation
local TurnConstraint = 2 --Changes how far you can turn your camera (increasing the number decreases the amount it can turn)
local ButtonImage = "rbxassetid://247421264" --Sets the image of the mobile button. If you don't want one leave it as ""
local ButtonTitle = "" --Adds a title to the mobile button. Blank by default but to add one just type in anything between the ""
local ButtonAdjustsWithJumpButton = true --If turned to false, the mobile button will adjust according to it's default position. Changing the ButtonPosition variable would be suggested if setting this to false.
local ButtonPosition = UDim2.new(0,60, 0,0) --You can manually change the position of the default button here, although be warned, it is really wonky.

--Required Variables; Don't edit unless you're reworking camera
local RunService = game:GetService("RunService")
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")

local Player = game.Players.LocalPlayer
local Character = script:FindFirstAncestor(Player.Name)

local Humanoid = Character:WaitForChild("Humanoid")
local Camera = workspace.CurrentCamera
local Mouse = Player:GetMouse()

local LastInput = Enum.UserInputType.MouseMovement
local CDistanceX = 0
local CDistanceY = 0

--Functions
function CalculateDistance()
	--Check to see if a controller has returned a value.
	if LastInput == Enum.UserInputType.Gamepad1 then
		return CDistanceX,-CDistanceY
	else
		--Get the center of the screen
		local CenterX = Mouse.ViewSizeX/2
		local CenterY = (Mouse.ViewSizeY)/2
		--Make a distance cap
		local DistCapX = CenterX*1.25
		local DistCapY = CenterY*1.25
		--Adjust caps to work with center offset
		DistCapX = (CenterX-DistCapX)
		DistCapY = (CenterY-DistCapY)
		--Calculate the distance of the mouse from the center
		local DistanceX = CenterX-Mouse.X
		local DistanceY = CenterY-Mouse.Y
		--Divide cap from distance to create a -1 to 1 scale.
		DistanceX = DistanceX/DistCapX
		DistanceY = DistanceY/DistCapY
		if DistanceX > 1 and DistanceX > 0 then
			DistanceX = 1
		elseif DistanceX < -1 and DistanceX < 0 then
			DistanceX = -1
		end
		if DistanceY > 1 and DistanceY > 0 then
			DistanceY = 1
		elseif DistanceY < -1 and DistanceY < 0 then
			DistanceY = -1
		end
		return DistanceX,DistanceY
	end
end

--Controller version of Calculate distance, as manually firing an input changed whilst being fired from renderstep causes MAJOR lag. :(
UserInputService.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.Gamepad1 then
		LastInput = Enum.UserInputType.Gamepad1
		if input.KeyCode == Enum.KeyCode.Thumbstick2 then
			CDistanceX = input.Position.X
			CDistanceY = input.Position.Y
		end
	elseif input.UserInputType == Enum.UserInputType.MouseMovement then
		LastInput = Enum.UserInputType.MouseMovement
	end
end)

function UpdateTransparency(transparency)
	for i,v in pairs(Character:GetDescendants()) do
		if v:IsA("Part") or v:IsA("MeshPart") then
			v.LocalTransparencyModifier = transparency
		end
	end
end

function AdjustCamera()
	local DistanceX, DistanceY = CalculateDistance()
	local Head = Character:WaitForChild("Head")
	local HRP = Character:WaitForChild("HumanoidRootPart")
	Camera.CFrame = HRP.CFrame + Vector3.new(0,(Head.Position.Y-HRP.Position.Y),0)
	if AllowRotate then
		Camera.CFrame = Camera.CFrame * CFrame.Angles(-DistanceY/TurnConstraint,-DistanceX/TurnConstraint,0)
	end
end

local active = false
local manual = false
function ChangeCamera(title,state)
	if state == Enum.UserInputState.Begin or manual then
		active = not active
		if active then
			UpdateTransparency(1)
			Camera.CameraType = Enum.CameraType.Scriptable
			Humanoid.AutoRotate = false
			RunService:BindToRenderStep("CameraAdjust",1,AdjustCamera)
		else
			UpdateTransparency(0)
			Camera.CameraType = Enum.CameraType.Custom
			RunService:UnbindFromRenderStep("CameraAdjust")
			Humanoid.AutoRotate = true
			Player.CameraMinZoomDistance = 10
			wait(0.1)
			Player.CameraMinZoomDistance = 0.5
		end
	end
end

--Input
ContextActionService:BindAction("ChangeCamera",ChangeCamera,EnableMobileButton,KeyboardKeybind,ControllerKeybind)
--ButtonConfigs
if ButtonAdjustsWithJumpButton and UserInputService.TouchEnabled then
	local JumpButton = Player.PlayerGui:WaitForChild("TouchGui").TouchControlFrame:WaitForChild("JumpButton")
	ContextActionService:SetPosition("ChangeCamera", JumpButton.Position-ButtonPosition)
else
	ContextActionService:SetPosition("ChangeCamera", ButtonPosition)
end
ContextActionService:SetImage("ChangeCamera", ButtonImage)
ContextActionService:SetTitle("ChangeCamera", ButtonTitle)

--Reset
Humanoid.Died:Connect(function()
	manual = true
	active = true
	ChangeCamera()
	ContextActionService:UnbindAction("ChangeCamera")
end)

This is a pretty common problem with CFrame.Angles
The problem is that the X (pitch, or up and down) angle is applied before the Y (yaw, or side to side) angle
When you rotate up and down before side to side, it rotates the entire Y axis and causes it to tilt, as demonstrated in the gif:

This can be easily fixed by using
CFrame.FromEulerAnglesYXZ() rather than CFrame.Angles in this particular instance

This will cause the Y angle to be applied before the X, resulting in a “properly” rotated camera

2 Likes

Interesting. Thanks for the help!

1 Like