View Bobbing script breaks when FPS unlocker used

Problem:

I used this view bobbing script that moves the screen when the player walks or sprints. The view bobbing works perfectly fine when on 60 FPS but when the player's FPS is low or high (Using an FPS unlocker), then the view bobbing either becomes slow/fast depending on the FPS.


60 FPS (The view bobbing I’m aiming for):

360 and 30 FPS (On 360 FPS the screen moves too fast, probably because of how fast the FPS is. On 30 FPS the screen moves too slow, probably because of how slow the FPS is):


Script:

--!strict

-- SERVICES --
local RunS = game:GetService("RunService")
local TweenS = game:GetService("TweenService")

-- VARIABLES --
local Player = game.Players.LocalPlayer
local Char = Player.Character or Player.CharacterAdded:Wait()
local Hum = Char:WaitForChild("Humanoid")
local RootPart = Char:WaitForChild("HumanoidRootPart")
local Camera = workspace.CurrentCamera
--
local TiltIntensity_Folder = script:WaitForChild("TiltIntensity")
local TiltSpeed_Folder = script:WaitForChild("TiltSpeed")
local BobbingIntensity_Folder = script:WaitForChild("BobbingIntensity")
local BobbingSpeed_Folder = script:WaitForChild("BobbingSpeed")
--
local bobbing = nil
local tiltSideSpeed : number = TiltSpeed_Folder.SideSpeed.Value -- Default: 0.05
local tiltSpeed : number = TiltSpeed_Folder.Speed.Value -- Default: 0.08
local tiltIntensity : number = TiltIntensity_Folder.Walking.Value -- Default: 1.5
local bobbingSpeed : number = BobbingSpeed_Folder.Speed.Value -- Default: 0.155
local bobbingIntensityWalking : number = BobbingIntensity_Folder.Walking.Value -- Default: 0.008
local bobbingIntensitySprinting : number = BobbingIntensity_Folder.Sprinting.Value -- Default: 0.02
--
local tilt : number = 0
local tiltValue : number = 0
local sinValue : number = 0

-- FUNCTIONS --
function lerp(a : number, b : number, t : number) : number
	return a + (b - a) * t
end

function calculateSine(speed : number, intensity : number) : CFrame
	sinValue += speed 
	if sinValue > (math.pi * 2) then sinValue = 0 end
	local sineY : number = intensity * math.sin(2 * sinValue)
	local sineX : number = intensity * math.sin(sinValue)
	local sineCFrame : CFrame = CFrame.new(sineX, sineY, 0)
	return sineCFrame
end

function calculateTilt(speed : number, intensity : number) : CFrame
	tiltValue += speed 
	if tiltValue > (math.pi * 2) then 
		tiltValue = 0 
	end
	local sineY : number = intensity * math.sin(2 * tiltValue)
	local sineX : number = intensity * math.sin(tiltValue)
	local sineCFrame : CFrame = CFrame.new(sineX, sineY, 0)
	return sineCFrame
end

local previousSineX : number = 0
local previousSineY : number = 0
local previousTilt : number = 0

-- Camera sway loop
bobbing = RunS.RenderStepped:Connect(function(dt : number)
	-- Check if the humanoid is dead
	if Hum.Health <= 0 then
		bobbing:Disconnect() -- Disconnect view bobbing when dead
		return
	end
	
	local movementVector : Vector3 = Camera.CFrame:VectorToObjectSpace(RootPart.AssemblyLinearVelocity / math.max(Hum.WalkSpeed, 0.01) * tiltIntensity)
	local speedModifier : number = (Hum.WalkSpeed / 16)
	tilt = math.clamp(lerp(tilt, movementVector.X * tiltSideSpeed, 0.1), -0.25, 0.1)

	local sineCFrame : CFrame = calculateSine(bobbingSpeed * speedModifier, movementVector.Magnitude * 1)
	local tiltCFrame : CFrame = calculateTilt(tiltSpeed * 1, movementVector.Magnitude * 1)
	
	local lerpedSineX : number
	local lerpedSineY : number
	
	if speedModifier <= 1 then
		lerpedSineX = lerp(previousSineX, sineCFrame.X, bobbingIntensityWalking * speedModifier)
		lerpedSineY = lerp(previousSineY, sineCFrame.Y, bobbingIntensityWalking - 0.001 * speedModifier) -- Don't move much on Y-axis
		tiltIntensity = TiltIntensity_Folder.Walking.Value
	else
		lerpedSineX = lerp(previousSineX, sineCFrame.X, bobbingIntensitySprinting * (speedModifier * 1.2))
		lerpedSineY = lerp(previousSineY, sineCFrame.Y, bobbingIntensitySprinting - 0.001 * (speedModifier * 1.2)) -- Don't move much on Y-axis
		tiltIntensity = TiltIntensity_Folder.Sprinting.Value
	end
	
	local lerpedTilt : number = lerp(previousTilt, tiltCFrame.Y, 0.001)
	
	TweenS:Create(Camera, TweenInfo.new(dt, Enum.EasingStyle.Quad), {CFrame = Camera.CFrame * CFrame.Angles(0, 0, -tilt + lerpedTilt) * CFrame.new(lerpedSineX, lerpedSineY, 0)}):Play()
	
	previousSineX = lerpedSineX
	previousSineY = lerpedSineY
	previousTilt = lerpedTilt
end)

