FPS unlocker makes ADS and movements like weapon sway unstable/broken

Hello! I’m in the middle of making a weapon framework and have been testing everything just fine so far; Only when i used my FPS unlocker i’ve noticed really bad results. Everything is/was acting as if it’s being done twice, and ADS being massively broken.

Note: I’ve implemented an FPS counter to help with diagnostics. It’s on the top of the screen.

--i threw it in here in case someone wants to check if it's correct
spawn(function()
	fps = 0
	spawn(function()
		while game:GetService("RunService").RenderStepped:Wait() do
			fps = fps + 1
		end	
	end)
	
	while wait(1) do
		game.Players.LocalPlayer.PlayerGui.Game.FPS.Text = "FPS : "..fps
		fps = 0
	end
end)

I checked up on framerate independent motion and added * deltaTime * 60 to any instances of the springs being shoved inside RenderStepped. This “seems” to have fixed most of bobbing issues but i’m still left with ADS not working. Example:

Locked:
https://gyazo.com/03565bc2c790f6923796932d388ed7da

Unlocked:
https://gyazo.com/e57fe72170e38332a59b6e13723e8a6c

Second try was limiting deltaTime to 1/60;

deltaTime = math.max(1/60,deltaTime)

Unlocked:
https://gyazo.com/f2ab99c00cbb76e59f75977857d3a6e7

Locked:
https://gyazo.com/aecebc8955f7fcdb58c017d79753dec1

I don’t have any other solutions to try since i’m pretty clueless about this.

Bobbing is calculated every frame, like this: (but mutliple times with multiple springs for other movements like sway)

local speed = 2
local modifier = 4
local workBobble = Vector3.new(math.sin(tick()*5*speed)*modifier, math.sin(tick()*10*speed)*modifier, 0)-- Vector3.new(Bobble_X,Bobble_Y,0) 
self.springs.bobble:shove(workBobble * deltaTime * scalar)

Aiming itself is a bit more complex than that. Every frame 2 offsets (holding and aiming) are lerped between each other based on a spring’s X output. This output is pushed when clicking RMB and adjusted in the renderstepped function itself. both the adjustment and initial push have custom values.

Custom values:

--values
local ADS_AIMDOWNDIVIDER = 30 --30
local ADS_AIMDOWNSPEED =  0.6--0.6

RenderStepped function:

--lerping adjustment
self.lastAimLerp = self.aimLerp
self.aimLerp = self.aimLerp + self.springs.aim:update(deltaTime).X 
		
if math.clamp(self.aimLerp,1,1) ~= self.aimLerp and self.aiming then
	self.springs.aim:shove(Vector3.new(((1 - self.aimLerp) / ADS_AIMDOWNDIVIDER),0,0) * deltaTime * scalar)
end 
		
if not self.aiming then
	self.aimLerp = self.aimLerp / 1.1	
end

--offset calculation
local offset = self.gun.Properties.HoldOffset.Value:lerp(self.gun.Properties.AimOffset.Value,self.aimLerp)
self.gun.Handle.CFrame = self.viewmodel.camera.CFrame:ToWorldSpace(offset)

Aiming function:

function fpsHandler:aim(toAim)
		self.aiming = toAim
		local tweeningInformation = TweenInfo.new(0.6, Enum.EasingStyle.Cubic,Enum.EasingDirection.Out)
		if toAim then
			local properties = { FieldOfView = self.properties.aimFOV or self.baseFOV }
			local tween = tweenService:Create(camera,tweeningInformation,properties)
			tween:Play()

			self.springs.aim:shove(Vector3.new(ADS_AIMDOWNSPEED,0,0))	
		else
			local properties = { FieldOfView = self.baseFOV }
			local tween = tweenService:Create(camera,tweeningInformation,properties)
			tween:Play()			
			
			self.springs.aim:shove(Vector3.new(-ADS_AIMDOWNSPEED,0,0))
		end
	end

