Tilt Script Changes Speed depending on Frame Rate

I’ve been struggling to make this script work independently of the players framerate.

Currently the script will tilt the player correctly at the right speed if they are at 60 fps, but if they have higher fps, for example 240 fps, then they tilt incredibly slowly.

I’ve tried a good bit of things but nothing would work with all frame rates, and look good at the same time.

Any help would be greatly appreciated.

local RunService = game:GetService("RunService")

local char = script.Parent.Parent

local MaxTiltAngle = 6

local RootJoint = char.HumanoidRootPart:WaitForChild'RootJoint'
local RootC0 = RootJoint.C0
local Tilt = CFrame.new()

RunService.RenderStepped:Connect(function(Delta)	
	local MoveDirection = char.HumanoidRootPart.CFrame:VectorToObjectSpace(char.Humanoid.MoveDirection)
	Tilt = Tilt:Lerp(CFrame.Angles(math.rad(-MoveDirection.Z) * MaxTiltAngle, math.rad(-MoveDirection.X) * MaxTiltAngle, 0), 0.1 ^ (1 / (Delta * 60)))
	RootJoint.C0 = RootC0 * Tilt
end)
2 Likes
  • All versions of runservice connections on the client assume the player’s FPS.
  • This is independent of the 60fps cap on the server-side runservice connections.

With these in mind, I’d also like to plug this: Using RunService with an FPS unlocker - #2 by goldenstein64

  • You should avoid using RenderStepped for something like this. I believe Stepped is more appropriate. RenderStepped is not appropriate because this function is more dependent on the “Rendering” phase, rather than “Pre-Rendering” which is what RenderStepped infers.

The best solution would be to swap to do one of the following:

  • [LEAST EFFICIENT] swap to a while task.wait(0.01667) which includes a 0.01667 second (1 frame) wait between each run (ultimately no different than a runservice connection at constant 60fps).
    • I only mention this because this isn’t related to the camera or any other MAJOR system, so it shouldn’t be a noticeable performance change.

OR

  • [MOST EFFICIENT] Adjust your function by using a set linear interpolation formula.
    In the case of your calculation:
    • (1 / (Delta * 60) = (1/[.0167x60]) = .1, then .1^.1 = .8 at 60fps
    • (1 / (Delta * 60) = (1/[.0083x60]) = 2, then .1^2 = .01 at 120fps
    • (1 / (Delta * 60) = (1/[.0042x60]) = 4, then .1^4= .0001 at 240fps
      You’ll notice how diverse these values are; ΔT is shrinking the higher the FPS. Read below for a full explanation and guide.

The problem in your code is not that your calculations are necessarily incorrect; you are properly interpolating between two values, but your range is forever changing.

  • Now from here, you can decide if you want that ‘smooth as butter’ look based on your FPS. [I suggest the clamping method, and I provide it below! I give alternatives because ultimately there is no true ‘right answer’!]

    • If you DO, then you should adjust the final argument for :Lerp() to have a smaller multiplication factor.
      • Adjust 60 to be a smaller number. Smaller number naturally means less impact by smaller delta intervals! [I.e. 30, 15, 10]
      • I’d also get rid of any exponential functions here. Notice how .8 turned to .0001, which is a 8000x change! Stick to multiplication.
    • If you DO NOT want difference based on HIGHER frames, then very simply just clamp your values. I.e. math.clamp(ΔT * 30, 0.1, 0.8) which will not let the value exceed 1 or go below 0.1.
      • This is by far the most efficient and recommended solution.
      • Quite honestly, use this! It is given below as well.
    • If you just want a constant animation, use my previous suggestion of task.wait.
    • You will have to play around with these values, these are examples. I use some below:
  • Code Sample relative to example 2:

local RunService = game:GetService("RunService")

local char = script.Parent.Parent

local MaxTiltAngle = 6

--Ease of Access
local DeltaMultiplier = 10 -- example value for ensuring deltatime is useable for math without excessive calculation
local DeltaClampMin = 0.2 -- example value for the min clamp on calculated values
local DeltaClampMax = 0.8-- example value for the max clamp on calculated values

local RootJoint = char.HumanoidRootPart:WaitForChild'RootJoint'
local RootC0 = RootJoint.C0
local Tilt = CFrame.new()

RunService.Stepped:Connect(function(_, Delta) -- Note stepped includes a time argument, which we dont need.
	local MoveDirection = char.HumanoidRootPart.CFrame:VectorToObjectSpace(char.Humanoid.MoveDirection)
	Tilt = Tilt:Lerp(
         CFrame.Angles(math.rad(-MoveDirection.Z) * MaxTiltAngle, 
         math.rad(-MoveDirection.X) * MaxTiltAngle, 0), 
         math.clamp(Delta*30, DeltaClampMin, DeltaClampMax) -- ΔT clamps, i.e. 60fps .0167*30= .501 not changed, but at 30fps 0.03*30 = .9 clamps to .8 min, and at 120fps 0.00416*30 = .1248 clamps to .2 min
         )  
	RootJoint.C0 = RootC0 * Tilt
end)

[Expanded comment]
ΔT clamps, i.e. 60fps .0167x30= .501 not changed, but at 30fps 0.03x30 = .9 clamps to .8 min, and at 120fps 0.00416x30 = .1248 clamps to .2 min

1 Like

My small brain could barely comprehend what you were talking about, but I kinda understood it enough to figure it out, it still tilts slightly faster at higher frame rates, but wayyyy less and pretty much unnoticeable. Thanks a whole bunch!

Of course, you can ask if you have any questions.
In short:

  • Don’t use renderstepped because it runs prior to anything else in-game rendering. TLDR: BAD!!!
  • you can use task.wait to represent frames, but this isn’t the best solution for your case. just putting it out there.
    • Frame values are really just 1 / fps, so 240 fps is just 1 / 240 and 60 fps is 1 / 60
  • :Lerp(), or in mathematics, Linear Interpolation, is the process to find a point between two points at a certain value.
    • I.e. lerping with a 3rd value of .4 will give a result that is 40% of the path from A->B
    • You were calculating the 3rd input with exponents, meaning sometimes it would be .01 or other times .8, which is a huge difference

My solution was very simply:

  • Instead of lerping with a value that is very volatile (.01 at 240 fps, and .8 at 60 fps), i simply changed the calculation to be less harsh.
    • The code i provided not only clamps the value, so that it cannot go outside given min and max, but also doesn’t use exponents so the value is more controlled.

Hope this helps!

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