Failed Solutions:

1.) Changed RenderStepped to Heartbeat, Stepped, etc. It just breaks the camera.

2.) Experimented with "dt" (DeltaTime). Didn't work out for me because I'm not that good at math. I think the solution has something to do with that variable but I just don't know how to use it.


Any kind of help will be very useful since I’m stuck at this, I’m not the best at math.

2 Likes

This is what you should do. Delta time is the amount of time that has elapsed since the last event fired, therefore you can make your scripts adjust to any FPS.

If you can send me the other scripts mentioned here:

I can try to fix your issue.

Hello, thanks for replying!

Those variables basically just leads to folders for settings. You can see the values I used on the script’s comments:

image
image

Feel free to continue asking me if you have any questions!

A simple fix is just use deltatime. TweenInfo.new(DeltaTime * 10)

Thanks for replying but it didn’t really help. It just decreased the intensity of the view bobbing.

Please show a video so I can see what effect it has.

The solution is to incorporate the deltaTime variable. Here’s the reason why:

RenderStepped fires every frame, which means if you have a high framerate, the function will be called a lot more than a lower framerate. If you want a consistent speed amongst multiple frames, you’ll need to do so by using how much time has passed since the last frame (deltaTime). In most cases, you can incorporate deltaTime where you lerp the value for the frame. Specifically, you’d use it with the alpha for the lerp.

That means you should be incorporating deltaTime when you calculate your lerpedSinX, lerpedSineY, and lerpedTilt variables. The reason for this is because those are the variables that carry over to the next frame (previousSineX, previousSineY, previousTilt). You will more than most likely need to adjust the deltaTime when lerping each value to get the desired speed. Here’s a quick example of what I mean:

--Without any speed adjustments
local currentTilt = lerp(previousTilt, targetTilt, deltaTime)

--Made six times faster
local currentTilt = lerp(previousTilt, targetTilt, deltaTime * 6)

On a different note…

Just in case you don’t know, this isn’t where deltaTime should be incorporated. Also, I wouldn’t recommend using TweenService in a loop like this.

1 Like

This is obviously due to the runservice. maybe try using the delta time to slow it down?

Thank you for informing me about not using TweenService for this script! That was a bad idea on my part.

Thank you everything who helped! Thanks to you I already found a solution for this problem!

All I had to do was create a variable that gets the target FPS (60 FPS) and divides it with the current FPS. This makes it so that if the target FPS isn’t reached then the speed would adjust to the variable.
local adjustFPS = TARGET_FPS / (1 / dt)

Here’s the full script:

--!strict

-- SERVICES --
local RunS = game:GetService("RunService")
local TweenS = game:GetService("TweenService")

