見出し画像

Timelineアニメーションの移動先を外部から指定する(その1、Unityメモ)

Unityメモをマガジンに束ねました。

 今回はいわゆる「スキル」のエフェクト演出を実装するにあたって、Timeline上でユニットを固定の目標地点ではなく任意のターゲットに向かって動かせるような機能を実装しました。

TimelineとDOTweenとの違い

 GameObjectの位置を1フレームずつ動かす方法としては、Unity公式の機能であるTimelineや、DOTweenなどの外部ライブラリを使う方法があります。

Timeline

メリット:GUI上でプレビューしながらタイミングを編集できる
デメリット:機能追加にとても手間がかかる(この記事)

DOTweenなどのライブラリ

メリット:実装が楽。ほんと楽
デメリット:動作確認で毎回コンパイルが必要なため、確認の手間がかかる

 以前の記事ではTimelineの使用をあきらめたのですが、演出を組むにあたりGUI上で細かく確認ができないデメリットが無視できなさそうなので、Timelineでの演出の編集を検討することにしました。

問題点

 Timelineの使用をためらった大きな理由は、「任意のターゲットに向かって行動者を動かす」という演出がデフォルトの機能ではできなかった点です。DOTweenの場合、任意のターゲットの座標をスクリプト上で参照して移動先に指定する、という処理は簡単に書けます。
 一方Timelineでは、デフォルトの機能では移動先座標などのパラメータは固定値しか指定できず、Timelineアセットの外側にあるデータを参照することができません。そのため、ターゲットに応じて移動先座標を変えることができませんでした。

Transform Tween Trackを使いたい。しかし

 Timelineの機能を色々調べてみて、目標地点を外部から設定できるライブラリがあることが分かりました。Default Playablesという公式アセットの中のTransform Tween Trackというカスタムトラックです。記事は老舗テラシュールブログのものです。

こちらにもTransform Tween Trackの使用例がありました。イベント演出の作成にも有用そうです。

 Transform Tween Trackは、GameObjectで移動開始地点・終了地点を指定するという方法が編集作業時に分かりやすいうえに、指定したGameObjectを動的に移動させると、アニメーションさせたいオブジェクトの移動先も変化させることが可能です。演出の再生前に、地点を指すGameObjectインスタンスの位置を行動者とターゲットの位置に書き換えてやれば、任意の行動者を任意のターゲットに向かって動かすことができます。
 早速試してみたのですが、一つ欠点がありました。それは地点を指すGameObjectを指定してもプレハブ化すると指定が保存されない、というものです。

Transform Tween Trackを拡張したい

アセットからシーン上のオブジェクトを参照するには

 Timelineというよりアセット全般の特徴として、シーンとアセットは分離していて、アセットから特定のシーン上のGameObjectインスタンスへの参照を保存しておくことができません。
 地点を指すGameObjectとTimelineアセットをひとまとめにして演出データ1個へとプレハブ化することは可能ですが、そうすると地点を指すGameObjectを演出データごとにバラバラに持たせることになります。インスタンス名を規約で統一しておいても、演出再生のたびに毎回インスタンスをTransform.Findで探し出すのは処理が少し重そうです。

 そこで以下の手段です。この記事もテラシュールブログにありました。

シーン上からアセットにデータを与える手段もTimelineにあって、それを使うことで同じ演出データ(特にTimelineアセット)を使っても再生ごとに動きを変えることができます。これを使ってTransform Tween Trackを拡張することにしました。

(参考)Find系の比較

GameObject.Find():計算量がかかる。地点のObjectをDisableにしていると検索されない
GameObject.FindWithTag():計算量を抑えられる。地点がDisableだと検索されない、タグ名がかぶる場合がありうる
Transform.Find():計算量がかかるが、特定のGameObject直下を探せる。地点がDisableでも検索可→使うとすればこれ

