How to Add Roll-Changing on Z Coordinate in Roblox's Free Camera Script

Hello there!
Recently, I have encountered an issue of genuinely not understanding Roblox’s Free Cam script. I would like to add the ability to change Roll on the camera, with Y and U buttons, but I have no idea on how to do it. Any pointers would be greatly appreciated. The most (probably) important function here is StepFreecam, and all I need to do is to add the ability to change Z coordinate. How would I do it? Below is the entire FreeCam script (STIFFNESS values modified)

local plr = game.Players.LocalPlayer
local whitelist = {"c0rtus"} ----ADD USERNAMES HERE FOR EXAMPLE {"c0rtus";"nva_f";"DefaultBrain"}
if not table.find(whitelist, plr.Name) then script:Destroy() end

------------------------------------------------------------------------
-- Freecam
-- Cinematic free camera for spectating and video production.

------------------------------------------------------------------------

local pi    = math.pi
local abs   = math.abs
local clamp = math.clamp
local exp   = math.exp
local rad   = math.rad
local sign  = math.sign
local sqrt  = math.sqrt
local tan   = math.tan

local ContextActionService = game:GetService("ContextActionService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local StarterGui = game:GetService("StarterGui")
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")

local LocalPlayer = Players.LocalPlayer
if not LocalPlayer then
	Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
	LocalPlayer = Players.LocalPlayer
end

local Camera = Workspace.CurrentCamera

Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
	local newCamera = Workspace.CurrentCamera
	if newCamera then
		Camera = newCamera
	end
end)


------------------------------------------------------------------------

local TOGGLE_INPUT_PRIORITY = Enum.ContextActionPriority.Low.Value
local INPUT_PRIORITY = Enum.ContextActionPriority.High.Value
local FREECAM_MACRO_KB = {Enum.KeyCode.LeftShift, Enum.KeyCode.P}

local NAV_GAIN = Vector3.new(1, 1, 1)*64
local PAN_GAIN = Vector2.new(0.75, 1)*8
local FOV_GAIN = 200

local PITCH_LIMIT = rad(90)

local VEL_STIFFNESS = 1
local PAN_STIFFNESS = 0.5
local FOV_STIFFNESS = 0.5

------------------------------------------------------------------------

local Spring = {} do
	Spring.__index = Spring

	function Spring.new(freq, pos)
		local self = setmetatable({}, Spring)
		self.f = freq
		self.p = pos
		self.v = pos*0
		return self
	end

	function Spring:Update(dt, goal)
		local f = self.f*2*pi
		local p0 = self.p
		local v0 = self.v

		local offset = goal - p0
		local decay = exp(-f*dt)

		local p1 = goal + (v0*dt - offset*(f*dt + 1))*decay
		local v1 = (f*dt*(offset*f - v0) + v0)*decay

		self.p = p1
		self.v = v1

		return p1
	end

	function Spring:Reset(pos)
		self.p = pos
		self.v = pos*0
	end
end

------------------------------------------------------------------------

local cameraPos = Vector3.new()
local cameraRot = Vector2.new()
local cameraFov = 0

local velSpring = Spring.new(VEL_STIFFNESS, Vector3.new())
local panSpring = Spring.new(PAN_STIFFNESS, Vector2.new())
local fovSpring = Spring.new(FOV_STIFFNESS, 0)

------------------------------------------------------------------------

