Unityタイムラインでベジェ曲線に沿ってオブジェクトを移動する

# はじめに

タイムラインで軌道にそってオブジェクトを動かすことはアニメーションでも実現可能ですが、軌跡が見えないのでタイムラインを拡張して実装しようと思いました。
GitHubに公開していて、OpenUPMにも登録してあります。

https://github.com/k-okawa/WaypointSystemForTimeline

![143799354-433e3214-bd28-4a22-a105-77f4bce7dc58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/4445c56e-3a04-2a76-2f9b-e22ca76b2e26.png)

# 使い方
使い方はGitHubのReadMeに記述してあるので、こちらをご参照ください。

https://github.com/k-okawa/WaypointSystemForTimeline/blob/master/README.ja.md

こちらの記事では上記パッケージを例にタイムラインの拡張方法を書いていきます。

# タイムライン拡張
WayPointSystemForTimelineを例に拡張方法を書いていきます。

## タイムライン拡張に必要な３大クラス
今回はトラック同士をブレンドしないので、タイムライン拡張に必要なクラスは３つになります。

### PlayableTrack
トラック作成を担うクラスです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/8c81f77d-05e1-337f-2a2d-5bd4ce91d0a7.png)

```csharp
[Serializable]
[TrackClipType(typeof(WaypointTimelineClip))]
[TrackBindingType(typeof(WaypointComponent))]
public class WaypointTimelineTrack : PlayableTrack {
    
}
```

ブレンドが必要な場合はTrackAssetを継承するようです。
[参考(https://light11.hatenadiary.com/entry/2019/05/16/214328#Clip%E3%82%92%E4%BD%9C%E3%82%8B)

**Serializable**
タイムラインで編集した情報はファイルに保存して、実行時にはそこからオブジェクトを作る必要があるので、これから作成するクラスにこちらの属性はつけるようにしています。(つけなくても良い場合もあると思いますが、基本的につけていれば間違いないと思います)

**TrackClipType**
トラック上に配置するクリップのタイプの指定します。
WaypointTimelineClipの詳細は後述の[PlayableAsset](#PlayableAsset)をご参照ください。

**TrackBindingType**
トラックに参照を持たせたいオブジェクトがある場合に指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/758dc580-fed8-5e6b-45cd-a378b1d1877c.png)

後述の[PlayableBehaviour](PlayableBehaviour)でこちらを取得して操作することが可能です。

### PlayableAsset
タイムライン上のクリップに該当します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/287d5b68-ba27-341d-c53b-4f4db1cb34eb.png)

```csharp
[Serializable]
public class WaypointTimelineClip :  PlayableAsset, ITimelineClipAsset {
    public WaypointTimelinePlayableBehaviour template = new WaypointTimelinePlayableBehaviour();

    public ClipCaps clipCaps => ClipCaps.SpeedMultiplier;

    public override Playable CreatePlayable(PlayableGraph graph, GameObject owner) {
        var playable = ScriptPlayable<WaypointTimelinePlayableBehaviour>.Create(graph, template);
        return playable;
    }
}
```

後述の[PlayableBehaviour](###PlayableBehaviour)とPlayableGraphからScriptPlayableを生成します。
おまじないのようなものでほとんどケースで書き方はあまり変わらないかと思います。

**ITimelineClipAsset**はクリップの性質を決めるもので指定したいときに継承します。
clipCapsプロパティを実装して性質を指定します。
今回はブレンドを対応しないためSpeedMultiplierのみを指定しました。(これもなくてもいいかも)
複数指定したい場合は|でつなげて指定していきます。

https://docs.unity3d.com/ja/2018.4/ScriptReference/Timeline.ClipCaps.html

今回のSpeedMultiplierのみの指定だと以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/12b402fc-001f-e534-aa12-38534b604667.png)


また、ITimelineClipAssetを継承しない場合はクリップのインスペクターは以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/6673485a-e99b-43c6-f24c-2c4d47c81fec.png)
指定しない場合はClipCaps.Blendingが設定されるようです。

### PlayableBehaviour
クリップが再生されているときのふるまいを実装します。

```csharp
[Serializable]
public class WaypointTimelinePlayableBehaviour : PlayableBehaviour {
    public override void ProcessFrame(Playable playable, FrameData info, object playerData) {
        if (playerData == null) {
            return;
        }
    
        var wayPointsComponent = playerData as WaypointComponent;

        double t = playable.GetTime() / playable.GetDuration();
        wayPointsComponent.SetPosition((float)t);
    }
}
```

今回はProcessFrameのみを使用して1フレームごとの更新処理を記述します。
他にもポーズ時やクリップ再生が始まったときなどの処理を書くことができます。
[PlayableTrack](###PlayableTrack)のTrackBindingTypeで指定したものがobjectで渡されてくるのでキャストでコンポーネントにアクセスすることができます。
WaypointComponentに0.0f~1.0fの補完値を渡すことでベジェ曲線上の位置にPositionを変更することができるので、クリップの現在の時間をクリップの長さで割って正規化して渡しています。

# ベジェ曲線上に移動させているMonoBehaviour
タイムライン側は３つのクラスを作成して、それらの中身も少ない記述量で今回のシステムを実現しています。
ベジェ曲線に関する計算はWaypointComponentですべて実装されています。

https://github.com/k-okawa/WaypointSystemForTimeline/blob/master/Assets/Bg/WaypointSystemForTimeline/Runtime/WaypointComponent.cs

今回の記事はベジェ曲線の計算に関する内容ではないので説明は省かさせていただきます。

# ベジェ曲線を編集するためのエディター拡張
https://github.com/k-okawa/WaypointSystemForTimeline/blob/master/Assets/Bg/WaypointSystemForTimeline/Editor/WaypointComponentInspector.cs

上記と同じくエディタ拡張に関する記事ではないので説明は省かさせていただきます。

現状ベジェ曲線の編集がしやすいかどうかがいまいちなのでこちらは改善する余地はありそうです・・・

# (余談)クリップのインスペクター上でPlayableBehaviourのフィールドを設定する
今回のシステムは特に設定することはないのでなくても大丈夫ですが、インスペクターで値を設定することが可能です。
(今後機能追加するときに必要になるかも)
WaypointTimelinePlayableBehaviourに

```csharp
public int test;
```
を追加すると以下のように表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/f6de3dd1-b8d4-5bc2-c234-8cabc72b23c8.png)

Spine公式のタイムライン拡張のスクリプトを参考にさせて頂いたのですが、割と力技でした。
スクリプトはこちらになります。

https://github.com/k-okawa/WaypointSystemForTimeline/blob/master/Assets/Bg/WaypointSystemForTimeline/Editor/WaypointTimelineClipInspector.cs

# 最後に
タイムライン拡張はとっかかりがないとなかなか手を付けにくいのですが、一度簡単なものを作ると今後スムーズに作れるようになると思います。
これからタイムライン拡張をしようと思われている方に参考になれば幸いです。