FPS Unlocker causing problems with Heartbeat

I’m having issues where people with FPS unlockers are able to go faster in a boat. I don’t want to disable or force players who are using these to turn them off, but how I can prevent it from causing issues with this particular render stepped and still run smoothly for users in 60fps.

function BoatController:UpdateMovement()
	local MoveVector = self.ControlModule:GetMoveVector() -- Player controls
	
	script.Inputs.Forward.Value = MoveVector.Z <= -0.2 -- Forward -> -1 Y
	script.Inputs.Backward.Value = MoveVector.Z >= 0.2 -- Backward -> 1 Y
	script.Inputs.Right.Value = MoveVector.X >= 0.2 -- Right -> 1 X
	script.Inputs.Left.Value = MoveVector.X <= -0.2 -- Left -> -1 X
	
	if self.CurrentBoat then
		local NewDriveVector = Vector3.new(
			0,
			0,
			math.clamp(script.ForwardAmount.Value * script.SlowdownPercent.Value, -1, 1)
		)
		local BoatBaseCFrame = self.CurrentBoat.CFrame
		local TrueDirectionVector = BoatBaseCFrame:VectorToWorldSpace(NewDriveVector)
		local HorizontalVector = TrueDirectionVector * Vector3.new(1, 0, 1)
		
		local Speed = self.BoatMaxSpeed
		if CacheController.Data.ActiveBoosts["Speed"] then
			Speed *= 2
		end
		
		if CacheController.Data.PowerUps["Speed"] then
			Speed *= 2
		end
		
		self.BodyVelocity.Velocity = HorizontalVector * Speed
		
		local Multiplier = math.sign(NewDriveVector.Z)
		
		self.BodyGyro.CFrame = self.BodyGyro.CFrame * CFrame.Angles(0, math.rad((self.Turn / 10) * Vector3.new(0, 0, NewDriveVector.Z).Magnitude * Multiplier), 0)
		
		-- Boat rocking
		local BoatSpeed = self.CurrentBoat.AssemblyLinearVelocity.Magnitude / 10
		local SlowdownPercent = script.SlowdownPercent.Value
		SlowdownPercent = math.clamp(1 - SlowdownPercent - .5, 0, 1)
		self.CurrentBoat.RootPart.CFrame = self.CurrentBoat.CFrame * self.RootOffset 
			* CFrame.Angles(math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) ) * self.RockMagnitude * SlowdownPercent * BoatSpeed), 
				0, 
				math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) * 2) * self.RockMagnitude * SlowdownPercent * BoatSpeed))
	end
end

--// Update movement
	RunService.Heartbeat:Connect(function()
		self:UpdateMovement()
	end)
3 Likes

Heartbeat passes a dt argument that you’re supposed to scale any time-dependent changes by to ensure that speeds are frame-rate independent. You really shouldn’t count on people having 60 fps.

2 Likes

btw, dt is short for delta time (or delta), which is how long it’s been since the last frame. You can multiply your speed by the delta to make the speed consistent between any FPS.

RunService.Heartbeat:Connect(function(Delta)
    Thing.Position += Direction * (Speed * Delta)
    --You can use (Speed * Delta * 60) or * 120 if you don't want to adjust the Speed variable.
    --You'd need to adjust the Speed variable because Delta tends to be shorter than 1 (1 / 60 in most cases),
    --which means things will move slower.
end)

(Note: Delta is passed with all RunService events, which, AFAIK, are Heartbeat, Stepped and RenderStepped)

2 Likes

When I do

self.BodyVelocity.Velocity = HorizontalVector * (Speed * deltaTime)

the boat just doesn’t move

1 Like

It’s because deltaTime is generally a very small number. You could try changing it to

self.BodyVelocity.Velocity = HorizontalVector * (Speed * deltaTime * 120)

or

self.BodyVelocity.Velocity = HorizontalVector * (Speed * deltaTime * 60)

This made people with higher fps go slower, and people with lower fps go faster tho :confused:


I went from 60FPS cap, no FPS cap, and then 30FPS cap. Very noticeable difference in the boats speed

1 Like

That shouldn’t be happening. Can you send how you’re making the boat move?

In the video example, SlowdownPercent is at 1 the entire time, and ForwardAmount is at -1 (for the sake of getting a clear video, I had both of them constants, in game they do change, but even if there’s problems with them at a constant, then there’s gonna be problems when they change too)

