Custom camera script glitches out past 81 degree rotations on the x axis?

So this is my first time creating a custom camera script and I’m looking into it because I’ve been messing around with trying to create space-like physics for another game of mine. I was hoping to essentially allow the user to rotate fully around in any direction, without having a limit at the top and bottom, to sort of remove the player’s sense of up and down.

I managed to disable the default camera scripts and I got most of my custom camera working in a basic local script inside of StarterPlayerScripts, but whenever I look up or down past 81 degrees, it glitches out and doesn’t let me keep going.

Here’s what I mean: link

Here’s the part of the script that finds the normal to adjust the camera to:

-- Mouse Moved
uis.InputChanged:Connect(function(inputObj)
	if inputObj.UserInputType == Enum.UserInputType.MouseMovement and rcDown then

		-- Finding Deltas
		local dx = inputObj.Delta.X
		local dy = inputObj.Delta.Y

		-- Creating Reference Part
		local part = Instance.new("Part")
		part.Transparency = 1
		part.CanCollide = false
		part.Anchored = true

		part.CFrame = cam.CFrame
		part.Orientation = Vector3.new(part.Orientation.X,part.Orientation.Y,0) + Vector3.new(-dy*0.5,-dx*0.5,0)

		--[[if math.abs(part.Orientation.X) > 81 then
			part.CFrame = cam.CFrame
			part.Orientation = Vector3.new(part.Orientation.X,part.Orientation.Y,0) + Vector3.new(dy*0.5,dx*0.5,0)
		end]] -- I used this in attempt to maybe flip the camera upside down once it reaches the glitching point, but all it managed to do was stop it from spazzing out as much.

		camNormal = part.CFrame.LookVector
		debris:AddItem(part,0)

	end
end)

Here’s what it does when I add the part I commented out: link

I’m not sure if there’s a better way of finding the normal without creating a part and taking it’s LookVector, but either way, here’s the full script if needed:

-- Player Stuff
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:wait()
local root = char:WaitForChild("HumanoidRootPart")

-- Character Added
player.CharacterAdded:Connect(function(Char)

	-- Character Stuff
	char = Char
	root = char:WaitForChild("HumanoidRootPart")

end)

-- Mouse Stuff
local mouse = player:GetMouse()

-- Services
local uis = game:GetService("UserInputService")
local runService = game:GetService("RunService")
local debris = game:GetService("Debris")
local ts = game:GetService("TweenService")

-- Camera Stuff
local cam = workspace.CurrentCamera
cam.CameraSubject = root

local zoom = 10
local maxZoom = 500
local minZoom = 1
local zInterval = 5
local camNormal = -cam.CameraSubject.CFrame.LookVector

-- Other
local rcDown = false

-- Right Click Down
mouse.Button2Down:Connect(function()
	rcDown = true
end)

mouse.Button2Up:Connect(function()
	rcDown = false
end)

-- Zooming
mouse.WheelForward:Connect(function()
	if zoom - zInterval > minZoom then
		zoom = zoom - zInterval
	end
end)

mouse.WheelBackward:Connect(function()
	if zoom + zInterval < maxZoom then
		zoom = zoom + zInterval
	end
end)

-- Updating Camera
local function tweenPos(cf,obj)

	local info = TweenInfo.new(
		0.05,
		Enum.EasingStyle.Sine,
		Enum.EasingDirection.InOut,
		0,false,0
	)

	local properties = {
		CFrame = cf
	}

	local tween = ts:Create(obj,info,properties)
	tween:Play()
	return tween

end

local oldT = nil
runService.Stepped:Connect(function()

	if oldT and oldT.PlaybackState == Enum.PlaybackState.Playing then
		oldT:Cancel()
	end

	oldT = tweenPos(CFrame.new(cam.CameraSubject.Position + camNormal*-zoom, root.Position),cam)

end)

