Remove Look Angle Limit

Currently, when you look straight up, your camera stops and doesn’t allow you to move your head up any further. How would I make it so that I can infinitely keep moving my mouse up?

Simply put, I’m making a space game, and I don’t want there to be a down and up.
I’ve tried playing around with Quaternions but I can’t seem to get it to work.

Are you attempting to accomplish this?

If you’ve ever used the Blender camera, this one operates similarly.
If this is what you are searching for, I still have the source and wouldn’t mind sharing it.

Yes, actually, that’s exactly what I’m trying to do. I just would like to know how you did it?

This is the reference

local camera = workspace.CurrentCamera;
local dir = Vector3.new(0,0,1);
local xRot = 0;
local yRot = 0;
local inputService = game:GetService("UserInputService");
local lookAtPos = Vector3.zero;
local sens = 0.5;
local distance = 20;
local range = 80;
local scrollSens = 10;
camera.CameraType = Enum.CameraType.Scriptable;

game:GetService("RunService").RenderStepped:Connect(function(dt)
	local flipUp = false;	
	
	-- Update desired x and y rotation
	yRot = (yRot + inputService:GetMouseDelta().Y * sens) % 360;
	-- We are upside down so flip the x rotation and up for lookat	
	flipUp = yRot >= 90 and yRot < 270;
	xRot = (xRot + (flipUp and -1 or -1) *  inputService:GetMouseDelta().X * sens) % 360;

	-- Calculate pos
	local rot = CFrame.Angles(math.rad(yRot), 0, 0);
	local pos = rot * CFrame.new(dir * distance);
	pos = CFrame.Angles(0, math.rad(xRot), 0) * pos;
	
	-- Calulate up
	-- https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/lookat-function
	local fwd = -pos.Position.Unit;
	local up = flipUp and -1 or 1;
	
	up = Vector3.new(0, up ,0);
	local right = up:Cross(fwd);
	up = fwd:Cross(right);
	
	--lookAtPos = Vector3.new(10,0,0); change position
	
	pos = CFrame.new(lookAtPos) * pos;
	
	local cf = CFrame.lookAt(pos.Position, lookAtPos, up);
	camera.CFrame = cf --camera.CFrame:Lerp(cf, 0.8);	
end)

inputService.InputBegan:Connect(function(input, processed)
	
	if processed then return end
	
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	inputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition;
		
end)

inputService.InputEnded:Connect(function(input, processed)

	if processed then return end
	
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	inputService.MouseBehavior = Enum.MouseBehavior.Default;
	
end)

inputService.InputChanged:Connect(function(input, processed)
	if processed then return end
	
	if (input.UserInputType == Enum.UserInputType.MouseWheel) then
		
		local x = 0
		
		local t = 0 -- time
		local d = 50 -- duration

		--while (t <= d) do
		--	x = math.sin(t/d * (math.pi/2))
		--	distance = math.max(5, distance - input.Position.Z * scrollSens + (x / 100))
		--	t += 1
		--	task.wait()
		--end
		
		distance = math.max(5, distance - input.Position.Z * scrollSens);
	end
end)

You can view my comments that I have left.
ReplicatedFirst is where this script is inserted.


Edit:

There is a “better” and “simpler” way to accomplish this for readers who see this post in the future.
This approach is simpler to understand (at least for me) and uses spherical coordinates.
Trigonometry is used in this method, but it’s not too difficult.

We came at this conclusion on our own, therefore you are not limited to using this method with Roblox.
Lookat functions and similar functions are not used by us.
The only thing we used was a matrix, which is typical of most game engines.

For your information, here is a brief visualization:
Kugelkoord-lokb-e.svg
DVF3U

Only: r; ϕ, phi; θ, theta; r^, r hat; ϕ^, phi hat; θ^, theta hat need to be calculated.
The unit vectors, sometimes referred to as the directions, are the hat products, and the remaining information is required for spherical coordinates.

Be sure to conduct some independent research before continuing to read. The mathematics underlying the camera system will not be discussed; only the camera system will be.

You might also simply copy and paste it. :grinning:


Setting up the script

Make sure CharacterAutoLoads is disabled or that your character load won’t interfere with our camera configurations before we begin. additionally keep in mind that since we lack a character, Streaming Enabled must likewise be disabled.
image
image
image

We must first configure our local script. Like the previous one, this will be located in replicated first.

Create configuration
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local currentCamera = workspace.CurrentCamera
currentCamera.CameraType = Enum.CameraType.Scriptable

local subjectPosition = Vector3.zero -- position the camera will be looking at

local sensitivity = 0.5
local radius = 20 -- distance from subjectPosition
local theta = 0 -- x rotation in degrees
local phi = 0 -- y rotation in degrees

The coordinates

An example of a function to generate spherical coordinates is provided below.

