Bezier Projectiles With De Casteljau’s Algorithm
A generic Luau BezierCurve function that works on any type you can lerp, and how Bard’s Fighting Arena uses it for projectile arcs.
Grenades, leaps, and other arcs in Bard’s Fighting Arena follow Bezier curves. The function behind them does not care whether it is moving a CFrame, a Vector3, or a number. You pass in how to lerp, and it handles the curve.
De Casteljau in one sentence
To find a point on a Bezier curve at t, lerp between each pair of neighboring control points by t, then repeat on the results until one point remains.
The code
function BezierCurve<T>(Lerp : (T, T, number) -> T, TInfo : TweenInfo, Callback : (T, number) -> any, ... : T)
local Points = {...}
local Count = #Points
local Buffer = table.create(Count)
local NumberValue = Instance.new("NumberValue") do
local Tween = game.TweenService:Create(NumberValue, TInfo, Globals.Value1)
local Thread = task.spawn(function()
local Changed = NumberValue.Changed
while true do
local Alpha = Changed:Wait() do
for Index = 1, Count do
Buffer[Index] = Points[Index]
end
local WorkingCount = Count
while WorkingCount > 1 do
for Index = 1, WorkingCount-1 do
Buffer[Index] = Lerp(Buffer[Index], Buffer[Index+1], Alpha)
end
WorkingCount -= 1
end
end
Callback(Buffer[1], Alpha)
end
end)
Tween:Play()
Tween.Completed:Once(function()
pcall(task.cancel, Thread)
NumberValue:Destroy()
end)
return Tween, NumberValue
end
end
--// Example code (Shown on the DEMO)
local Angle = CFrame.Angles(math.rad(math.random(-360, 360)), math.rad(math.random(-360, 360)), math.rad(math.random(-360, 360)))
BezierCurve(CFrameLerp, TweenInfo.new(Time, Enum.EasingStyle.Linear, Enum.EasingDirection.Out), function(CF, Alpha)
Grenade.CFrame = CF * CFrame.identity:Lerp(Angle, Alpha)
end, ...)
Why pass the lerp in?
Taking Lerp : (T, T, number) -> T as a parameter makes the function generic. The same curve code moves a CFrame (with a CFrame lerp function) or a position (Vector3.zero.Lerp) without separate versions for each type.
Driving it with TweenService
The curve is sampled from a NumberValue that a tween animates from 0 to 1. That means every easing style TweenService supports works on the curve for free.
How the grenade uses it
The grenade splits the work between the server and the client. The server initializes the projectile: it creates it and sets the curve’s control points toward the direction it was thrown. Each client then animates the projectile along that curve with BezierCurve, which simulates the physics of the throw without sending the projectile’s position over the network every frame.