-- Mouse Moved
uis.InputChanged:Connect(function(inputObj)
	if inputObj.UserInputType == Enum.UserInputType.MouseMovement and rcDown then

		-- Finding Deltas
		local dx = inputObj.Delta.X
		local dy = inputObj.Delta.Y

		-- Creating Reference Part
		local part = Instance.new("Part")
		part.Transparency = 1
		part.CanCollide = false
		part.Anchored = true

		part.CFrame = cam.CFrame
		part.Orientation = Vector3.new(part.Orientation.X,part.Orientation.Y,0) + Vector3.new(-dy*0.5,-dx*0.5,0)

		--[[if math.abs(part.Orientation.X) > 81 then
			part.CFrame = cam.CFrame
			part.Orientation = Vector3.new(part.Orientation.X,part.Orientation.Y,0) + Vector3.new(dy*0.5,dx*0.5,0)
		end]] -- I used this in attempt to maybe flip the camera upside down once it reaches the glitching point, but all it managed to do was stop it from spazzing out as much.

		camNormal = part.CFrame.LookVector
		debris:AddItem(part,0)

	end
end)

I looked around on some topics, and I found one topic from a while back that was talking about creating a custom camera like this, but I could never quite figure out what to do. One person there fully created such a camera and posted a roblox game file, but when I opened it up it was no longer functional.

I decided to look into the actual script for that camera and noticed they used various things like self and metatables that I’ve heard about, but I don’t really quite understand yet. I figured I’d just try doing it my own way with a simple local script, and here’s where I am now. Would it be more efficient to figure out this other person’s scripts and learn to recreate it in a way that works, or is there a relatively easy fix for what I have at the moment?

2 Likes

Without taking an in-depth look at what you’ve got, I see you’re using
CFrame.new(pos, lookat).

At high pitch angles (around 82 degrees), you may experience numerical instability using that.

Try using CFrame.lookAt(pos, lookat) though I’m not sure if this results in the same numerical instability.

1 Like

Hm, okay. I tried it with using CFrame.lookAt() instead and it works better now. It doesn’t glitch out at 81-82 degrees anymore, but now it spins around right at 90 degrees and never goes upside down like I want it to.

video

I don’t know how you would fix this but I can suggest an alternative system.

  1. game.Workspace.Gravity = 0 (turn off gravity)
  2. Humanoid.PlatformStand = true (means that players just float around. they will not be able to walk or jump and they will not stay upright. Although rotation may still occur when player rotates their camera.)
  3. Add controls to allow you to rotate or move the player
  4. Lock player into 1st person, other camera stuff may need doing.
1 Like

You want it to not stop rotating at ± 90, but rather just keep going?
It’s probably because of how you’re doing this… Orientation property can be a little weird… from what I’ve seen, it clamps to 180 degrees, and anything more than that starts changing the other axes.
I’d suggest ditching the reference part, and do rotations like this:

CAM_ROTATION = CAM_ROTATION + rotation * math.rad(CAM_ROTATION_SPEED)
CAM_ROTATION = Vector2.new(CAM_ROTATION.X, math.clamp(CAM_ROTATION.Y, -math.pi/2, math.pi/2))
local cameraRotationCFrame = CFrame.Angles(0, -CAM_ROTATION.X, 0)*CFrame.Angles(-CAM_ROTATION.Y, 0, 0)

where rotation variable is delta and Cam_Rotation is a Vector2 defined elsewhere, but that’s based on your situation. If you’d want it to not clamp at top and bottom, you would remove the math.clamp line. (This is taken out from my camera module, so context may lack - let me know)

You would just set the camera CFrame by doing it like: Camera.CFrame = CFrame.new(position) * cameraRotationCFrame

1 Like

Sorry for the late response, but I’m not 100% sure I understood everything in your example. Would something like this be sort of the same thing as you’re saying?:

local oldT = nil
runService.Stepped:Connect(function()
	
	if oldT and oldT.PlaybackState == Enum.PlaybackState.Playing then
		oldT:Cancel()
	end
	
	oldT = tweenPos(CFrame.lookAt(cam.CameraSubject.Position + camNormal*-cSettings.Zoom.Current, root.Position),cam)
	
end)

