【Unity2022】スプラインとタイムラインを同期させる

こじゃらこじゃら

タイムラインからスプラインに沿ってキャラを動かすようにする方法はないの?

このはこのは

連携用のスクリプトを実装すれば実現できるわ。

Unity公式スプラインとタイムラインを連携させる方法の解説記事です。

Unity2022.1より使えるビルトインスプライン(Splinesパッケージ)は、スプラインに沿ってオブジェクトを移動させる機能を提供しますが、タイムラインに同期して移動させる機能は提供されていません。 [1]

そのため、スプラインとタイムラインを連携させるには、両者を連携させるスクリプトを実装する必要があります。

実装方法はいくつか存在しますが、本記事ではコントロールトラックを用い、ITimeControlインタフェースを実装したスクリプトで連携させる方法を紹介します。

最終的に、次のようにタイムラインに従い、オブジェクトをスプラインに沿って動かすところを目指します。

なお、記事を読み進めるにあたっては、スプラインやタイムライン、C#スクリプトの基本を理解している必要がありますので、予めご了承ください。

動作環境
  • Unity 2022.1.10f1
  • Splines 1.0.1
  • Timeline 1.7.1

スポンサーリンク

前提条件

Unity2021.1以降Unityプロジェクトが作成され、SplinesとTimelineパッケージがインストールされているものとします。

パッケージのインストールは、以下手順で行います。

パッケージのインストール手順
  • UnityエディタトップメニューのWindow > Package Managerを選択してPackage Mangerウィンドウを開く
  • 左上のドロップダウンリストからPackages: Unity Registryを選択
  • 左のリストからSplinesを選択し、Installボタンをクリック
  • Timelineパッケージがインストールされていなければ、左のリストからTimelineを選択し、Installボタンをクリック

最終的に、該当するパッケージにチェックマークが付いていればOKです。

スプラインの基本的な使い方については、以下記事で解説しておりますので、必要な方はご覧ください。

事前準備(必要ならば)

本記事では、スプラインに沿ってキューブを動かすことを例にとって解説していきます。

本手順はデモシーンのセットアップのためのものなので、不要ならスキップして問題ありません。

スプラインのセットアップ

まず、適当なスプラインを配置します。

次に、スプラインに沿って移動させるオブジェクトを配置します。

例では、デフォルトキューブを移動させるものとします。

スプラインの配置方法について分からない方は、以下記事をご覧ください。

また、床やキューブのマテリアルには、Standard Assetsのものを使用しています。Standard Assetsの使い方は以下記事で解説しております。 [2]

タイムラインの準備

オブジェクトをスプラインに沿って動かすためのタイムラインを準備していきます。

プロジェクトウィンドウの右クリックからCreate > Timelineの順に選択し、タイムラインアセットを作成します。

次に、作成したタイムラインアセットをヒエラルキーウィンドウにドラッグ&ドロップします。

すると、次のようにPlayable Directorコンポーネントがアタッチされたオブジェクトが追加されます。

オブジェクトを移動させるスクリプトの実装

タイムラインの時刻に同期してオブジェクトをスプラインに沿って移動させるスクリプトを実装していきます。

実装の流れは以下の通りです。

実装の流れ
  • ITimeControl継承クラスの定義
  • 時刻からスプライン上の位置を計算する処理の実装

サンプルスクリプト

以下、実装例です。

SplineTimelineMover.cs
using UnityEngine;
using UnityEngine.Splines;
using UnityEngine.Timeline;

// エディットモードのプレビューでも動かすために[ExecuteInEditMode]属性付加
[ExecuteInEditMode]
public class SplineTimelineMover : MonoBehaviour, ITimeControl
{
    // スプライン
    [SerializeField] private SplineContainer _splineContainer;

    // スプラインを一周する時間[s]
    [SerializeField] private double _duration = 1;

    public void SetTime(double time)
    {
        // エラーチェック
        if (_splineContainer == null || _duration <= 0) return;

        // 正規化された割合計算
        var percentage = (float) (time / _duration);

        // 得られたスプライン位置を反映
        transform.position = _splineContainer.EvaluatePosition(percentage);
    }

    public void OnControlTimeStart()
    {
    }

    public void OnControlTimeStop()
    {
    }
}

上記をSplineTimelineMover.csという名前でUnityプロジェクトに保存しておきます。

スクリプトの適用

スプラインに沿って移動させたいオブジェクトに、前述のスクリプトSplineTimelineMoverをアタッチします。

追加したスクリプトのSpline Container項目にスプラインオブジェクトをアタッチします。

必要に応じてDuration(一周にかかる時間)をお好みで設定しておきます。

最終的に以下のように設定されていればOKです。

このはこのは

これで下準備は一通り終わりだわ。スプラインとタイムラインがやっと連携出来るようになったわ。

タイムラインにトラックを配置する

ここからは、タイムラインにトラックを配置してオブジェクトを動かすようにしていきます。

対象となるタイムラインアセットをダブルクリックしてTimelineウィンドウを開き、ヒエラルキーよりタイムラインオブジェクト(Playable Directorがアタッチされているオブジェクト)を選択します。