local Input = {} do
	local thumbstickCurve do
		local K_CURVATURE = 2.0
		local K_DEADZONE = 0.15

		local function fCurve(x)
			return (exp(K_CURVATURE*x) - 1)/(exp(K_CURVATURE) - 1)
		end

		local function fDeadzone(x)
			return fCurve((x - K_DEADZONE)/(1 - K_DEADZONE))
		end

		function thumbstickCurve(x)
			return sign(x)*clamp(fDeadzone(abs(x)), 0, 1)
		end
	end

	local gamepad = {
		ButtonX = 0,
		ButtonY = 0,
		DPadDown = 0,
		DPadUp = 0,
		ButtonL2 = 0,
		ButtonR2 = 0,
		Thumbstick1 = Vector2.new(),
		Thumbstick2 = Vector2.new(),
	}

	local keyboard = {
		W = 0,
		A = 0,
		S = 0,
		D = 0,
		E = 0,
		Q = 0,
		U = 0,
		H = 0,
		J = 0,
		K = 0,
		I = 0,
		Y = 0,
		Up = 0,
		Down = 0,
		LeftShift = 0,
		RightShift = 0,
	}

	local mouse = {
		Delta = Vector2.new(),
		MouseWheel = 0,
	}

	local NAV_GAMEPAD_SPEED  = Vector3.new(1, 1, 1)
	local NAV_KEYBOARD_SPEED = Vector3.new(1, 1, 1)
	local PAN_MOUSE_SPEED    = Vector2.new(0.5, 0.5)*(pi/64)
	local PAN_GAMEPAD_SPEED  = Vector2.new(1, 1)*(pi/8)
	local FOV_WHEEL_SPEED    = 1
	local FOV_GAMEPAD_SPEED  = 0.25
	local NAV_ADJ_SPEED      = 0.8
	local NAV_SHIFT_MUL      = 0.18

	local navSpeed = 1

	function Input.Vel(dt)
		navSpeed = clamp(navSpeed + dt*(keyboard.Up - keyboard.Down)*NAV_ADJ_SPEED, 0.01, 4)

		local kGamepad = Vector3.new(
			thumbstickCurve(gamepad.Thumbstick1.X),
			thumbstickCurve(gamepad.ButtonR2) - thumbstickCurve(gamepad.ButtonL2),
			thumbstickCurve(-gamepad.Thumbstick1.Y)
		)*NAV_GAMEPAD_SPEED

		local kKeyboard = Vector3.new(
			keyboard.D - keyboard.A + keyboard.K - keyboard.H,
			keyboard.E - keyboard.Q + keyboard.I - keyboard.Y,
			keyboard.S - keyboard.W + keyboard.J - keyboard.U
		)*NAV_KEYBOARD_SPEED

		local shift = UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) or UserInputService:IsKeyDown(Enum.KeyCode.RightShift)

		return (kGamepad + kKeyboard)*(navSpeed*(shift and NAV_SHIFT_MUL or 1))
	end

	function Input.Pan(dt)
		local kGamepad = Vector2.new(
			thumbstickCurve(gamepad.Thumbstick2.Y),
			thumbstickCurve(-gamepad.Thumbstick2.X)
		)*PAN_GAMEPAD_SPEED
		local kMouse = mouse.Delta*PAN_MOUSE_SPEED
		mouse.Delta = Vector2.new()
		return kGamepad + kMouse
	end
	
	function Input.Fov(dt)
		local kGamepad = (gamepad.ButtonX - gamepad.ButtonY)*FOV_GAMEPAD_SPEED
		local kMouse = mouse.MouseWheel*FOV_WHEEL_SPEED
		mouse.MouseWheel = 0
		return kGamepad + kMouse
	end

	do
		local function Keypress(action, state, input)
			keyboard[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
			return Enum.ContextActionResult.Sink
		end

		local function GpButton(action, state, input)
			gamepad[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
			return Enum.ContextActionResult.Sink
		end

		local function MousePan(action, state, input)
			local delta = input.Delta
			mouse.Delta = Vector2.new(-delta.y, -delta.x)
			return Enum.ContextActionResult.Sink
		end

		local function Thumb(action, state, input)
			gamepad[input.KeyCode.Name] = input.Position
			return Enum.ContextActionResult.Sink
		end

		local function Trigger(action, state, input)
			gamepad[input.KeyCode.Name] = input.Position.z
			return Enum.ContextActionResult.Sink
		end

		local function MouseWheel(action, state, input)
			mouse[input.UserInputType.Name] = -input.Position.z
			return Enum.ContextActionResult.Sink
		end

		local function Zero(t)
			for k, v in pairs(t) do
				t[k] = v*0
			end
		end

		function Input.StartCapture()
			ContextActionService:BindActionAtPriority("FreecamKeyboard", Keypress, false, INPUT_PRIORITY,
				Enum.KeyCode.W, Enum.KeyCode.U,
				Enum.KeyCode.A, Enum.KeyCode.H,
				Enum.KeyCode.S, Enum.KeyCode.J,
				Enum.KeyCode.D, Enum.KeyCode.K,
				Enum.KeyCode.E, Enum.KeyCode.I,
				Enum.KeyCode.Q, Enum.KeyCode.Y,
				Enum.KeyCode.Up, Enum.KeyCode.Down
			)
			ContextActionService:BindActionAtPriority("FreecamMousePan",          MousePan,   false, INPUT_PRIORITY, Enum.UserInputType.MouseMovement)
			ContextActionService:BindActionAtPriority("FreecamMouseWheel",        MouseWheel, false, INPUT_PRIORITY, Enum.UserInputType.MouseWheel)
			ContextActionService:BindActionAtPriority("FreecamGamepadButton",     GpButton,   false, INPUT_PRIORITY, Enum.KeyCode.ButtonX, Enum.KeyCode.ButtonY)
			ContextActionService:BindActionAtPriority("FreecamGamepadTrigger",    Trigger,    false, INPUT_PRIORITY, Enum.KeyCode.ButtonR2, Enum.KeyCode.ButtonL2)
			ContextActionService:BindActionAtPriority("FreecamGamepadThumbstick", Thumb,      false, INPUT_PRIORITY, Enum.KeyCode.Thumbstick1, Enum.KeyCode.Thumbstick2)
		end

		function Input.StopCapture()
			navSpeed = 1
			Zero(gamepad)
			Zero(keyboard)
			Zero(mouse)
			ContextActionService:UnbindAction("FreecamKeyboard")
			ContextActionService:UnbindAction("FreecamMousePan")
			ContextActionService:UnbindAction("FreecamMouseWheel")
			ContextActionService:UnbindAction("FreecamGamepadButton")
			ContextActionService:UnbindAction("FreecamGamepadTrigger")
			ContextActionService:UnbindAction("FreecamGamepadThumbstick")
		end
	end
end

local function GetFocusDistance(cameraFrame)
	local znear = 0.1
	local viewport = Camera.ViewportSize
	local projy = 2*tan(cameraFov/2)
	local projx = viewport.x/viewport.y*projy
	local fx = cameraFrame.rightVector
	local fy = cameraFrame.upVector
	local fz = cameraFrame.lookVector

	local minVect = Vector3.new()
	local minDist = 512

	for x = 0, 1, 0.5 do
		for y = 0, 1, 0.5 do
			local cx = (x - 0.5)*projx
			local cy = (y - 0.5)*projy
			local offset = fx*cx - fy*cy + fz
			local origin = cameraFrame.p + offset*znear
			local _, hit = Workspace:FindPartOnRay(Ray.new(origin, offset.unit*minDist))
			local dist = (hit - origin).magnitude
			if minDist > dist then
				minDist = dist
				minVect = offset.unit
			end
		end
	end

	return fz:Dot(minVect)*minDist
end

------------------------------------------------------------------------

local function StepFreecam(dt)
	local vel = velSpring:Update(dt, Input.Vel(dt))
	local pan = panSpring:Update(dt, Input.Pan(dt))
	local fov = fovSpring:Update(dt, Input.Fov(dt))

	local zoomFactor = sqrt(tan(rad(70/2))/tan(rad(cameraFov/2)))

	cameraFov = clamp(cameraFov + fov*FOV_GAIN*(dt/zoomFactor), 1, 120)
	cameraRot = cameraRot + pan*PAN_GAIN*(dt/zoomFactor)
	cameraRot = Vector3.new(clamp(cameraRot.x, -PITCH_LIMIT, PITCH_LIMIT), cameraRot.y%(2*pi))

	local cameraCFrame = CFrame.new(cameraPos)*CFrame.fromOrientation(cameraRot.x, cameraRot.y, 0)*CFrame.new(vel*NAV_GAIN*dt)
	cameraPos = cameraCFrame.p

	Camera.CFrame = cameraCFrame
	Camera.Focus = cameraCFrame*CFrame.new(0, 0, -GetFocusDistance(cameraCFrame))
	Camera.FieldOfView = cameraFov
	
end

------------------------------------------------------------------------

local PlayerState = {} do
	local mouseBehavior
	local mouseIconEnabled
	local cameraType
	local cameraFocus
	local cameraCFrame
	local cameraFieldOfView
	local screenGuis = {}
	local coreGuis = {
		Backpack = true,
		Chat = true,
		Health = true,
		PlayerList = true,
	}
	local setCores = {
		BadgesNotificationsActive = true,
		PointsNotificationsActive = true,
	}

	-- Save state and set up for freecam
	function PlayerState.Push()
		for name in pairs(coreGuis) do
			coreGuis[name] = StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType[name])
			StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType[name], false)
		end
		for name in pairs(setCores) do
			setCores[name] = StarterGui:GetCore(name)
			StarterGui:SetCore(name, false)
		end
		local playergui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
		if playergui then
			for _, gui in pairs(playergui:GetChildren()) do
				if gui:IsA("ScreenGui") and gui.Enabled then
					screenGuis[#screenGuis + 1] = gui
					gui.Enabled = false
				end
			end
		end

		cameraFieldOfView = Camera.FieldOfView
		Camera.FieldOfView = 70

		cameraType = Camera.CameraType
		Camera.CameraType = Enum.CameraType.Custom

		cameraCFrame = Camera.CFrame
		cameraFocus = Camera.Focus

		mouseIconEnabled = UserInputService.MouseIconEnabled
		UserInputService.MouseIconEnabled = false

		mouseBehavior = UserInputService.MouseBehavior
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	end

	-- Restore state
	function PlayerState.Pop()
		for name, isEnabled in pairs(coreGuis) do
			StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType[name], isEnabled)
		end
		for name, isEnabled in pairs(setCores) do
			StarterGui:SetCore(name, isEnabled)
		end
		for _, gui in pairs(screenGuis) do
			if gui.Parent then
				gui.Enabled = true
			end
		end

		Camera.FieldOfView = cameraFieldOfView
		cameraFieldOfView = nil

		Camera.CameraType = cameraType
		cameraType = nil

		Camera.CFrame = cameraCFrame
		cameraCFrame = nil

		Camera.Focus = cameraFocus
		cameraFocus = nil

		UserInputService.MouseIconEnabled = mouseIconEnabled
		mouseIconEnabled = nil

		UserInputService.MouseBehavior = mouseBehavior
		mouseBehavior = nil
	end
