Camera has strange behavior when rotating in more than 1 direction

  1. What do you want to achieve?
    Have to move the camera horizontally and vertically at the same time produce expected results (move both horizontally and vertically at regular speed).

  2. What is the issue?
    The camera has an interesting result when doing so.

  3. What solutions have you tried so far?
    A lot of different methods for rotating the camera. Unfortunately, none have worked without flaws. This is the best I can get at the momment.

Here is all of the script code (although all of the actual camera work happens in the RenderStepped function)

local STORAGE = game:GetService("ReplicatedStorage")
local INPUT = game:GetService("UserInputService")
local SOUND = game:GetService("SoundService")
local RUN = game:GetService("RunService")
local ui = workspace:WaitForChild("screen"):WaitForChild("g")
local progress = 0
local movecamera = true
local lastrot = nil
local keyboardgaming = false
local onsides = {false,false,false,false}
local sides = {}

local function resetsides()
	local function fromscale(X,Y)
		return Vector2.new(math.floor(X * workspace.CurrentCamera.ViewportSize.X),math.floor(Y * (workspace.CurrentCamera.ViewportSize.Y + 36)))
	end

	sides[1] = {fromscale(1,0.1),Vector2.new(0,0)}
	sides[2] = {fromscale(0.1,1),Vector2.new(0,0)}
	sides[3] = {fromscale(1,0.1),fromscale(0,0.9)}
	sides[4] = {fromscale(0.1,1),fromscale(0.9,0)}
end

resetsides()

workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
	resetsides()
end)

workspace.CurrentCamera:GetPropertyChangedSignal("CameraType"):Connect(function()
	workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
	workspace.CurrentCamera.CFrame = CFrame.new(0,0,0) * CFrame.Angles(0,math.rad(180),0)
end)

workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
workspace.CurrentCamera.CFrame = CFrame.new(0,0,0) * CFrame.Angles(0,math.rad(180),0)

game:GetService("StarterGui"):SetCoreGuiEnabled(Enum.CoreGuiType.All,false)

game:GetService("RunService").RenderStepped:Connect(function()
	game:GetService("Players"):ClearAllChildren()
end)

INPUT.InputBegan:Connect(function(key)
	if movecamera then
		if INPUT.KeyboardEnabled and keyboardgaming ~= nil then
			if key.KeyCode == Enum.KeyCode.W then
				keyboardgaming = true
				onsides[1] = true
			elseif key.KeyCode == Enum.KeyCode.A then
				keyboardgaming = true
				onsides[2] = true
			elseif key.KeyCode == Enum.KeyCode.S then
				keyboardgaming = true
				onsides[3] = true
			elseif key.KeyCode == Enum.KeyCode.D then
				keyboardgaming = true
				onsides[4] = true
			end
		end
		
		if key.UserInputType == Enum.UserInputType.Gyro then
			
		end
	end
end)

INPUT.InputEnded:Connect(function(key)
	if movecamera then
		if key.KeyCode == Enum.KeyCode.W then
			keyboardgaming = false
			onsides[1] = false
		elseif key.KeyCode == Enum.KeyCode.A then
			keyboardgaming = false
			onsides[2] = false
		elseif key.KeyCode == Enum.KeyCode.S then
			keyboardgaming = false
			onsides[3] = false
		elseif key.KeyCode == Enum.KeyCode.D then
			keyboardgaming = false
			onsides[4] = false
		end
	end
end)

INPUT.InputChanged:Connect(function(key)
	if key.UserInputType == Enum.UserInputType.MouseMovement and keyboardgaming ~= true then
		for x = 1,4 do
			if key.Position.X > sides[x][2].X and key.Position.Y > (sides[x][2].Y - 36) and key.Position.X < sides[x][1].X + sides[x][2].X and key.Position.Y < (sides[x][1].Y + sides[x][2].Y - 36) then
				keyboardgaming = nil
				onsides[x] = true
			else
				onsides[x] = false
			end
		end
	end
end)

if INPUT.GyroscopeEnabled then
	INPUT.DeviceRotationChanged:Connect(function(rotation,rot)
		if keyboardgaming ~= nil and keyboardgaming ~= true then
			if not lastrot then
				lastrot = rot
			end

			local delta = rot * lastrot:inverse()
			local x,y,z = delta:toEulerAnglesXYZ()

			delta = CFrame.Angles(x,-y,z)
			workspace.CurrentCamera.CFrame *= delta

			lastrot = rot
		end
	end)
end

RUN.RenderStepped:Connect(function(delta)
	if movecamera then
		local none = true
		
		for x = 1,4 do
			if onsides[x] then
				none = false
				local _,_,_,m00,m01,m02,_,_,m12,_,_,m22 = workspace.CurrentCamera.CFrame:GetComponents()
				local angle = CFrame.fromOrientation(math.atan2(-m12, m22),math.asin(m02),math.atan2(-m01, m00))
				
				if x % 2 == 0 then
					workspace.CurrentCamera.CFrame *= CFrame.Angles(angle.X,math.rad(angle.Y + (x == 2 and 180 * delta or -180 * delta)),angle.Z)
				else
					workspace.CurrentCamera.CFrame *= CFrame.Angles(math.rad(angle.X + (x == 1 and 180 * delta or -180 * delta)),angle.Y,angle.Z)
				end
			end
		end
		
		if none and keyboardgaming == nil then
			keyboardgaming = false
		end
	end
end)
1 Like

I think this is the same issue as what I described here:

1 Like

The new version doesn’t work too great

