Help with Fixed Top-Down Camera Rotation

So in the game I’m currently working on the camera is in a fixed top down position (with a slight angle). My goal is to allow the player to rotate the screen using Q and E. So I created the following script:

local UserInputService = game:GetService("UserInputService")
local rot = script.rot
local num = script.num
local q = script.q
local e = script.e

local function onInputPressed(inputObject,gameProcessedEvent)
	if gameProcessedEvent then return end
	if inputObject.KeyCode.Name == "Q" then
		q.Value = 1
	elseif inputObject.KeyCode.Name == "E" then
		e.Value = -1
	elseif inputObject.KeyCode.Name == "R" then
		rot.Value = 0
	end
	num.Value = q.Value + e.Value
end

local function onInputReleased(inputObject,gameProcessedEvent)
	if gameProcessedEvent then return end
	if inputObject.KeyCode.Name == "Q" then
		q.Value = 0
	elseif inputObject.KeyCode.Name == "E" then
		e.Value = 0
	end
	num.Value = q.Value + e.Value
end

UserInputService.InputBegan:Connect(onInputPressed)
UserInputService.InputEnded:Connect(onInputReleased)

Basically all it does is set the variable q to 1 when it’s pressed and e to -1 when it’s pressed. They are also both set back to 0 when released. It then adds those two numbers to make the variable num. Num is what’s used to determine the way the player is trying to rotate their screen. Next is a script I literally named loop:

local input = script.Parent.Input
local rot = input.rot
while true do
	wait()
	rot.Value = rot.Value + input.num.Value*2
end

This is just a loop to constantly add the value of num to rot (short for rotation). So if the player is holding q num will grow negative, if they’re holding e it will grow positive, when pressing nothing (or both q+e) it will stop (because num would be = 0).

Now we finally get to the part I’m having trouble with (sorry this got kind of long the above code probably could have been summed up easily). Anyways this is the camera code:

local rotation = script.Parent.Parent.PlayerScripts.Input.rot
local tilt = 15
local dist = 28

while not game.Players.LocalPlayer.Character:FindFirstChild('Head') do wait() end

local cam = workspace.CurrentCamera
cam.CameraType = 'Custom'
cam.CameraType = 'Scriptable'
cam.FieldOfView = 60

local RunService = game:GetService('RunService')
while RunService.Stepped:wait() do
    local head = game.Players.LocalPlayer.Character:FindFirstChild('Head')
    local distance = dist
    local headPos = head.Position
    cam.CFrame = CFrame.new(Vector3.new(headPos.x-tilt, headPos.y + distance, headPos.Z + rotation.Value),headPos)
end

Now there aren’t any errors or anything like that, the code does do exactly what it’s supposed to, but not what I want it to. This was originally just for locking the camera into a top down position, so I had hoped I could easily just add the rotation variable in there and everything would work out, but things are not always that easy. When I added the rotation variable it does sort of work, but with a major problem: the rotation works for about 30 degrees but then the camera pans away from the player and stalls about the half way point only distancing itself instead of rotating. The goal was to make it smoothly rotate around the player in a nice consistent manner, but I really don’t know where to go from here. I get why it’s doing that (sort of), and it was silly to think I could just throw that number in the camera somewhere and expect it to work. I have looked around a bit on various different places and haven’t found a solution, probably partially because I don’t even know what to look for. I suspect it would require some serious math but maybe not I could be wrong (as per usual).

Anyways this is my first post on here sorry it got so incredibly long I’ll do better in the future. Any and all help/advice would be greatly appreciated thanks in advance.

2 Likes

Everything is correct apart from:

The issue is how you’re CFraming the camera - the rotation value (rot.Value) is all working correctly! We’re rotating the camera in the X and Z axis, but in the camera CFrame only the X value is dependent on the tilt and only the Z value is dependent on the rotation. Depending on the value of rot.Value, we may want to give the X variable a lot of tilt or none at all, the same for the Z value. We can do this using trigonometry, where we can get values between 1 and -1 based on an angle.

In the below diagram, there is a lot going on - just take a look at the theta value in the top left corner (this corresponds to rot.Value), and the cos and sin values in the bottom right corner. You see how they’re changing based on the angle?

We can use these to weight the X and Z axis depending on our angle of rotation.

The new camera script is as follows:

local rotation = script.Parent.Input.rot
local tilt = 15
local dist = 28

while not (game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character:FindFirstChild('Head')) do wait() end

local cam = workspace.CurrentCamera
cam.CameraType = 'Custom'
cam.CameraType = 'Scriptable'
cam.FieldOfView = 60

local RunService = game:GetService('RunService')
while RunService.Stepped:wait() do
    local head = game.Players.LocalPlayer.Character:FindFirstChild('Head')
    local distance = dist
    local headPos = head.Position
	
	local rot = math.rad(rotation.Value); --math.sin and math.cos use radians
	local x;
	local z;
	x = headPos.x + math.sin(rot) * tilt;
	z = headPos.z + math.cos(rot) * tilt;
	--print('x:', x, '  z:', z, '  tilt:', tilt, '  rot:', rot, '  sin:', math.sin(rot), '  cos:', math.cos(rot))
	
    cam.CFrame = CFrame.new(Vector3.new(x, headPos.y + distance, z),headPos)
end

Is a bit slow but I’m sure you can figure out how to speed it up!

3 Likes

Absolutely stunning! Was not expecting such an awesome response thank you so much and I can definitely speed it up myself thanks!

1 Like