How to make RenderStepped Independent of Frame Rate?

Hello, I’m using a gun engine to make guns, but a big downside of it is that its RenderStepped/Animation functions aren’t independent of framerate. The script is meant to run at 60 FPS, and I found this out because that was the only framerate range where the animations were smooth. When I fpsunlocked and was getting 150+, it would be 2 times or more the original speed. Same thing with 30, except that it would be around half-speed. I’ve already identified the part of the code which controls the speed, and therefore is the problem (it’s the while loop). Here’s the code (it controls most of the animations)

Utilities.TweenJoint = function(Joint, newC0, newC1, Alpha, Duration)
	spawn(function()
		local newCode = math.random(-1e9, 1e9) --This creates a random code between -1000000000 and 1000000000
		local tweenIndicator = nil
		if (not Joint:findFirstChild("tweenCode")) then --If the joint isn't being tweened, then
			tweenIndicator = Instance.new("IntValue")
			tweenIndicator.Name = "tweenCode"
			tweenIndicator.Value = newCode
			tweenIndicator.Parent = Joint
		else
			tweenIndicator = Joint.tweenCode
			tweenIndicator.Value = newCode --If the joint is already being tweened, this will change the code, and the tween loop will stop
		end
		if Duration <= 0 then --If the duration is less than or equal to 0 then there's no need for a tweening loop
			if newC0 then Joint.C0 = newC0 end
			if newC1 then Joint.C1 = newC1 end
		else
			local Increment = 1.5 / Duration --Calculate the increment here so it doesn't need to be calculated in the loop
			local startC0 = Joint.C0
			local startC1 = Joint.C1
			local X = 0

			while true do -- THIS WHILE LOOP IS THE PROBLEM, BECAUSE IT'S ONLY RUNNING AT 1/60 OF A SECOND.
				rs:wait() --This makes the for loop step every 1/60th of a second
				local newX = X + Increment
				X = (newX > 90 and 90 or newX) --Makes sure the X never goes above 90
				if tweenIndicator.Value ~= newCode then break end --This makes sure that another tween wasn't called on the same joint
				if newC0 then Joint.C0 = startC0:lerp(newC0, Alpha(X)) end
				if newC1 then Joint.C1 = startC1:lerp(newC1, Alpha(X)) end
				if X == 90 then break end --If the tweening is done...
			end
		end
		if tweenIndicator.Value == newCode then --If this tween functions was the last one called on a joint then it will remove the code
			tweenIndicator:Destroy()
		end
	end)	
end

I tried using ratios and didn’t get good results, as well.

EDIT:
I fixed it, and my ratio idea was 100% accurate, LETS GOOOO!! Turns out I was just doing my debounces and code approach wrong.

3 Likes

What I mean by ratios:

trying to replace the newX with this -

local currentfps = 1 / game:GetService("RenderStepped"):Wait()
local newX = (X + Increment) * 60/currentfps
2 Likes

Then why don’t you change the rs:wait()?
To something wait, more than 1/30 which is minimium wait time

1 Like

Yeah I thought about that too, but I don’t know how to do it, because minimum wait time is 1/30.

How would I be able to change the rs:wait() to something like wait(1/fps)?

1 Like

Well, you can use math.clamp() then, like for example
math.clamp(NUMBER, MIN,MAX)

Basic example:

math.clamp(10,1,20) -- returns 10, because is less than MAX (20) and more than min (1)

Math.clamp() will return X number is it’s equal or more than 1, and if it’s equal or less than 20, else if it’s 0.1 then

--0.1 = 1 as 1 was MIN number

--or if it's 50 then
--50 = 20 as 20 was MAX number

for it doesn’t pass certain speed! Hope It helped :slight_smile:

You can’t actually change the speed at which RenderStepped fires, but you can do something better.

Get the delta time between each frame, and then move each animation in that frame based on that delta time.

local last = tick()

rs.RenderStepped:Connect(function()
    local delta = tick() - last
    last = tick()

    -- do animation stuff based on delta time
end)

Heartbeat actually gives the delta time as an argument. RenderStepped does not.

3 Likes

So where exactly would I put this?

I’ll try this out right now, thanks. I’ll get back to you very soon

By the way, do yourself a favour and never ever ever ever use spawn. Use coroutine.wrap().

Give the first 10 replies of this thread a read if you care.

1 Like

For sure. I’ll read on this after I implement your code

By the way, I could do 1/delta to get the FPS, right?

Why not use heartbeat? I runs 60 times per second no matter the frame-rate.

RS.Heartbeat:Connect(function()

end)

or

RS.Heartbeat:Wait()

It didn’t work, it makes the gun animations all jittery. Unless I used your code wrong.

Here’s what I did:

local Increment = 90 / Duration -- changed the increment from 1.5 to 90 to match the previous ratio after being multiplied by delta (previous ratio was 1.5/ Duration for 60FPS)

local last = tick()
			local renderstep = true
			
			if renderstep == true then
				rs:Connect(function()
				    local delta = tick() - last
				    last = tick()
					local newX = (X + Increment) * delta
					X = (newX > 90 and 90 or newX) --Makes sure the X never goes above 90
					if tweenIndicator.Value ~= newCode then renderstep = false end --This makes sure that another tween wasn't called on the same joint
					if newC0 then Joint.C0 = startC0:lerp(newC0, Alpha(X)) end
					if newC1 then Joint.C1 = startC1:lerp(newC1, Alpha(X)) end
					if X == 90 then renderstep = false end --If the tweening is done...
				
				end)
			end

Uh, no it doesn’t according to this:

They made Heartbeat to change on frame rate too.

And, also my goal was to get the wait above 1/60, which is what I have already (and is the problem)

This could work but it would just make the animation jittery.

If I was playing at 150 fps, the loop would still be running at 1/60 of a second, so it would look like its skipping frames

Oh, I didn’t know that. Have you tried using tick()? You can offset the time depending on how much time is lost:

local duration = 5
local t1 = tick()
local stop = start + duration
local t2 = t1
while t2 < stop do
	wait()
    bullet.Position = disposition:Lerp(t2 - t1)
	t2 = tick()
end

Something along those lines.

Time is lost where? In between frames? So this is getting a delta time?

Plus, your code still wouldn’t run more than 1/30 a second, and I’m trying to get 1/CurrentFps

DeltaTime is actually the first argument passed to your RenderStepped connection’s function! No need to calculate it yourself.

2 Likes

Exactly, that’s what I was thinking lol
Used the deltatime argument in renderstepped anyways.

Anyways, since all of the settings were made for 60 FPS, I just used deltatime to get a ratio:

local ratio = 60 / (1/deltaTime)

And then multiplied by ratio where it was needed. It was fixed with 100% accuracy! This was my idea from the very beginning, turns out I just did my debounces and code wrong the first couple of times :joy:

6 Likes

Hey, bumping this topic again to say this isn’t actually the correct way of lerping independent of framerate.
Please check this post
th