function BoatController:UpdateMovement(deltaTime)
	local MoveVector = self.ControlModule:GetMoveVector()
	
	script.Inputs.Forward.Value = MoveVector.Z <= -0.2 -- Forward -> -1 Y
	script.Inputs.Backward.Value = MoveVector.Z >= 0.2 -- Backward -> 1 Y
	script.Inputs.Right.Value = MoveVector.X >= 0.2 -- Right -> 1 X
	script.Inputs.Left.Value = MoveVector.X <= -0.2 -- Left -> -1 X
	
	if self.CurrentBoat then
		local NewDriveVector = Vector3.new(
			0,
			0,
			math.clamp(script.ForwardAmount.Value * script.SlowdownPercent.Value, -1, 1)
		)
		local BoatBaseCFrame = self.CurrentBoat.CFrame
		local TrueDirectionVector = BoatBaseCFrame:VectorToWorldSpace(NewDriveVector)
		local HorizontalVector = TrueDirectionVector * Vector3.new(1, 0, 1)
		
		local Speed = self.BoatMaxSpeed
		if CacheController.Data.ActiveBoosts["Speed"] then
			Speed *= 2
		end
		
		if CacheController.Data.PowerUps["Speed"] then
			Speed *= 2
		end
		
		self.BodyVelocity.Velocity = HorizontalVector.Unit * (Speed * deltaTime * 60)
		--print("DELTA TIME:", deltaTime, "MAGNITUDE", self.BodyVelocity.Velocity.Magnitude)
		
		local Multiplier = math.sign(NewDriveVector.Z)
		
		self.BodyGyro.CFrame = self.BodyGyro.CFrame * CFrame.Angles(0, math.rad((self.Turn / 10) * Vector3.new(0, 0, NewDriveVector.Z).Magnitude * Multiplier), 0)
		
		-- Boat rocking
		local BoatSpeed = self.CurrentBoat.AssemblyLinearVelocity.Magnitude / 10
		local SlowdownPercent = script.SlowdownPercent.Value
		SlowdownPercent = math.clamp(1 - SlowdownPercent - .5, 0, 1)
		self.CurrentBoat.RootPart.CFrame = self.CurrentBoat.CFrame * self.RootOffset 
			* CFrame.Angles(math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) ) * self.RockMagnitude * SlowdownPercent * BoatSpeed), 
				0, 
				math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) * 2) * self.RockMagnitude * SlowdownPercent * BoatSpeed))
	end
end



script.Inputs.Forward.Changed:Connect(function()
	if script.Inputs.Forward.Value then
		TweenService:Create(
			script.ForwardAmount,
			TweenInfo.new(0.8),
			{
				Value = -1
			}
		):Play()
	else
		TweenService:Create(
			script.ForwardAmount,
			TweenInfo.new(2.5),
			{
				Value = 0
			}
		):Play()
	end
end)

--// Backward
script.Inputs.Backward.Changed:Connect(function()
	if script.Inputs.Backward.Value then
		TweenService:Create(
			script.ForwardAmount,
			TweenInfo.new(0.8),
			{
				Value = 1
			}
		):Play()
	else
		TweenService:Create(
			script.ForwardAmount,
			TweenInfo.new(2.5),
			{
				Value = 0
			}
		):Play()
	end
end)

and the ControlModule is the Roblox one,

self.ControlModule = require(Player:WaitForChild("PlayerScripts"):WaitForChild("PlayerModule"):WaitForChild("ControlModule"))

With the print I commented out too, the lower delta time meant higher velocity. Velocity needs to maintain a consistent speed, regardless of the delta time

1 Like

Just curious what is the real physics fps when running the fps unlocker using this function Workspace:GetRealPhysicsFPS?

The way I see it the main movement is the BodyVelocity which should be constant as you have set initially.

If it goes faster with higher fps even though the velocity is constant 2 studs per second then that means time is going faster.

Nothing else is effecting the translational movement right?

Like does this CFraming effect the movement? I’m not sure what variables you have set like RootOffset.

		SlowdownPercent = math.clamp(1 - SlowdownPercent - .5, 0, 1)
local pos1 = self.CurrentBoat.RootPart.CFrame.Position
		self.CurrentBoat.RootPart.CFrame = self.CurrentBoat.CFrame * self.RootOffset 
			* CFrame.Angles(math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) ) * self.RockMagnitude * SlowdownPercent * BoatSpeed), 
				0, 
				math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) * 2) * self.RockMagnitude * SlowdownPercent * BoatSpeed))

local pos2 = self.CurrentBoat.RootPart.CFrame.Position
print((pos2-pos1).Magnitude, "Any distance changes by CFraming?")

It constantly prints 60, regardless of what FPS I have the cap at

Alright that means the body velocity should be ok, what about the CFraming? Apologies it should be this to track into account horizontal movement.

local pos1 = self.CurrentBoat.RootPart.CFrame.Position*Vector3.new(1,0,1)
		self.CurrentBoat.RootPart.CFrame = self.CurrentBoat.CFrame * self.RootOffset 
			* CFrame.Angles(math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) ) * self.RockMagnitude * SlowdownPercent * BoatSpeed), 
				0, 
				math.rad(math.sin(tick() * self.RockSpeed + (self.RockSpeed * SlowdownPercent) * 2) * self.RockMagnitude * SlowdownPercent * BoatSpeed))

local pos2 = self.CurrentBoat.RootPart.CFrame.Position*Vector3.new(1,0,1)
print((pos2-pos1).Magnitude, "Any horizontal distance movement caused by CFraming?")

have u found a solution to this, even task.wait() or task.wait(1/60) gets faster or slower depending on client fps, its important for client visuals so the server doesnt overload

As other said it passes delta time, you can + delta time until it will be >= 1 and then you can do stuff, it will make it do every 1 second no matter the fps (At least that how it should work)

(Did not knew its old, still no solution so why not help)