RUN.RenderStepped:Connect(function(delta)
	if movecamera then
		local none = true
		
		for x = 1,4 do
			if onsides[x] then
				none = false
				local _,_,_,m00,m01,m02,_,_,m12,_,_,m22 = workspace.CurrentCamera.CFrame:GetComponents()
				local angle = CFrame.fromOrientation(math.atan2(-m12, m22),math.asin(m02),math.atan2(-m01, m00))
				
				if x % 2 == 0 then
					workspace.CurrentCamera.CFrame *= workspace.CurrentCamera.CFrame:ToObjectSpace(CFrame.Angles(0,(x == 1 and 180 * delta or -180 * delta),0))
				else
					workspace.CurrentCamera.CFrame *= CFrame.Angles((x == 2 and 180 * delta or -180 * delta),0,0) 
				end
			end
		end
		
		if none and keyboardgaming == nil then
			keyboardgaming = false
		end
	end
end)

It’s not clear to me what the purpose of all this is. Do you want the camera to rotate according to mouse movement, like in normal FPS or 3rd person cameras?

You can ignore the angle variable as with the current method it doesn’t do anything.
The game is meant to support multiple input types for the camera (mouse, keyboard, gyro (which has its own separate functionality), etc).

To do this I have each input type edit a table called onsides containing 4 booleans (W,A,S,D). Then every time RenderStepped is fired I move the camera. The intended behavior is for the camera to be able to rotate in all 4 directions at 360 degrees. The camera is also never intended to move as the game is meant to function similarly to your standard VR experience (without any VR of course).

1 Like

To further elaborate, the mouse moves the camera if the player moves the mouse to a certain side/corner of the screen in which case the camera moves relative to said side/corner.

1 Like

Could you try this?

RUN.RenderStepped:Connect(function(dt)
	if movecamera then
		local pitchAngVel = (onsides[1] - onsides[3]) * cameraPitchSpeed
                local yawAngVel = (onsides[2] - onsides[4]) * cameraYawSpeed
                
                camera.CFrame *= CFrame.Angles(pitchAngVel * dt, 0, 0)
                camera.CFrame *= camera.CFrame:ToObjectSpace( CFrame.Angles(0, pitchAngVel * dt, 0) )
		
		if none and keyboardgaming == nil then
			keyboardgaming = false
		end
	end
end)

In case I’m not reading your script right, here’s a reference implementation that shows what I’m trying to do:

local RunS = game:GetService("RunService")
local InputS = game:GetService("UserInputService")

local Camera = game.Workspace.CurrentCamera
do --Setup Camera
	Camera.CameraType = "Scriptable"
	Camera.CFrame = CFrame.new(0, 10, 0)
	
	local c
	c = Camera:GetPropertyChangedSignal("CameraType"):Connect(function()
		Camera.CameraType = "Scriptable"
		c:Disconnect()
	end)
end

local inputState = {
	camera = {
		["pitchUp"] = 0,
		["pitchDown"] = 0,
		["yawLeft"] = 0,
		["yawRight"] = 0
	}
}

function getInput()
	-- if keyboardEnabled then
	getKeyboardInput()
	-- end	
end

function getKeyboardInput()
	inputState.camera.pitchUp = InputS:IsKeyDown(Enum.KeyCode.W) and 1 or 0
	inputState.camera.pitchDown = InputS:IsKeyDown(Enum.KeyCode.S) and 1 or 0
	inputState.camera.yawLeft = InputS:IsKeyDown(Enum.KeyCode.A) and 1 or 0
	inputState.camera.yawRight = InputS:IsKeyDown(Enum.KeyCode.D) and 1 or 0
end

--Rotates the camera according to inputState.camera
function updateCamera(dt)
	local rotateAmount = getCameraAngularVelocity() * dt
	if (rotateAmount.Magnitude > 0) then
		Camera.CFrame = pitchCameraCFrame(rotateAmount.X)
		Camera.CFrame = yawCameraCFrame(rotateAmount.Y)
	end
end

function getCameraAngularVelocity()
	local pitch = inputState.camera.pitchUp - inputState.camera.pitchDown
	local yaw 	= inputState.camera.yawLeft - inputState.camera.yawRight
	return Vector3.new(pitch, yaw, 0)
end

-- Returns the CFrame that the camera should be set to if its current CFrame were pitched up by pitchAmount.
-- Rotates the camera on its local X axis.
function pitchCameraCFrame(pitchAmount)

	return Camera.CFrame * CFrame.Angles(pitchAmount, 0, 0)
end

-- Returns the CFrame that the camera should be set to if its current CFrame were yawed left by yawAmount.
-- Rotates the camera on the world Y axis.
function yawCameraCFrame(yawAmount)
	local worldUp = Vector3.new(0, 1, 0)
	local localUp = Camera.CFrame:VectorToObjectSpace(worldUp)
	return Camera.CFrame * CFrame.fromAxisAngle(localUp, yawAmount)
end

RunS.RenderStepped:Connect(function(dt)
	getInput()
	updateCamera(dt)
end)
1 Like

Thank you! While it’s not a perfect solution (moving in both directions constantly switches from right-side-up to upside-down) it does do its job!

end result from the renderstepped event:

RUN.RenderStepped:Connect(function(delta)
	if movecamera and onsides ~= {false,false,false,false} then
		local rotate = Vector3.new(((onsides[1] and 1 or 0) - (onsides[3] and 1 or 0)),((onsides[2] and 1 or 0) - (onsides[4] and 1 or 0)), 0) * delta
		if (rotate.Magnitude > 0) then
			workspace.CurrentCamera.CFrame *= CFrame.Angles(rotate.X,0,0)
			workspace.CurrentCamera.CFrame *= CFrame.fromAxisAngle(workspace.CurrentCamera.CFrame:VectorToObjectSpace(Vector3.new(0,1,0)),rotate.Y)
		end
		
	elseif keyboardgaming == nil then
			keyboardgaming = false
	end
end)
1 Like