Dreamteck Splines實現沿路徑移動功能
最近有一個“讓物體沿固定路徑移動”的需求,因此接觸到了Dreamteck Splines外掛。
Dreamteck Splines可以很方便地繪製各種插值曲線,但在實現物體移動的時候卻遇到了很多坑,因此在這裡記錄一下。
1. 繪製路徑線
首先,讓我們在場景上建立一個空物體,並新增SplineComputer
元件。
由於我這是個2D專案,所以選擇在Z平面上繪製。
之後編輯器中就會顯示出跟隨滑鼠的網格線,點選左鍵就可以逐點繪製Spline了。
在右側選項中可以修改Spline的型別。預設型別是Catmull Rom
,我們可以把它改成直線Linear
。
2. 獲取座標
SplineComputer
類有兩類獲取座標點的方法:
GetPoint(int)
GetPoints()
這兩個方法用於獲取我們手動新增的座標點,也就是我們上圖中的那三個點。這明顯不符合我們“沿路徑移動”的需求。
而下面這三個方法才是返回Spline上的所有座標點
Evaluate(double)
EvaluatePosition(double)
EvaluatePositions()
其中,Evaluate
和EvaluatePosition
的區別在於,Evaluate
返回的是SplineSample
物件,包括了座標、朝向、顏色、百分比等資訊,而EvaluatePosition
則是簡單地返回一個Vector3
的座標。在只需要座標的情況下,推薦使用更加輕量化的EvaluatePosition
。
3. 移動示例(踩坑)
EvaluatePosition(double)
方法傳入一個0~1
的值,就會返回Spline上對應的座標,因此我們可以用這個指令碼手動模擬物體的移動過程。
using Dreamteck.Splines;
using UnityEngine;
public class Move : MonoBehaviour
{
[Range(0, 1)]
public float Percent;
public Transform Target;
private SplineComputer spline;
void Start()
{
spline = GetComponent<SplineComputer>();
}
void Update()
{
Target.position = spline.EvaluatePosition(Percent);
}
}
將指令碼掛載到SplineComputer
所在的物體上,拖動右側的滑動條即可移動目標物體。
一切看似十分正常,直到我們又新增了一條長度不同的線段。
可以明顯地看出,在後面這個較短的路徑中,物體的移動速度明顯變慢了。
當我們直接將進度設為0.5
後,便能發現問題所在。
目標物體移動並沒有移動到Spline的終點,而是移動到了我們設定的第二個控制點上。
這個問題在官方文件的3.3. Sample Mode
中有對應的解答:
預設情況下,樣條曲線(Spline)在 [0-1] 的百分比範圍內進行計算(evaluated),涵蓋了所有座標點。
例如,一條由 3 個點組成的樣條曲線,計算百分比為 0.5 的座標點,將始終返回第二個點的位置,因為它位於中間。
然而,如果第一個點和第二個點非常接近,而第三個點距離它們很遠,計算百分比為 0.5 的座標點不會返回樣條曲線的中間位置,它仍將返回第二個點。因為某些區域的取樣點比其他區域更密集。
為了說明這一點,以下是顯示了取樣點密度的樣條曲線:每條垂直線表示一個取樣點(spline sample)。在這種情況下,點 1 和點 2 之間有 10 個取樣點,但點 2 和點 3 之間也只有 10 個取樣點。
從中我們可以看出,問題的根源在於,Evaluate
引數中的percent
並不是指Spline長度的百分比,而是表示Spline取樣點的百分比。而取樣點的不均勻分佈,導致了取樣點百分比和長度百分比不一致的情況。
5 方法一:修改取樣模式(Sample Mode)
前面我們提到"取樣點的不均勻分佈,導致了取樣點百分比和長度百分比不一致的情況"。
反過來說,我們只需要讓取樣點能夠均勻分佈,就可以解決這一問題。
SplineComputer
提供了三種取樣模式(Sample Mode):
- Default(預設):兩點間的取樣點數量固定
- Uniform(均勻):根據Spline長度,均勻分佈取樣點。但在Spline較長時會有更大的效能開銷。
- Optimized(最佳化):與預設模式相同,但會執行最佳化操作刪除不必要的取樣點
所以我們需要選擇Uniform
模式,以實現均勻分佈取樣點的需求。
請注意,在Default和Optimized模式下,當移動控制點時,Spline僅更新受該點影響的區域中的取樣點。而在Uniform模式下,將重新計算整個Spline。在Optimized模式下,還提供了一個額外的滑塊來控制最佳化的角度閾值。
這樣一來就能正確地勻速移動目標了
但是!
除了效能開銷外,這個方法還會帶來一系列問題。
首先,它會導致線段脫離控制點:
其次,它還有個很致命的BUG
Uniform
模式下,如果你用CalculateLength
方法獲取Spline的長度,那麼初始狀態下將會始終返回0
。此時必須對他"進行一些操作",比如移動控制點,修改其他引數等,讓他響應一次變化。之後CalculateLength
才能正確返回數值。
6 方法二:使用Travel函式(推薦)
為了避免上述問題,我們可以使用Travel
函式計算某個長度在Spline上對應的取樣點百分比。
它的使用方法在官方文件的20.4. Converting World Units to Spline Percentages
中有所提及。
假如我們要獲取Spline中心點的座標,只需要傳入Spline長度的一半,也就是spline.CalculateLength() / 2
,然後Travel
函式就會返回對應的percent
。這時再呼叫EvaluatePosition(percent)
即可得到中心點的座標。其他位置的座標也是同理,我們只需要給出對應的長度即可獲取座標。
這樣一來,我們就可以方便地實現沿路徑勻速移動的功能了。
using Dreamteck.Splines;
using UnityEngine;
public class Move : MonoBehaviour
{
[Range(0, 1)]
public double Percent;
public float Speed;
public Transform Target;
private float distance;
private SplineComputer spline;
void Start()
{
spline = GetComponent<SplineComputer>();
distance = 0;
}
void Update()
{
distance += Speed * Time.deltaTime;
// 有需要的話可以用這個限制上限
// distance = Math.Min(distance, spline.CalculateLength());
Percent = spline.Travel(0, distance);
Target.position = spline.EvaluatePosition(Percent);
if (Percent == 1)
{
// do something
Debug.Log("Done");
}
}
}
效果如下
參考資料
Dreamteck Splines – User Manual
本文釋出於2024年7月14日
最後編輯於2024年7月14日