end


local function StartFreecam()
	local cameraCFrame = Camera.CFrame
	cameraRot = Vector2.new(cameraCFrame:toEulerAnglesYXZ())
	cameraPos = cameraCFrame.p
	cameraFov = Camera.FieldOfView

	velSpring:Reset(Vector3.new())
	panSpring:Reset(Vector2.new())
	fovSpring:Reset(0)

	PlayerState.Push()
	RunService:BindToRenderStep("Freecam", Enum.RenderPriority.Camera.Value, StepFreecam)
	Input.StartCapture()
end

local function StopFreecam()
	Input.StopCapture()
	RunService:UnbindFromRenderStep("Freecam")
	PlayerState.Pop()
	print("Stopped Freecam")
end

------------------------------------------------------------------------

do
	local enabled = false

	local function ToggleFreecam()
		if enabled then
			StopFreecam()
		else
			StartFreecam()
		end
		enabled = not enabled
	end

	local function CheckMacro(macro)
		for i = 1, #macro - 1 do
			if not UserInputService:IsKeyDown(macro[i]) then
				return
			end
		end
		ToggleFreecam()
	end

	local function HandleActivationInput(action, state, input)
		if state == Enum.UserInputState.Begin then
			if input.KeyCode == FREECAM_MACRO_KB[#FREECAM_MACRO_KB] then
				CheckMacro(FREECAM_MACRO_KB)
			end
		end
		return Enum.ContextActionResult.Pass
	end

	ContextActionService:BindActionAtPriority("FreecamToggle", HandleActivationInput, false, TOGGLE_INPUT_PRIORITY, FREECAM_MACRO_KB[#FREECAM_MACRO_KB])
end
1 Like

You could do this by:

  • Storing a roll angle in a variable
  • Updating the roll variable each render stepped by rollDirection * rollSpeed * dt, where rollDirection is -1, 0, or 1 depending on the input
  • In this line of the StepFreeCam function:

Replace that line with:

-- Roll is rotation around the z axis
local rollOffset = CFrame.Angles(0, 0, math.rad(rollAngle))  -- rollAngle should be stored and updated based on input
-- Rotate the cameraCFrame relatively by the rollOffset
local cameraCFrame = cameraCFrame.CFrame:ToWorldSpace(rollOffset)
-- Update the camera's CFrame
Camera.CFrame = cameraCFrame
1 Like

Got an issue of receiving the following error:

RunService:fireRenderStepEarlyFunctions unexpected error while invoking callback: Players.viberbread.PlayerGui.FreeCam.FreecamScript:77: attempt to perform arithmetic (sub) on nil and Vector3 <

The way I interpreted your script is by:

Adding rollSpring alongside velSpring, panSpring and fovSpring (you can find them in source code)
local rollSpring = Spring.new(VEL_STIFFNESS, Vector3.new())

Adding this as an Input Function, same ones used for Pan and FOV to update rollAngle variable and pass it to Stepfreecam function

function Input.Roll(dt)
		local rollAngle = 0
		UserInputService.InputBegan:Connect(function(input)
			if input.KeyCode == Enum.KeyCode.Y then
				rollAngle = rollAngle - 0.05
				if input.KeyCode == Enum.KeyCode.U then
					rollAngle = rollAngle + 0.05
					return rollAngle
				end
			end
		end)
	end

In Stepfreecam function, I have added
local RollSpring = rollSpring:Update(dt, Input.Roll(dt)) and

local rollOffset = CFrame.Angles(0, 0, math.rad(RollSpring))
	local cameraCFrame = cameraCFrame.CFrame:ToWorldSpace(rollOffset)
	Camera.CFrame = cameraCFrame

RollSpring here should serve as a variable because we update it through Input.Roll(dt) and we return the supposed rollAngle through said Input.Roll(dt), so I dont understand if there’s anything wrong with it. Any advice?

Here you should multiple this by dt so that the roll rate is independent of the frame rate.

For these two if statements, the inner if statement shouldn’t be inside the if statement for the Y input. I would use an if [Y] elseif [U] end kind of thing instead.

I believe the roll angle also should be stored at a higher scope (i.e. the local rollAngle = ... part should go outside the function so it’s not reset each time the function is callled). Otherwise, the roll angle would be reset to 0 + or - 0.05 every frame, instead of the sum of the inputs over time.

What line is this from? This just means that some types didn’t match up, specifically that something was nil that shouldn’t have been.

Another potential problem is that the Input.Roll function doesn’t always return a value (so its result can be nil instead of a number). Make sure to always return the rollAngle from this function (i.e. put it at the end of the function right before the last end). This could be where the nil error is coming from.

1 Like

Still getting both of these issues:

RunService:fireRenderStepEarlyFunctions unexpected error while invoking callback: Players.viberbread.PlayerGui.FreeCam.FreecamScriptUPD:77: attempt to perform arithmetic (sub) on number and Vector3  -  Studio
Players.viberbread.PlayerGui.FreeCam.FreecamScriptUPD:77: attempt to perform arithmetic (sub) on number and Vector3  -  Studio

Seems like this time Input.Roll does reliably return a number.

Current Important parts of code are:

local rollAngle = 0 
	function Input.Roll(dt)
		local rollAngle = 0
		UserInputService.InputBegan:Connect(function(input)
			if input.KeyCode == Enum.KeyCode.Y then
				rollAngle = rollAngle - 0.05 
			else
				if input.KeyCode == Enum.KeyCode.U then
					rollAngle = rollAngle + 0.05
				end
			end
		end)
		return rollAngle
	end

Rest is unchanged

Yeah, with those extra lines it should return a number.

What line is giving the error?

To debug this, just find where the errors is from, find what variable is nil when it shouldn’t be there, then trace the variable back to where it becomes nil.

Both are on line 77, which is:

function Spring:Update(dt, goal)
		local f = self.f*2*pi
		local p0 = self.p
		local v0 = self.v

-	-	local offset = goal - p0
		local decay = exp(-f*dt)

		local p1 = goal + (v0*dt - offset*(f*dt + 1))*decay
		local v1 = (f*dt*(offset*f - v0) + v0)*decay

		self.p = p1
		self.v = v1

		return p1
	end

(line 77 is highlighted with dashes), and I have to be honest, I do not understand anything in here, hence why I’m still posting it here, but seems like we need to introduce a new variable for Roll, as f, v and p all for Fov, Vel and Pan.

Where do you declare rollSpring? If you call the constructor properly (a function that’s something like Spring() or Spring.new()) is should initialize the .f, .v, and .p properties.

So the problem is either that self.p is nil, which would happen if the constructor wasn’t called properly, or that Input.Roll(dt) is returning nil.

You can add print statements before the erroring line to print out goal and p0 to see which is nil when it shouldn’t be. The fact that it thinks either goal or p0 is a vector3 is also problematic.

Apologies, here’s where I declare rollSpring:

local velSpring = Spring.new(VEL_STIFFNESS, Vector3.new())
local panSpring = Spring.new(PAN_STIFFNESS, Vector2.new())
local fovSpring = Spring.new(FOV_STIFFNESS, 0)
local rollSpring = Spring.new(VEL_STIFFNESS, Vector3.new())

No idea if I shouldn’t use VEL_STIFFNESS, but it’s a number declared almost at the start of the source code, and it’s 1. Printing out goal and p0 values results in this, and unless nil = 0 in Luau, everything seems to work fine and return normal values:

Since the roll is a number, a good first fix would be to use 0 where the code has Vector3.new() here.

It’s possible that the nil error is just because the subtraction between a number and the vector3 is failing and a nil value is being stored in the spring. I’d try doing the fix above and seeing if that changes things.

Even with this piece of code now in place:
local rollSpring = Spring.new(VEL_STIFFNESS, 0)
Nothing exactly changed, still gives out the same error.

A thing to note, though, is this line in Stepfreecam Function:
local RollSpring = rollSpring:Update(dt, Input.Roll(dt)), where, I assume, it’s trying to perform an operation between dt and Input.Roll, which, of course, is Vector3. Still, no idea what to do with it.
Another noteworthy thing is this snippet of code:

	function Spring.new(freq, pos)
		local self = setmetatable({}, Spring)
		self.f = freq
		self.p = pos
		self.v = pos*0
		return self
	end

A function that creates a Spring, so, theoretically, we may need to implement something like self.r (r for roll), just an idea incase it’s actually going to turn into a fix

Is VEL_STIFFNESS a number of vector3? I’d swap that out for FOV_STIFFNESS.

You also can just not use the spring code all together if you’re having trouble debugging it. Just store the rollAngle as a variable and add rollSpeed * rollDirection * dt to it instead of doing the spring stuff.

VEL_STIFFNESS is a Vector3, yes, so I just changed it to FOV_STIFFNESS, which resulted in the script working correctly, albeit roll still doesn’t work. Here’s our current Stepfreecam function (where I assume we need to make some changes for roll to work):

local function StepFreecam(dt)
	local vel = velSpring:Update(dt, Input.Vel(dt))
	local pan = panSpring:Update(dt, Input.Pan(dt))
	local fov = fovSpring:Update(dt, Input.Fov(dt))
	local RollSpring = rollSpring:Update(dt, Input.Roll(dt))

	local zoomFactor = sqrt(tan(rad(70/2))/tan(rad(cameraFov/2)))

	cameraFov = clamp(cameraFov + fov*FOV_GAIN*(dt/zoomFactor), 1, 120)
	cameraRot = cameraRot + pan*PAN_GAIN*(dt/zoomFactor)
	cameraRot = Vector3.new(clamp(cameraRot.x, -PITCH_LIMIT, PITCH_LIMIT), cameraRot.y%(2*pi))

	local cameraCFrame = CFrame.new(cameraPos)*CFrame.fromOrientation(cameraRot.x, cameraRot.y, 0)*CFrame.new(vel*NAV_GAIN*dt)
	cameraPos = cameraCFrame.p
	local rollOffset = CFrame.Angles(0, 0, math.rad(RollSpring))
	local cameraCFrame = cameraCFrame.CFrame:ToWorldSpace(rollOffset)
	Camera.CFrame = cameraCFrame
	Camera.Focus = cameraCFrame*CFrame.new(0, 0, -GetFocusDistance(cameraCFrame))
	Camera.FieldOfView = cameraFov
	
end

Everything else is the same, and I’m not sure if I should use the rollSpeed * rollDirection * dt method

You can print out the RollSpring variable (I’d maybe rename that to make it more clear it’s not the spring, maybe something like rollAngle or rollValue) to first check that the roll angle is being updated properly.

Specifically, make sure you fix what I was saying here so that the roll doesn’t always stay at 0:


This should also be just cameraCFrame:ToWorldSpace(...).


If you run into coding problems it’s probably more practical to see what the values involved are doing using print statements or debugging tools (break points, log points, etc).

If you run into coding problems it’s probably more practical to see what the values involved are doing using print statements or debugging tools (break points, log points, etc).

I completely agree with this, but the reason I require so much guidance is due to my insufficient knowledge in scripting logic. I can do simple stuff, but calculations and general complex relations between everything in the script are beyond me.
However, after printing out RollAngle value, it is properly updated. Rollspring cannot be printed since we’re still running into that scripting error.

Current code for Stepfreecam function:

local function StepFreecam(dt)
	local vel = velSpring:Update(dt, Input.Vel(dt))
	local pan = panSpring:Update(dt, Input.Pan(dt))
	local fov = fovSpring:Update(dt, Input.Fov(dt))
	local RollSpring = rollSpring:Update(dt, Input.Roll(dt))
	print("RollSpring")

	local zoomFactor = sqrt(tan(rad(70/2))/tan(rad(cameraFov/2)))

	cameraFov = clamp(cameraFov + fov*FOV_GAIN*(dt/zoomFactor), 1, 120)
	cameraRot = cameraRot + pan*PAN_GAIN*(dt/zoomFactor)
	cameraRot = Vector3.new(clamp(cameraRot.x, -PITCH_LIMIT, PITCH_LIMIT), cameraRot.y%(2*pi))

	local cameraCFrame = CFrame.new(cameraPos)*CFrame.fromOrientation(cameraRot.x, cameraRot.y, 0)*CFrame.new(vel*NAV_GAIN*dt)
	cameraPos = cameraCFrame.p
	local rollOffset = CFrame.Angles(0, 0, math.rad(RollSpring))
	local cameraCFrame = cameraCFrame:ToWorldSpace(rollOffset).
	Camera.CFrame == cameraCFrame
	Camera.Focus = cameraCFrame*CFrame.new(0, 0, -GetFocusDistance(cameraCFrame))
	Camera.FieldOfView = cameraFov

Current code for Input.Roll:

	local rollAngle = 0 
	function Input.Roll(dt)
		UserInputService.InputBegan:Connect(function(input)
			if input.KeyCode == Enum.KeyCode.Y then
				rollAngle = rollAngle - 0.05 
			else
				if input.KeyCode == Enum.KeyCode.U then
					rollAngle = rollAngle + 0.05
				end
			end
		end)
		print(rollAngle)
		return rollAngle
	end

Once again, rollAngle is properly updated, but the scripting error below is still here, on line highlighted with an arrow:

Players.viberbread.PlayerGui.FreeCam.FreecamScriptUPD:78: attempt to perform arithmetic (sub) on number and Vector3 - Studio

	function Spring:Update(dt, goal)
		local f = self.f*2*pi
		local p0 = self.p
		local v0 = self.v

	->	local offset = goal - p0
		local decay = exp(-f*dt)

		local p1 = goal + (v0*dt - offset*(f*dt + 1))*decay
		local v1 = (f*dt*(offset*f - v0) + v0)*decay

		self.p = p1
		self.v = v1

		return p1
	end

Upon printing both goal and p0, the result is approximately in this format:

Any ideas?

Still waiting for a reply on that