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.

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.

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:

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.

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.

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)

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.

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).

-- 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)