end
3 Likes

The problem is most likely due to the accuracy that Roblox can run at. Whether it be number accuracy or timing accuracy, you’re probably moving too much at a time over the very small frame increments. You can try simply limiting the fps your script runs at like this:

-- deltaTime is from the RenderStepped function.
local originalDeltaTime = deltaTime
while deltaTime < 1/60+1/((1/originalDeltaTime)+4) do
	deltaTime = deltaTime + RenderStepped:Wait()
end
-- The fps the script will run at is now limited to 60 fps plus one actual frame with 4 frames extra.

*how does this script work, exactly?

It will count up frame time until the total frame time is >= 1/60+1/((1/originalDeltaTime)+4).
1/(1/60) is 60. So 1/((1/originalDeltaTime)+4) is 1/(fps+4) where fps is estimated based on delta time.

I’ve implemented it:

function fpsHandler:update(deltaTime)
	
	-- deltaTime is from the RenderStepped function.
	local originalDeltaTime = deltaTime
	while deltaTime < 1/60+1/((1/originalDeltaTime)+4) do
		deltaTime = deltaTime + game:getService("RunService").RenderStepped:Wait()
	end
	-- The fps the script will run at is now limited to 60 fps plus one actual frame with 4 frames extra.
	
--script goes on here, please don't get angry at me for copy pasting

But i get mixed results. This seems to work just like the math.max method, only with more inconsistent results. the gun jitters around whether the game is unlocked or not. Like this:
https://gyazo.com/7bc36c787ce27e82a90f31685a4806aa

Edit: It’s possible to adjust the gun’s position BEFORE the wait time to fix jittering, but it doesn’t solve the ADS issues either way.

I believe I know what the issue is there as well as what actually might be the overall issue. Rather than using RenderStepped:Connect to fire the fpsHandler:update function try making a while loop like this:

spawn(function()
	while true do
		local deltaTime = RenderStepped:Wait()
		local success, err = pcall(function()
			fpsHandler:update(deltaTime) -- This will run as fast as possible without overlapping onto other frames. If this doesn't fix the issue than my other idea mentioned may work if combined with this.
		end)
	end
end)

The issue may be caused by overlapping frames. If your code runs slower than their FPS it will be running multiple times.

using this alone, or together with the other solution, brought really bad results. I did run into an idea: Updating the springs once every 1/60th a second, while updating the module’s position every frame. I’m going to try to implement this one way or another to see how it could work.

I’ve run into another problem: having the springs applied/updated 1/4th of the frames would cause major jittering. Instead, i think i’m going to have the springs only shoved on part of the frames, not updated. this could fix the issues.

As it turns out, it doesn’t seem to work. Maybe because i’ve implemented it badly or not.

spawn(function()
	while true do
		local deltaTime = game:GetService("RunService").RenderStepped:Wait()
		local originalDeltaTime = deltaTime
		while deltaTime < 1/60+1/((1/originalDeltaTime)+4) do
			deltaTime = deltaTime + game:getService("RunService").RenderStepped:Wait()
		end
		local success, err = pcall(function()
			controller:update(deltaTime,false) -- This will run as fast as possible without overlapping onto other frames. If this doesn't fix the issue than my other idea mentioned may work if combined with this.
		end)
	end
end)

spawn(function()
	while true do
		local deltaTime = game:GetService("RunService").RenderStepped:Wait()
		controller:update(deltaTime,true) -- This will run as fast as possible without overlapping onto other frames. If this doesn't fix the issue than my other idea mentioned may work if combined with this.
	end
end)

the second parameter is checked in any parts where springs are shoved (movement, aiming adjusted) and this both breaks 60 FPS gameplay and worsens 240 FPS (everything runs just about faster than with the math.max solution)
unlocked:
https://gyazo.com/9fa1077b3f78ba60ce4dc3d9258f9bd3
locked:
https://gyazo.com/5c7a25c69b0f952f2f3a5f9245f80b37