Pitching Camera Angle - Code & Design | 3D Platformer Camera

This is both a coding and design question I think.

I’d like to angle the player’s Camera (the pitch specifically) for ease of platforming, but I’m not sure what the best course of action is for this system. Should it be running all the time, meaning that the camera auto-adjusts whenever the angle isn’t at the angle I’d want it so be?

Roblox’s Follow camera type covers the horizontal side of things, but not the pitch/vertical. My current system for pitching the camera could be less intrusive on the players. And, I think Roblox’s Follow camera does a great job at this.

  • I’m looking at Roblox’s CameraModule scripts, specifically the ClassicCamera Script, to see what makes that a ‘good’ implementation. This is probably a key part of it :
-- Reset tween speed if user is panning
 if CameraInput.getRotation() ~= Vector2.new() then
 	tweenSpeed = 0
 	self.lastUserPanCamera = tick()
 end
  • I’m having trouble understanding the system as a whole, and how to use it for my system, but I’m trying to dissect to. Shot in the dark, but maybe @AllYourBlox can jump in with thoughts.

Here’s the code I have currently (it is located under StarterPlayerScripts):

click to open
local RunService = game:GetService("RunService")

local checkDebounce = false
local playerHasPitchAngleControlEnabled = true
local lastCameraCFrame
local pitchAngleNumber = math.rad(-15)
local cameraIsInNormalMode = false
local count = 0
local camera = game.Workspace.CurrentCamera
local run

local function moveCameraFunc()
	local countForMovingCamera = 0
	
	run = RunService.PostSimulation:Connect(function(deltaTime)
		local x,y,z = game.Workspace.CurrentCamera.CFrame:ToEulerAnglesXYZ()
		local goal = CFrame.Angles(pitchAngleNumber,y,z)	
		camera.CFrame = camera.CFrame:Lerp(goal,0.05)	
		countForMovingCamera = countForMovingCamera + 1
		
		task.wait(deltaTime)
		
		if countForMovingCamera >= 100 then
			print("done!")
			run:Disconnect()
		end		
		
	end)
	
	
end


local function checkMovementOfCameraFunc()
	while checkDebounce == true do
		task.wait(1)
					
		-- check if camera is already at the angle that we want to set it to
		-- if it's already at the angle, then no need to set it?
			
		if lastCameraCFrame == nil then
			lastCameraCFrame = game.Workspace.CurrentCamera.CFrame
		else
			if game.Workspace.CurrentCamera.CFrame == lastCameraCFrame then
				count = count + 1
				if count == 5 then
					-- waited 5 seconds
					print("camera CFrame has been the same for 5 seconds!")
					count = 0
					lastCameraCFrame = nil
					--checkDebounce = false
					moveCameraFunc()
				end
			else
				--print("camera CFrame is different")
				lastCameraCFrame = nil
				count = 0
			end 
		end

		
	end
end

script.TogglePitchControl.Event:Connect(function(toggleState)
	cameraIsInNormalMode = toggleState
	checkDebounce = toggleState
	
	if cameraIsInNormalMode == true then
		checkMovementOfCameraFunc()
	end
end)

Video of what I have so far:


Ultimately, I’d like to make a good camera system for my 3d platformer. I’d say that means keeping the pitch/angle of the camera at an angle where the player can be comfortable platforming/moving in 3d space. Also, I’ll have an option to toggle the automatic pitch changing.

Fixed my issue with the wrong angle, timing, and I found out how to stop the code from running when a player moves their camera. Still open to ideas on making it better. Feel free to DM.

Code:
local RunService = game:GetService("RunService")
local CameraInput_RobloxScript = require(script.Parent:WaitForChild("PlayerModule"):WaitForChild("CameraModule"):WaitForChild("CameraInput"))
local TellTheRestOfTheGameThe_PauseState = game.Workspace.RemoteEventsFolder.TellTheRestOfTheGameThe_PauseState
local isTheGamePaused = false

local togglePitchAdjustment_BindableEvent = game.Workspace.RemoteEventsFolder.OptionsMenuToggles.TogglePitchAdjustment
local playerAllowsSettingThePitch = true

local checkDebounce = false
local playerHasPitchAngleControlEnabled = true
local lastCameraCFrame
local pitchAngleNumber_degree = -20
local cameraIsInNormalMode = false
local count = 0
local camera = game.Workspace.CurrentCamera
local run = nil
local countUpForAlpha = 0.02
 
local function moveCameraFunc()
	local countForMovingCamera = 0
		
	run = RunService.PostSimulation:Connect(function(deltaTime)
		
		if CameraInput_RobloxScript.getRotationActivated() == true or countForMovingCamera >= 1 or isTheGamePaused == true then
			run:Disconnect()
			run = nil
		end
		
		local x,y,z = game.Workspace.CurrentCamera.CFrame:ToEulerAnglesXYZ()
		local goal = CFrame.Angles(math.rad(pitchAngleNumber_degree),0,0)
		
		camera.CFrame = camera.CFrame:Lerp(goal,countUpForAlpha*2)	
		countForMovingCamera = countForMovingCamera + countUpForAlpha/2.5
		
		task.wait(deltaTime)
		
	end)
	
	--print("done?!")
		
	
end


local function checkMovementOfCameraFunc()
	while checkDebounce == true and playerAllowsSettingThePitch == true do
		task.wait(1)
		
		if isTheGamePaused == false then

			local x,y,z = game.Workspace.CurrentCamera.CFrame:ToEulerAnglesXYZ()
			if x >= pitchAngleNumber_degree - 1 and x <= pitchAngleNumber_degree + 1 then
				-- the angle is already fine

			else
				if lastCameraCFrame == nil then
					lastCameraCFrame = game.Workspace.CurrentCamera.CFrame
				else
					if game.Workspace.CurrentCamera.CFrame == lastCameraCFrame then
						count = count + 1
						if count == 4 then
							-- waited x seconds
							--print("camera CFrame has been the same for x seconds!")
							count = 0
							lastCameraCFrame = nil
							--checkDebounce = false
							
							if run ~= nil then
								run:Disconnect()
							end
							
							moveCameraFunc()
						end
					else
						--print("camera CFrame is different")
						lastCameraCFrame = nil
						count = 0
					end 
				end

			end	

		end
		
		
	end
end

script.TogglePitchControl.Event:Connect(function(toggleState)
	cameraIsInNormalMode = toggleState
	checkDebounce = toggleState
	
	if cameraIsInNormalMode == true then
		checkMovementOfCameraFunc()
	end
end)

TellTheRestOfTheGameThe_PauseState.OnClientEvent:Connect(function(state)
	isTheGamePaused = state
end)

togglePitchAdjustment_BindableEvent.Event:Connect(function(isPitchAdjustmentEnabled)
	playerAllowsSettingThePitch = isPitchAdjustmentEnabled
end)

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