How can I perfectly time a lerp in a .RenderStepped()?

I’m currently making a first person gun and to achieve an ADS effect I’m using lerp. I can easily do this but the part that I’m having trouble with is perfectly timing the lerp. I have a variable called “aimSpeed” that is how long I want the lerp to take to finish in seconds but I can’t seem to make the lerp sync up with the variable time. Here was my shot at it:

function Aim(self, delta)
	if aiming then
		aimCountDown = 0
		aimCFrame = CFrame.new():Lerp(aimOffset, aimCount/100) -- CFrame.new() so that it will lerp properly 
		aimCount += 1 + (delta * 100) -- multiply delta by 100 because we divided by 100 in RenderStepped() function because lerp is from 0 - 1
	else
		if aimCountDown == 0 then
			aimCountDown = 100 - aimCount
		end
		aimCount = 0
		aimCFrame = aimOffset:Lerp(CFrame.new() , aimCountDown/100)
		aimCountDown += 1 + (delta * 100) -- multiply delta by 100 because we divided by 100 in Aim() function because lerp is from 0 - 1
	end
	aimCount = math.clamp(aimCount, 0, 100)
	aimCountDown = math.clamp(aimCountDown, 0, 100)
end

function ViewModel:RenderSteppedUpdate()
	self.Model.PrimaryPart.CFrame = camera.CFrame * aimCFrame
	if tick() - deltaTime >= aimSpeed / 100 then
		Aim(self, (tick() - deltaTime) - (aimSpeed / 100))
		deltaTime = tick()
	end
end

This code works fine when the variable is 1, finishing the lerp at approximately 1.01 seconds. But if I were to increase the variable to 2 the lerp finishes at approximately 1.433 seconds and that number gets more inaccurate the more I increase “aimSpeed”.

Part of the problem is that you’re always adding 1 every frame which is not scaled by the delta.

Also you’re using aimSpeed to check how long the aim has been going for, but also you’re subtracting it from the frame time or something?

Take a step back and simplify things for yourself—first, RunService | Roblox Creator Documentation (which I’m assuming you bind this function to) gives you the time since the last frame, so we can use that parameter instead of calculating it ourselves.

function ViewModel:RenderSteppedUpdate(dt)

Instead of keeping track of two variables for up/down like this, it will be cleaner to just keep track of one and add/subtract to it.

Also, we can just make that one variable always be in the range 0 and 1 so we can use it directly in the Lerp. All we have to add to (or subtract from!) it is aimSpeed * delta.

There’s some other issues like Aim should probably just be a method instead of explicitly taking in a self parameter, or the fact that you could do this with TweenService and save yourself some trouble, and that aimCount should really be a class property rather than a global variable, but we can save that for later :slight_smile:

Putting it together:

function Aim(self, delta)
    if aiming then
        aimCount += delta * aimSpeed
        if aimCount > 1 then aimCount = 1 end
    else
        -- reusing same aimCount variable for down, just subtracting
        aimCount -= delta * aimSpeed
        if aimCount < 0 then aimCount = 0 end
    end
    
    aimCFrame = CFrame.new():Lerp(aimOffset, aimCount) -- using aimCount directly
end

function ViewModel:RenderSteppedUpdate(delta) -- new parameter we will get from BindToRenderStep
    Aim(self, delta)
    self.Model.PrimaryPart.CFrame = camera.CFrame * aimCFrame
end
3 Likes

While the new code did clean up my code a little bit, it isn’t exactly what I want. I want the aimSpeed to be how long the lerp takes to complete in seconds. So for example if aimSpeed was 5 I want the alpha of the lerp to reach 0 to 1 in 5 seconds. That’s the part that I’m having trouble on.

Then instead of delta * aimSpeed do delta / aimSpeed :slight_smile:

1 Like

Kind of off topic but you mentioned

“self” is a parameter of Aim() because I want to be able to use variables inside of the constructor function without Aim() appearing as a method when the module is required from a LocalScript. Is this bad practice? If so how can I use properties from the constructer function without it appearing as a method? Also, you mentioned

This module should be required from a LocalScript and not a ServerScript so if the variable is changed, it won’t be affected for every other script that requires the module. Is this ok? Or should I change aimCount to a class property like you mentioned?

Nah, it’s not bad practice. That reasoning makes sense to me!

Your reasoning makes sense for now—there will only ever be a single ViewModel per player—but it’s still a good idea to move it into the ViewModel instance (the self object).

Maybe later, you decide that it actually does make sense for there to be more than one ViewModel per client. Maybe you’re making a killcam or spectator cam or something. Now all of a sudden, all the ViewModels are going to fight over the same aimCount, and you’re going to get all kinds of unexpected bugs.

If you’re going with OOP, individual instances should not affect each other in unexpected ways. It helps you prevent bugs.