タイムラインウィンドウの左上の+アイコンよりControl Trackを選択し、トラックを追加します。

前述のサンプルスクリプト(SplineTimelineMover)をアタッチしたオブジェクトを、追加したトラックへドラッグ&ドロップし、クリップを追加します。

実行結果

タイムラインの時刻に同期してオブジェクトが移動するようになりました。

サンプルスクリプトでは、実行していない状態でもプレビューで移動できるようになっています。

スクリプトの説明

先述のサンプルスクリプトの処理についても触れておきます。

タイムラインに同期させるために、ITimeControlインタフェースを継承したクラスを実装します。

// エディットモードのプレビューでも動かすために[ExecuteInEditMode]属性付加
[ExecuteInEditMode]
public class SplineTimelineMover : MonoBehaviour, ITimeControl

ゲームを再生していない状態でもプレビューできるように、[ExecuteInEditMode]属性をクラスに指定しています。

参考:Interface ITimeControl | Timeline | 1.7.1

スプラインに沿って移動させる処理は、以下SetTimeメソッド内で行っています。

public void SetTime(double time)
{
    // エラーチェック
    if (_splineContainer == null || _duration <= 0) return;

    // 正規化された割合計算
    var percentage = (float) (time / _duration);

    // 得られたスプライン位置を反映
    transform.position = _splineContainer.EvaluatePosition(percentage);
}

SetTimeメソッドは、タイムライン側から呼ばれるメソッドで、引数timeには再生中クリップの現在時刻が渡されます。

スプライン上の位置は、0~1の範囲で割合として指定するようになっています。ある時間Duration秒かけて開始から終了まで移動させる場合は、時間をDurationで割れば良いです。

指定された割合における位置は、SplineContainer.EvaluatePositionメソッドから取得しています。得られる位置はワールド座標のため、transform.positionプロパティにそのまま結果を代入しています。

速さに基づいた移動をさせる

ここまで解説した方法は、スプライン全体の移動にかかる時間に基づいていました。

速さに基づいて移動させたい場合、次のように全体の移動にかかる時間を導き出せます。

移動時間を求める計算式

全体の移動時間 = スプライン全体の道のり / 速さ

また、スプラインの移動割合は「時刻 / 全体の移動時間」として計算できるため、最終的に次の計算式となります。

割合を求める計算式

割合 = 時刻 × 速さ / スプライン全体の道のり

サンプルスクリプト

以下、速さ基準で移動させるように書き直した例です。

SpeedBasedMover.cs
using UnityEngine;
using UnityEngine.Splines;
using UnityEngine.Timeline;

// エディットモードのプレビューでも動かすために[ExecuteInEditMode]属性付加
[ExecuteInEditMode]
public class SpeedBasedMover : MonoBehaviour, ITimeControl
{
    // スプライン
    [SerializeField] private SplineContainer _splineContainer;

    // 移動する速さ[m/s]
    [SerializeField] private double _speed = 1;

    public void SetTime(double time)
    {
        // エラーチェック
        if (_splineContainer == null || _speed <= 0) return;

        // スプライン全体の道のり計算
        var splineLength = _splineContainer.CalculateLength();
        
        // 正規化された割合計算
        // 割合は「時間」×「速さ」/「距離」となる
        var percentage = (float) (time * _speed / splineLength);

        // 得られたスプライン位置を反映
        transform.position = _splineContainer.EvaluatePosition(percentage);
    }

    public void OnControlTimeStart()
    {
    }

    public void OnControlTimeStop()
    {
    }
}

上記スクリプトをSpeedBasedMover.csという名前で保存し、1つ目の例と同じ要領でセットアップを行うと機能するようになります。

必要に応じて、インスペクタからSpeedをお好みの値に調整してください。

実行結果

速度に基づいて移動するようになりました。

使い方は一つ目の例と全く一緒です。

スクリプトの説明

主な変更点は、SetTimeメソッド内の処理です。

// スプライン全体の道のり計算
var splineLength = _splineContainer.CalculateLength();

// 正規化された割合計算
// 割合は「時間」×「速さ」/「距離」となる
var percentage = (float) (time * _speed / splineLength);

// 得られたスプライン位置を反映
transform.position = _splineContainer.EvaluatePosition(percentage);

スプライン全体の道のり(長さ)は、SplineContainer.CalculateLengthメソッドから取得できます。

参考:Class SplineContainer | Splines | 1.0.1

割合は、「時刻 × 速さ / スプライン全体の道のり」として算出しています。

さいごに

スプラインとタイムラインを連携する方法について解説しました。

2通りの例を紹介しましたが、いずれも巻き戻しに対応していることが特徴です。時刻に基づいて位置を決定するようにしているためです。

ITimeControlインタフェースを活用すると、スプライン以外にも様々な物をタイムラインと同期できます。

パスに沿って移動するオブジェクトを使ったムービーを作成したい場合に役立つかもしれません。

関連記事

参考サイト

スポンサーリンク