-- Mouse Moved
uis.InputChanged:Connect(function(inputObj)
	if inputObj.UserInputType == Enum.UserInputType.MouseMovement and rcDown then
		
		-- Finding Deltas
		local dx = inputObj.Delta.X*cSettings.Sensitivity
		local dy = inputObj.Delta.Y*cSettings.Sensitivity
		
		-- Rotating Camera
		local cf = cam.CFrame * CFrame.Angles(-dy/40,0,0) * CFrame.Angles(0,-dx/40,0)
		
		-- Finding Normal
		camNormal = cf.LookVector
		tweenPos(CFrame.lookAt(cam.CameraSubject.Position + camNormal*-cSettings.Zoom.Current, root.Position),cam)
		
	end
end)

It seems to work to rotate the camera, but it still spins and glitches out right at ± 90 degrees.

video

I’m not sure about the issue… You should be able to use the CFrame without tweening though. Tweening may have something to do with it, but I can give it a more in-depth look later.

I realize you are trying to make it rotate around your character, not freely, which I didn’t do, so I may be missing something.

The issue could also be in the camNormal*Zomm part, could try removing that to see what, if anything, changes. You removed the reference part, right?

When it comes to tweening into the CFrame, I only do that in order to make the movement of the camera smoother, since otherwise—from my previous testing—it sometimes lags behind and has a glitchy motion without the tweening.

Here’s what it looks like without the tweening: link
(It might be hard to see in that video, but when I jump the camera doesn’t keep up super well, and it also still has the issue with not being able to rotate all the way around.)

Also, I’m not entirely sure what you mean by rotating the camera freely rather than around the character. How do you make the camera face the character without rotating the camera around the character?

When it comes to removing the camNormal * Zoom part, the camNormal*Zoom part is the part that actually positions the camera so it’s facing the character. I rotate the camera using

local cf = cam.CFrame * CFrame.Angles(-dy/40,0,0) * CFrame.Angles(0,-dx/40,0)

and then I take the LookVector of that CFrame to adjust the position of the camera so that it actually faces towards the character from a distance of the zoom in studs. In other words, I find the rotation, and based on that rotation, I position the camera. I’m not sure how I could remove that without completely reworking how the camera moves and rotates.

Last but not least, yes, I removed the reference part. I was originally using that because I didn’t know how else to position the camera so it faces the character, so I used a part to just use the orientation property, but then after your last message, I got the idea to change it to how I explained above, since that’s much more efficient.

I found a solution! Thank you to everyone who helped me with this issue, and I definitely learned some things I didn’t know before, but here’s how I did it to not stop at the top and bottom:

-- Updating Zoom
runService.RenderStepped:Connect(function()
	
	local angles = cam.CFrame - cam.CFrame.Position
	cam.CFrame = root.CFrame * CFrame.new(cam.CFrame.LookVector*-cSettings.Zoom.Current) * angles
	
end)

-- Mouse Moved
uis.InputChanged:Connect(function(inputObj)
	if inputObj.UserInputType == Enum.UserInputType.MouseMovement then
		
		-- Finding Deltas
		local dx = (inputObj.Delta.X/50)*cSettings.Sensitivity
		local dy = (inputObj.Delta.Y/50)*cSettings.Sensitivity
		
		-- Rotating Camera
		local angles = (cam.CFrame - cam.CFrame.Position) * CFrame.Angles(-dy,-dx,0)
		cam.CFrame = root.CFrame * CFrame.new(cam.CFrame.LookVector*-cSettings.Zoom.Current) * angles
		
	end
end)

For the most part I just simply changed how I was creating the CFrame for the camera. Rather than using CFrame.lookAt and what not, I just simply separated the CFrame.Angles of the camera, added the delta X and delta Y to it, then set the camera’s CFrame to the root’s position plus the offset/zoom * the new angles.

I also removed the rcDown variable since I realized that was unnecessary.

Here’s what it looks like for anyone who’s curious:
https://gyazo.com/e797a1ac44d3c46dba0a271b03caf57c

3 Likes