Unity - 關於簡化移動的系統 "Splines"

:::note warn
**注意**  
此處提供的資訊可能會有所變更，請以官方資訊為準。  
本文針對的是 Ver.2.7（功能可能沒有太大的變化）。  
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4042593/eed4760a-c2ae-4d15-b405-f4f39f0a621f.png)[^1]  
***  

### 關於 Splines 的閒聊  

`Splines` 是 Unity 官方套件中的一項功能。它於 2022 年 1 月首次發布，至本文撰寫時（2025 年 3 月），已更新至 Ver.2.8。  
據說，曾經有許多要求希望將其作為標準功能進行搭載。  

> 很多用戶早就希望這項功能能作為標準功能提供，終於現在成為官方套件了！[^2]

### 開始使用 Spline  

首先，安裝 Spline。打開套件管理器（Package Manager），搜尋 `Splines`。  
您應該會看到如下畫面，點擊紅框中的 `Install` 按鈕即可。（由於我已經導入了 Splines，因此顯示的畫面可能不同。）  
![Screenshot 2025-03-23 173833.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4042593/7cbde08a-d1fe-465e-9ec0-4f5ac3a21c24.png)  

導入後，右鍵點擊層級窗口（Hierarchy），或者從 `GameObject` 中選擇 `Spline > New Spline`，在場景中繪製 Spline，按下 `Enter` 鍵即可完成。  
更詳細的方法請參考[這](https://unity.com/ja/blog/engine-platform/building-better-paths-with-splines-in-2022-2 "Unity 2022.2 の Splines を使って、フロー状態でより美しいパスを描き出そう")。  

### 沿著 Spline 移動物件  

若要沿著 Spline 移動物件：  
1. 點擊想移動的物件 `Inspector` 標籤的 `Add Component` 按鈕，搜尋 `Spline Animate`。  
2. 在 Spline 的選項中，附加剛才繪製的 Spline。  

執行後，物件應該就會沿著 Spline 移動了。[^3]  

### 自己編寫腳本  

在上一節中，我們使用的是預先編寫好的腳本來移動物件。但如何自己編寫腳本，讓物件沿著 Spline 移動呢？讓我們開始吧！  

### 使用腳本來移動 Spline[^4]  

以下是一段基本腳本，用於讓物件沿著 Spline 移動：  

```c#:SplineSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;

public class SplineSystem : MonoBehaviour
{
    [SerializeField] private SplineContainer splineContainer;
    [SerializeField] private Transform _followTarget; // 將希望追隨 Spline 的物件（準確來說是位置資訊：transform）添加到這裡。
    [SerializeField] private float SplinesPercentage;
    [SerializeField] private Vector3 AddUpPosition; // 附加功能。

    void Update()
    {
        if (splineContainer == null || _followTarget == null)
            return;

        // 更新位置。
        Vector3 position = splineContainer.EvaluatePosition(SplinesPercentage);

        position += AddUpPosition; // 加上指定的向量（若為負值則減去）。

        _followTarget.position = position;

        // 更新旋轉。
        Vector3 tangent = ((Vector3)splineContainer.EvaluateTangent(SplinesPercentage)).normalized;
        Vector3 up = ((Vector3)splineContainer.EvaluateUpVector(SplinesPercentage));
        _followTarget.rotation = Quaternion.LookRotation(tangent, up);
    }
}
```

:::note warn
**注意**  
當從腳本中操作 Spline 時，請務必添加以下語句：  
```c#
using UnityEngine.Splines;
```
:::

如果對腳本中的 `[SerializeField]` 感到疑惑，可參考[這](https://qiita.com/Cotorain/items/2484b8c9d5e809c938a6)。  

修改 `SplinePosition` 的值應該可以移動物件，並且 Spline 的旋轉效果應該也會被應用。  
至於具體機制，請參考[這](https://nekojara.city/unity-splines-script)。  

### 擴展上一個腳本  

這裡將介紹：  
1️⃣ 輸入從 Spline 起點開始的距離後，將物件移動到該位置的腳本。  
2️⃣ 指定速度（km/h），讓物件以該速度在 Spline 上移動的腳本。  

### 擴展腳本：Part 1️⃣  

上一個腳本是以比例計算位置的。  
這意味著，如果能知道 Spline 的總長度，是否能計算從起點開始的距離呢？  

計算 Spline 長度的函數是：  
```c#
CalculateLength();
```

以下是範例腳本：  

```c#:SplineSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;

public class SplineSystem : MonoBehaviour
{
    [SerializeField] private SplineContainer splineContainer;
    [SerializeField] private Transform _followTarget;
    private float SplinesPercentage; // 更改：完全私有化。
    [SerializeField] private float SplineLength_Position; // 添加：輸入從 Spline 起點開始的距離。
    /*[SerializeField]*/ private float SplineLength; // 添加：獲取指定 Spline 長度的變數。
    [SerializeField] private Vector3 AddUpPosition;
    [SerializeField] private float AddLength; // 附加功能。
    public TrainSystem trainSystem;

    void Start()
    {
        SplineLength = splineContainer.CalculateLength(); // 添加：獲取指定 Spline 的長度。
    }

    void Update()
    {
        if (splineContainer == null || _followTarget == null)
            return;
        
        SplinesPercentage = (SplineLength_Position + AddLength) / SplineLength; // 添加：將距離轉換為比例，並分配給位置變數。

        // 更新位置。
        Vector3 position = splineContainer.EvaluatePosition(SplinesPercentage);

        position += AddUpPosition; // 加上指定的向量（若為負值則減去）。

        _followTarget.position = position;

        // 更新旋轉。
        Vector3 tangent = ((Vector3)splineContainer.EvaluateTangent(SplinesPercentage)).normalized;
        Vector3 up = ((Vector3)splineContainer.EvaluateUpVector(SplinesPercentage));
        _followTarget.rotation = Quaternion.LookRotation(tangent, up);
    }
}
```

以下是 Part 2️⃣ 的續篇翻譯：

---

### 擴展腳本：Part 2️⃣ (續)

```c#:SplineSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;

public class SplineSystem : MonoBehaviour
{
    [SerializeField] private SplineContainer splineContainer;
    [SerializeField] private Transform _followTarget;
    private float SplinesPercentage;
    [SerializeField] private float SplineLength_Position;
    /*[SerializeField]*/ private float SplineLength;
    [SerializeField] private Vector3 AddUpPosition;
    [SerializeField] private float AddLength;
    [SerializeField] private float ObjectSpeed; // 添加：輸入速度（km/h）。

    void Start()
    {
        SplineLength = splineContainer.CalculateLength();
    }

    void Update()
    {
        if (splineContainer == null || _followTarget == null)
            return;

        SplineLength_Position += (ObjectSpeed * (1000f / 3600f)) * Time.deltaTime; // 添加：將 km/h 換算為 m/s 並計算。

        SplinesPercentage = (SplineLength_Position + AddLength) / SplineLength;

        // 更新位置。
        Vector3 position = splineContainer.EvaluatePosition(SplinesPercentage);

        position += AddUpPosition; // 加上指定的向量（若為負值則減去）。

        _followTarget.position = position;

        // 更新旋轉。
        Vector3 tangent = ((Vector3)splineContainer.EvaluateTangent(SplinesPercentage)).normalized;
        Vector3 up = ((Vector3)splineContainer.EvaluateUpVector(SplinesPercentage));
        _followTarget.rotation = Quaternion.LookRotation(tangent, up);
    }
}
```

以上腳本中，`// 添加` 的部分是新增的代碼，用於支持以 km/h 計算移動速度並更新位置。  
這樣，您可以根據自己的需求輕鬆控制物件的移動速度。

---

### 再次閒聊

感謝各位讀到這裡！我個人的近況是剛剛完成學校的畢業典禮，寫這篇文章時正悠閒地放鬆著。  
接下來要準備考試了，感覺有點崩潰，但還是要努力。  
不管是小學生、初中生還是高中生，要考試的朋友們，一起加油吧！

完結！
translated with Bing AI
[^1]: 画像・參考網站：[Unity 2022.2 の Splines を使って、フロー狀態でより美しいパスを描き出そう](https://unity.com/ja/blog/engine-platform/building-better-paths-with-splines-in-2022-2 "Unity 2022.2 の Splines を使って、フロー狀態でより美しいパスを描き出そう")  
[^2]: 引用：[Unity 2022 新機能！スプラインを使ってみよう！ - YouTube](https://www.youtube.com/watch?v=5IrKqVnvP6M)  
[^3]: 詳細方法請參考：[【Unity2022】スプラインツールの導入方法と使い方｜ねこじゃらシティ](https://nekojara.city/unity-splines-intro)  
[^4]: 參考網站：[【Unity2022】スプラインをスクリプトから扱う方法｜ねこじゃらシティ](https://nekojara.city/unity-splines-script "【Unity2022】スプラインをスクリプトから扱う方法｜ねこじゃらシティ")