Set speed for bezier curve?

Hello, I’m trying to make a leap ability using bezier curves and I’m trying to figure out how I can have a set speed since the current result is a bit wonky looking. I’ve found these 3 threads, but haven’t had any luck with the information provided Bezier curves to maintain same speed?, Bezier Curve Projectile Travel Speed, and How would I Lerp through a Bezier line using a set speed?

This is my current code

local function leap(character : Model, distance : number, height : number)
	local root = character:FindFirstChild("HumanoidRootPart")
	local distance = distance
	local height = height
	local pointA = root.CFrame
	local pointC = pointA*CFrame.new(0,0,distance)
	local pointB = pointA:lerp(pointC,0) * CFrame.new(0,height,0)

	for i = 0, 1, .1 do
		root.CFrame = pointA.Rotation + Util:QuadraticBezier(i,pointA.Position,pointB.Position,pointC.Position)
		task.wait(0.01)
	end	
end

And this is the result

It seems that this is what is delaying the loop. Can’t you add a higher value to it? (Such as 0.1 if that speed should be right)

Unfortunately, it seems like having a set speed is a bit more complicated than just changing the wait time. From what I’ve seen in the threads I linked you have to do some kind of calculation for the time or speed.

The end of this briefly talks about making the spacing more even and supplies some approximation code. (though it is based on their implementation of course)

local function leap(character : Model, distance : number, height : number)	
	local root = character:FindFirstChild("HumanoidRootPart")
	local distance = distance
	local height = height
	local pointA = root.CFrame
	local pointC = pointA*CFrame.new(0,0,distance)
	local pointB = pointA:lerp(pointC,0) * CFrame.new(0,height,0)
	
	local seconds = 1 --the amount of seconds you want it to take to complete the jump
	local current = 0
	while current <= seconds do
		local i = current/seconds
		root.CFrame = pointA.Rotation + Util:QuadraticBezier(i,pointA.Position,pointB.Position,pointC.Position)
		current = current + wait()
	end	
end

When you say “speed” I’m assuming you mean a target velocity (studs per second). Calculating the real length of a bezier curve is quite expensive so you can try estimating the length by cutting the bezier into segments and adding the magnitude (distance) between each of the points:

I came up with this for my module:

function module:EstimateQuadraticBezierLength(p0: CFrame, p1: CFrame, p2: CFrame, amountOfSegments: number?)
	local amountOfSegments: number = amountOfSegments or 10
	local segments: {Vector3} = {}
	for i = 1, amountOfSegments do
		table.insert(segments, module:QuadraticBezier(p0.Position, p1.Position, p2.Position, i / amountOfSegments))
	end
	local length = 0
	for i = 1, amountOfSegments do
		local this, next = segments[i], segments[i + 1]
		if not next then
			continue
		end
		length += (this - next).Magnitude
	end
	return length
end

More segments means more accuracy but in turn it’s also more expensive.

Anyway, your duration will now be the estimated bezier length divided by your goal velocity, so

-- assume p0, p1 and p2 are all Vector3's
local duration = util:EstimateQuadraticBezierLength(p0, p1, p2, 10) / goalVelocity

-- then finally for the loop we can just make a variable that continually increases until it's equal or greater than duration, but we also divide this by our duration to get a number between 0 and 1 for the position along the curve:
local timePassed = 0
while timePassed < duration do
    util:QuadraticBezier(p0, p1, p2, timePassed / duration)
    timePassed += task.wait()
end
1 Like

Hello, my bad for the necroposting, but for future users reading this: does this also work on higher degree Bézier functions?
If so, then what do we need to change?

It should, depending on your curve’s length you’d probably need a significantly higher number of segments

1 Like

Didn’t expect such a quick response lol

The current problem that I’m facing is trying to make the moving part rotate along its trajectory.
In the usual Bézier curve approach (where the total time passed between the movement is constant), this is easy to accomplish with a for loop (I made a post on this last weekend: How do I make a rocket point towards its trajectory?).

Basically, I’m making using the CFrame.lookAt() function to make it look at its following position.

Since this is using a while loop instead of a for loop, how would doing this be possible?

If I’m understanding your question correctly, for your next point for the second argument of lookAt, you’d just get a point that’s slightly further along the curve, say 0.01 + t, where t is the current time

Hey, I’m aware this may not be what you are looking for but perhaps a combination of AlignPosition and this module would help? This module is so much help for me and is truly amazing and removes all headache from bezier curve by defining it all into one simple function from loading the module …

Hope this helps :slight_smile:

Code Example:

local Bezier = require(--[[reference bezier module script]])

local function leap(character : Model, distance : number, height : number)
	local root = character:FindFirstChild("HumanoidRootPart")
	local distance = distance
	local height = height
	local pointA = root.CFrame
	local pointC = pointA*CFrame.new(0,0,distance)
	local pointB = pointA:lerp(pointC,0) * CFrame.new(0,height,0)
	
	local tweenTime = 1 --how long the bezier curve should take
	
	Bezier.new(pointA.Position, pointB.Position, pointC.Position)
	local b = Bezier:CreateCFrameTween(root, {"CFrame"}, TweenInfo.new(tweenTime))
	b:Play()
	task.delay(b.TweenInfo.Time, b.Destroy, b)
end