-- VARIABLES --
local Player = game.Players.LocalPlayer
local Char = Player.Character or Player.CharacterAdded:Wait()
local Hum = Char:WaitForChild("Humanoid")
local RootPart = Char:WaitForChild("HumanoidRootPart")
local Camera = workspace.CurrentCamera
--
local TiltIntensity_Folder = script:WaitForChild("TiltIntensity")
local TiltSpeed_Folder = script:WaitForChild("TiltSpeed")
local BobbingIntensity_Folder = script:WaitForChild("BobbingIntensity")
local BobbingSpeed_Folder = script:WaitForChild("BobbingSpeed")
--
local bobbing = nil
local tiltSideSpeed : number = TiltSpeed_Folder.SideSpeed.Value -- Default: 0.05
local tiltSpeed : number = TiltSpeed_Folder.Speed.Value -- Default: 0.08
local tiltIntensity : number = TiltIntensity_Folder.Walking.Value -- Default: 1.5
local bobbingSpeed : number = BobbingSpeed_Folder.Speed.Value -- Default: 0.155
local bobbingIntensityWalking : number = BobbingIntensity_Folder.Walking.Value -- Default: 0.008
local bobbingIntensitySprinting : number = BobbingIntensity_Folder.Sprinting.Value -- Default: 0.02
--
local tilt : number = 0
local tiltValue : number = 0
local sinValue : number = 0
--
local previousSineX : number = 0
local previousSineY : number = 0
local previousTilt : number = 0

-- SETTINGS --
local TARGET_FPS : number = 60 -- What FPS to aim for

-- FUNCTIONS --
function lerp(a : number, b : number, t : number) : number
	return a + (b - a) * t
end

function calculateSine(speed : number, intensity : number) : CFrame
	sinValue += speed 
	if sinValue > (math.pi * 2) then sinValue = 0 end
	local sineY : number = intensity * math.sin(2 * sinValue)
	local sineX : number = intensity * math.sin(sinValue)
	local sineCFrame : CFrame = CFrame.new(sineX, sineY, 0)
	return sineCFrame
end

function calculateTilt(speed : number, intensity : number) : CFrame
	tiltValue += speed 
	if tiltValue > (math.pi * 2) then 
		tiltValue = 0 
	end
	local sineY : number = intensity * math.sin(2 * tiltValue)
	local sineX : number = intensity * math.sin(tiltValue)
	local sineCFrame : CFrame = CFrame.new(sineX, sineY, 0)
	return sineCFrame
end

-- Camera sway loop
bobbing = RunS.RenderStepped:Connect(function(dt : number)
	-- Check if the humanoid is dead
	if Hum.Health <= 0 then
		bobbing:Disconnect() -- Disconnect view bobbing when dead
		return
	end
	
	local adjustFPS = TARGET_FPS / (1 / dt)
	
	local movementVector : Vector3 = Camera.CFrame:VectorToObjectSpace(RootPart.AssemblyLinearVelocity / math.max(Hum.WalkSpeed, 0.01) * tiltIntensity)
	local speedModifier : number = (Hum.WalkSpeed / 16)
	
	tilt = math.clamp(lerp(tilt, movementVector.X * tiltSideSpeed, 0.1 * adjustFPS), -0.25, 0.1)

	local sineCFrame : CFrame = calculateSine(bobbingSpeed * speedModifier * adjustFPS, movementVector.Magnitude * 1)
	local tiltCFrame : CFrame = calculateTilt(tiltSpeed * 1 * adjustFPS, movementVector.Magnitude * 1)
	
	local lerpedSineX : number
	local lerpedSineY : number
	
	if speedModifier <= 1 then
		lerpedSineX = lerp(previousSineX, sineCFrame.X, bobbingIntensityWalking * speedModifier * adjustFPS)
		lerpedSineY = lerp(previousSineY, sineCFrame.Y, (bobbingIntensityWalking - 0.001) * speedModifier * adjustFPS) -- Don't move much on Y-axis
		tiltIntensity = TiltIntensity_Folder.Walking.Value
	else
		lerpedSineX = lerp(previousSineX, sineCFrame.X, bobbingIntensitySprinting * (speedModifier * 1.2) * adjustFPS)
		lerpedSineY = lerp(previousSineY, sineCFrame.Y, (bobbingIntensitySprinting - 0.001) * (speedModifier * 1.2) * adjustFPS) -- Don't move much on Y-axis
		tiltIntensity = TiltIntensity_Folder.Sprinting.Value
	end
	
	local lerpedTilt : number = lerp(previousTilt, tiltCFrame.Y, 0.001 * adjustFPS)
	
	Camera.CFrame = Camera.CFrame * CFrame.Angles(0, 0, -tilt + lerpedTilt) * CFrame.new(lerpedSineX, lerpedSineY, 0)
	
	previousSineX = lerpedSineX
	previousSineY = lerpedSineY
	previousTilt = lerpedTilt
end)

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