Timelineの中身はPlayableGraph

 Transform Tween Trackを拡張するにあたって、Timelineの中身を勉強する必要がありました。以前も似たことを思いましたが、Timelineって見えているように実装されているわけじゃないんですね…
 つまりPlayable APIによる操作の一部をGUIエディタとして可視化したものがTimelineであって、Timelineに関わる機能を操作するにはPlayableのことを知る必要があります。(ライブラリ実装の時系列としては逆、Timelineを抽象化したものがPlayableだとは思いますが)

 Playableの全体像もテラシュールブログから。PlayableGraphがTimelineの実体で、PlayableOutputがTimeline(PlayableGraph)とシーン上のオブジェクト(Animator)とのインタフェースとなります。

クラス構造の解説はこちらから。

PlayableDirectorが仲介役

 そしてもう一つ重要なのが、Playable Directorです。「GameObjectにTimelineを設定すると自動的にアタッチされるやつ」です。実はこれがTimelineアセットからTimelineインスタンス(PlayableGraph)を生成し、アニメーションさせるオブジェクトをTrackBindingする(トラックに紐づける)役割を持っています。そしてアセットにデータを渡すときにはPlayableDirectorコンポーネントにデータを登録する必要があります。

PlayableDirector周りの関連。PlayableDirectorがシーン上のGameObjectとTimelineアセット(実態はPlayableGraph)の間のインタフェースとなっている

 注目点として、PlayableDirectorに設定するBindingsには、PlayableDirectorを持つGameObject以外のオブジェクトも設定できます。なので演出データのルートとなる空のGameObjectにPlayableDirectorを設定し、その子要素に実際に動くGameObjectを配置しておき、親のPlayableDirectorのBindingsに子を設定する、ということが出来ます。
 そうすることで、親のPlayableDirectorとTimelineアセットを使って子を動かせて、かつ演出データをひとまとめにできてプレハブを作れます。プレハブの中にあるオブジェクトへの参照は残るので、プレハブ化して参照が切れる問題も起きません。

演出データをひとまとめにしたプレハブ。画面右のStart Ref, End Refがプレハブ内のオブジェクトを参照していて、参照は有効(参照が切れた場合は赤字になる)

 ちなみにTimelineエディタで編集しているのはアセットです。そのため、エディタでTimelineアセットを編集すると、同じTimelineアセットを参照する他のGameObjectの動きも変化します。

Timelineアセットにパラメータを渡す

 Transform Tween Trackを拡張するときにやりたいことは以下の通りです。
・動かすGameObject(行動者とかターゲット)とは別に、動きのパラメータとして地点を指すGameObjectを指定
・シーンから切り離されたアセットに対してシーン上のオブジェクトを与える
・実行時にその都度シーン上のオブジェクトを与える

 動かすGameObjectはPlayableDirectorのGUIで指定できて、トラック名を規約で決めておけば、スクリプトでもPlayableDirector.SetGenericBinding()で設定できます。別のパラメータを渡す場合も同じようにSetGenericBindingで値となるオブジェクトを設定します。

 そして、PlayableDirectorにデータを登録する方法がいくつかあります。
・Timeline側(具体的にはクリップ、後述)でExposedReferenceを使ってメンバ宣言しておき、GUI上でオブジェクトを指定する。元のTransform Tween Trackの実装はこちら。
・シーン側ではPlayableDirectorに与えるデータのキーと値を登録するコンポーネントを用意し、Timeline側ではPlayableDirectorからキーを使ってデータを取得する
 このうち前者では、PlayableDirectorをプレハブ化するとExposedReferenceに指定したオブジェクトの設定が消えてしまいます。そのためTransform Tween Trackの拡張では後者の、PlayableDirectorにキーと値を設定する方法をとりました。

Transform Tween Trackの拡張に使った方法

次の記事に続く

記事が長くなったので分割しました。次の記事では実装を載せています。

この記事が気に入ったらサポートをしてみませんか?