Create spherical coordinates function
function SphericalCoordinate(r: number, theta: number, phi: number)
	local Position = Vector3.new(
		r * math.sin(phi) * math.cos(theta),
		r * math.cos(phi),
		r * math.sin(phi) * math.sin(theta)
	)

	local Hat_r = Vector3.new(
		math.sin(phi) * math.cos(theta),
		math.cos(phi),
		math.sin(phi) * math.sin(theta)
	)

	local Hat_theta = Vector3.new(
		-math.sin(theta),
		0,
		math.cos(theta)
	)

	local Hat_phi = Vector3.new(
		math.cos(phi) * math.cos(theta),
		-math.sin(phi),
		math.cos(phi) * math.sin(theta)
	)

	return {
		Position = Position,
		UpVector = -Hat_phi,
		LeftVector = Hat_theta,
		FrontVector = -Hat_r,
		DownVector = Hat_phi,
		RightVector = -Hat_theta,
		BackVector = Hat_r
	}
end

Locking the mouse

To obtain the mouse delta, the mouse must be manually locked, and I will demonstrate how to do so.

Mouse locking functions
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end)

UserInputService.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end)

Finishing up

The core loop must now be incorporated.

Core loop implementation
RunService.RenderStepped:Connect(function()
	local delta = UserInputService:GetMouseDelta()
	
	theta = (theta + delta.X * sensitivity) % 360
	phi = (phi + delta.Y * sensitivity) % 360
	
	local sphericalCoordinate = SphericalCoordinate(
		radius,
		theta * math.pi / 180, 
		phi * math.pi / 180
	)
	
	currentCamera.CFrame = CFrame.fromMatrix(
		sphericalCoordinate.Position + subjectPosition, 
		sphericalCoordinate.RightVector, 
		sphericalCoordinate.UpVector, 
		sphericalCoordinate.BackVector
	)
end)

What does this operation do?

If you’re curious about why we’re performing these computations:

theta * math.pi / 180,
phi * math.pi / 180

It only changes degrees into radians. Obviously, you could utilize:

math.rad()

When dealing with sensitivity, in my opinion, working with degrees is simpler. :thinking:

Why these directions?

In all honesty, I have no idea why we did, but they do the job.

currentCamera.CFrame = CFrame.fromMatrix(
	sphericalCoordinate.Position + subjectPosition, 
	sphericalCoordinate.RightVector,  -- direction(s) in question
	sphericalCoordinate.UpVector,   -- direction(s) in question
	sphericalCoordinate.BackVector  -- direction(s) in question
)

Resources

Do not understand? no issues! Here are some interactive elements for you to experiment with.

Here is a shorter solution, however it does depend on the existing camera system.


Reflection and result

Please tell me if you believe this might work well for #resources:community-tutorials or #resources:community-resources. I’ll be happy to offer instructions on how to add more features, such as the ability to adjust the camera’s roll or add zooming in and out using the scroll wheel.

The outcome is as follows:

The performance, readability, and scalability of your code are ultimately all that matter.
You can utilize the outdated system if you prefer. In my opinion, this version is better because it is simpler to use than the previous one. For me, it is more scalable.
Anyway, here is the complete source code for all the lazy folks (like me). :smile:

-- Setting up the script
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local currentCamera = workspace.CurrentCamera
currentCamera.CameraType = Enum.CameraType.Scriptable

local subjectPosition = Vector3.zero -- position the camera will be looking at

local sensitivity = 0.5
local radius = 20 -- distance from subjectPosition
local theta = 0 -- x rotation in degrees
local phi = 0 -- y rotation in degrees

-- The coordinates
function SphericalCoordinate(r: number, theta: number, phi: number)
	local Position = Vector3.new(
		r * math.sin(phi) * math.cos(theta),
		r * math.cos(phi),
		r * math.sin(phi) * math.sin(theta)
	)

	local Hat_r = Vector3.new(
		math.sin(phi) * math.cos(theta),
		math.cos(phi),
		math.sin(phi) * math.sin(theta)
	)

	local Hat_theta = Vector3.new(
		-math.sin(theta),
		0,
		math.cos(theta)
	)

	local Hat_phi = Vector3.new(
		math.cos(phi) * math.cos(theta),
		-math.sin(phi),
		math.cos(phi) * math.sin(theta)
	)

	return {
		Position = Position,
		UpVector = -Hat_phi,
		LeftVector = Hat_theta,
		FrontVector = -Hat_r,
		DownVector = Hat_phi,
		RightVector = -Hat_theta,
		BackVector = Hat_r
	}
end

-- Locking the mouse
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end)

UserInputService.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end
	
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end)

-- Finishing up
RunService.RenderStepped:Connect(function()
	local delta = UserInputService:GetMouseDelta()
	
	theta = (theta + delta.X * sensitivity) % 360
	phi = (phi + delta.Y * sensitivity) % 360
	
	local sphericalCoordinate = SphericalCoordinate(
		radius,
		theta * math.pi / 180, 
		phi * math.pi / 180
	)
	
	currentCamera.CFrame = CFrame.fromMatrix(
		sphericalCoordinate.Position + subjectPosition, 
		sphericalCoordinate.RightVector, 
		sphericalCoordinate.UpVector, 
		sphericalCoordinate.BackVector
	)
end)

A follow or heart would be greatly appreciated. :heart:

9 Likes

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