想要在 Unity 中讓物體進行拋物線運動,平常的我可能會直接用 Rigidbody 來施加力,讓系統自己去運算物理的大小事。但有時候我只是要做個「看起來」是拋物線運動的效果,沒有必要弄到非常真實,就可以套個曲線公式來施作。
原理
我們最常看到的貝茲曲線是只有一個原點、一個終點與一個控制點的「二次方貝茲曲線」,你可以透過移動控制點來修改曲線。在推導「二次方貝茲曲線」前,必須先了解「線性貝茲曲線」,它等同於我們常用的線性插值。
線性貝茲曲線
線性貝茲曲線等同線性差值,我們現在已知 A 與 B 的座標位置,也假定總共移動所需的時間 t 為 1,t 介於 0~1,可以直接想像成此次移動的完成百分比 (0~100%)。
t 代表時間,A 和 B 分別代表原點與終點,C 就是結果。

先以簡單的一維空間來解釋…
假設 A = 0、B = 10,求 t = 0.2 時 C 點的位置。這時就可以用 A~B 的距離乘 t 來表示 C 的位置:
$latex C=(B-A)t&s=2$
但是這個假設是建立在原點 = 0 的情況,所以正確的式子是:
$latex C=A+(B-A)t&s=2$
用分配律整理一下,這就是「線性貝茲曲線」的公式:
$latex C=A+(B-A)t=AB-A^2t=(1-t)A+Bt&s=2$
接下來帶入剛才假設的數值,得 C = 2:
$latex C=(1-0.2)0+10\times{0.2}=2&s=2$
二次方貝茲曲線
進入主題,二次方貝茲曲線是由三條線性貝茲曲線組合出來的產物,以下方影片為例,我們將使用到 $latex \overline{AB}$、$latex \overline{BC}$ 與 $latex \overline{DE}$:
- 先定義由左到右的座標分別為 A、B、C 點。
- 分別計算 A~B 與 B~C 上的點於指定 t 的時候所在的座標,可得出的 D、E 兩個座標點。
- 將 D 與 E 帶入公式最後得出我們要的 F 座標:
$latex F=(1-t)D+Et&s=2$
能用三個線性貝茲曲線算出座標後,是時候將他整理成一個公式…
首先是剛才計算 AB、 BC 線段上的 D、E 座標式子:
$latex D=(1-t)A+Bt&s=2$
$latex E=(1-t)B+Ct&s=2$
還有最終的 F 座標式子:
$latex F=(1-t)D+Et&s=2$
將 D 和 E 帶入 F 座標的式子:
$latex F=(1-t)[(1-t)A+Bt]+E[(1-t)B+Ct]&s=2$
整理後可得以下公式,t 介於 0~1 之間:
$latex F=(1-t)^2A+2t(1-t)B+t^2C , t \in[0 ,1]&s=2$
二次方貝茲曲線公式
$latex B(t)=(1-t)^2P_0+2t(1-t)P_1+t^2P_2 , t \in[0 ,1]&s=2$
語法
Unity 中的次方可使用 Mathf.Pow 來實現,最後的 Bt 座標就是指定時間點 t 的貝茲曲線座標。
public Transform P0, P1, P2, Bt;
private void Start()
{
var t = 0.5f;
var p0 = P0.position;
var p1 = P1.position;
var p2 = P2.position;
Bt.position = Mathf.Pow(1 - t, 2) * p0 + 2 * t * (1 - t) * p1 + Mathf.Pow(t, 2) * p2;
}
也可以放到 Update 中並給予時間與線段特效,以下影片為例:
完整腳本:
using UnityEngine;
public class BezierCalculator : MonoBehaviour
{
public int Duration = 5;
public Transform P0, P1, P2, Bt;
private float _time;
private float _duration;
private void Update()
{
if (_duration > Duration)
{
_duration = 0;
}
var t = _duration / Duration;
Bt.position =
Mathf.Pow(1 - t, 2) * P0.position +
2 * t * (1 - t) * P1.position +
Mathf.Pow(t, 2) * P2.position;
_duration += Time.deltaTime;
}
}