Smoothen out lerp

Hello; I am having issues with adjusting lerps to the players fps. It is for a volleyball system I am creating, and when I try lerping using an estimated fps, it can either be too fast or too slow, and a lag spike would cause issues, but when I use the constantly changing deltatime, the ball jitters. Here is a video using the actual deltatime:
robloxapp-20241228-2159061.wmv (817.5 KB)
as you can see, it is extremely jittery and unplayable, but I would like to be able to slow down and speed up the lerp to adjust to the fps, if possible.
Here is my code:

local module = {}
local run = game:GetService("RunService")




local function lerp(a,b,c)
	return a + (b-a) *c
end

local function quadbezier(t, a, b ,c)
	local l1 = lerp(a,b,t)
	local l2 = lerp(b,c,t)
	local quad = lerp(l1,l2,t)

	return quad
end

function module.Curve(pos1, pos2,pos3, ball, lv, char)

	if run:IsClient() then
		local frontraycast = char.HumanoidRootPart.CFrame.LookVector * 3
		local i = 0
		local stillupd = false
		local truedt = run.RenderStepped:Wait()
		print(truedt)
		local con
		local netcon
	local lerpid = math.random(0,100000000)
			
	ball:SetAttribute("LerpID", lerpid)
	local multby = 60
	con = run.RenderStepped:Connect(function(dt)
	if truedt < dt  then
		truedt = dt
	elseif truedt > dt then
		truedt = dt
	
	end
			stillupd = true
		i += 0.01

			if ball.Parent:GetAttribute("CanUpdateLerp") == false or ball:GetAttribute("LerpID") ~= lerpid then  con:Disconnect() return end
				if (ball.Position - pos3).Magnitude < 3 then con:Disconnect() return end
				
				local rayparams = RaycastParams.new()
				rayparams.CollisionGroup = "ServerBall"
				rayparams.IgnoreWater = true
			local rayres = workspace:Raycast(ball.Position, ball.CFrame.LookVector * 3,rayparams ) or workspace:Raycast(char.HumanoidRootPart.Position, frontraycast, rayparams)
		
				if rayres and rayres.Instance then
					if rayres.Instance.Name == "Net" then
						if not netcon then
						
						netcon = run.RenderStepped:Connect(function()
							
							if ball.Parent:GetAttribute("CanUpdateLerp")== false then netcon:Disconnect(); con:Disconnect() return end
							if ball:GetAttribute("LerpID") ~= lerpid then netcon:Disconnect();con:Disconnect() return end
							
								task.spawn(function()
								task.wait(0.4)
								if (Vector3.new(ball.Position.X, ball.Position.Y, 0) - Vector3.new(pos3.X, pos3.Y, 0) ).Magnitude < 3 then netcon:Disconnect(); con:Disconnect() return end
								end)

							print("clientnetcon")
							
							
							
							if lv then
							
								ball.CFrame =CFrame.lookAlong(Vector3.new(CFrame.new(quadbezier(i * truedt * multby, pos1, pos2, pos3)).p.X, CFrame.new(quadbezier(i * truedt * 60, pos1, pos2, pos3)).Y , rayres.Instance.Position.Z - lv.Z * 2), lv)
								
							else
								ball.CFrame = CFrame.new(quadbezier(i * truedt * multby, pos1, pos2, pos3).X, quadbezier(i * truedt * 60, pos1, pos2, pos3).Y, rayres.Instance.Position.Z)
							end
						end)
						end
					

					end
					return
				end
				if not netcon then
				if lv then

					ball.CFrame =CFrame.lookAlong(CFrame.new(quadbezier(i * truedt * multby   , pos1, pos2, pos3)).p, lv)

				else
					ball.CFrame = CFrame.new(quadbezier(i * truedt * multby, pos1, pos2, pos3))
				end
				
				end
		

	 end)
end

thank you for your help in advance

1 Like

bump bump bump bump bump bump bump bump bump

1 Like

oh well i guess ill just deal with inconsistency

1 Like

I can’t see the video, but a couple of things. First is note that bezier curves themselves aren’t consistent. The parts near the center are different speeds than the edges. You’ll need some method to approximate a distance along the curve if you want to keep it consistent.

As for getting things frame rate dependant. You are on the right track. It generally is direction * speed * dt. There is just too much code for me to really narrow down any issues specifically rn.

1 Like

Yeah that’s what Ive been doing but the problem is that if I use the actual dt every renderstepped loop, the ball will jitter back and forth, but if I use one fixed dt, if the users fps changes while the ball is moving the ball won’t adjust to the fps change

1 Like

Ohh. It’s because frame rates aren’t consistent. Dt is the time between frames. You are using a bezier though which expects a total time. You are getting the total time by multiplying i * dt. Instead of using dt, when the curve starts set a variable called startTick = tick(). Then have curTime be tick() - startTick. That way you get the total time since the loop started which is what you want for the bezier.

1 Like

But how would that help the curve stay consistent to fps?

1 Like

If you’re using RunService, it’s always going to slow down for the framerate. Instead try just simply using task.wait(), and see what changes.

1 Like

Putting task.wait() in a renderstepped loop doesnt really do anything

1 Like

Keeping something consistent with fps is about making sure it’s in the right spot every frame. When it comes to moving something, we multiply velocity * dt and add it to the current position because that will get us the new position every frame. This is applicable for when we know the velocity.

You however are using a bexier and therefore know the position needed at every timestamp. Which means we just need to know how long since it started to get the right position. It’s like doing the velocity thing if we always know it would be the same speed every frame we could do

Position = Position + velocity * timeSinceStart

That is roughly equal to Position = Position + velocity * dt where all of the dt’s add up to timeSinceStart.

The bezier is like the first example since it directly calculated the position based on the time elapsed instead of having to do it every frame. That might not make sense but the only other way I can think of to describe it is that we are approximating the integral when doing vel*dt, but we don’t need to approximate the integral if we have an anti derivative we know will remain valid the whole time.

how would i go about getting the velocity?

Maybe I will try again.

To get a position on the curve you are putting in a value T. As T gets bigger, the curve progresses. T in this case is the time elapsed since you started.

There are 2 ways we can get the time elapsed. We can directly use tick() - startTick() like I just suggested.

Or we can have a variable called T where we add dt to it every time. (T = T + dt). Where dt is the time between frames.

Both of these work, but what you are doing right now in the code is trying to get T by adding a constant value to the value “i” every frame.

local T = i * dt

This would work if frames were consistent. Since you are basically counting how many frames have happened with “i” and trying to extrapolate T by multiplying by dt. But dt changes slightly every frame (sometimes it’s early and sometimes it’s late). So you keep multiplying the errors in it by the frame count pretty much.

So you’ll want to before the loop set startTick to tick() so we know what time the thing started. Then every frame we do local T = tick() - startTick so we know how much time since the start has passed every frame. Then where you are doing i * truedt * multby you can replace that with T. Though youay want to do T * 0.1 or something because T directly might be too fast.

Oops. I responded to myself. I don’t know if it will alert you that way so here is this alert.

alright thanks i fixed it by replacing i+= 0.01 with i += dt and its very consistent

1 Like

No, I mean using a task.wait loop. As in:

while task.wait() do

end

thats basically the same thing

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