DOTweenのアニメーションをTimeLineでプレビューする

タイトルの通りですがUnityのアニメーションのアセットのDOTweenをTimeLineでプレビューする方法です。

ネットで調べると既に実装されている方が居たのですが現実的に使用するにはもう少し考慮が必要だったので先の実装例をを踏襲しつつ実装を変更 & 設定方法を紹介したいと思います。

実装の条件は以下の通り。

  • 編集時にTimeLine上からアニメーションのプレビューができる
  • 実行時にはタイムラインでは動かさない
  • 実行時はプレビューしたアニメーションをスクリプトから起動する

一応この作業をするとプレビューできるようになりますが制作に有効化はちょっとわからないです。

環境

確認環境は以下の通りです。

実装

では早速実装をしていきたいと思います。

TimeLinePreviwerクラス

まずタイムラインに対応する表示を実現するためにITimeControlを継承したTimeLinePreviwerクラスを以下の通り作成します。

// TimeLinePreviwer.cs

using UnityEngine;
using UnityEngine.Timeline;
using DG.Tweening;
using System;

/// <summary>
/// エディタ上のTimeLineでDOTweenのアニメーションをプレビューします。
/// </summary>
public abstract class TimeLinePreviwer : MonoBehaviour, ITimeControl
{
    //
    // Fields
    // - - - - - - - - - - - - - - - - - - - -

    private Sequence sequence;

    //
    // ITimeControl impl
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// クリップ侵入時に呼び出されます。
    /// <see cref="ITimeControl.OnControlTimeStart"/> の実装
    public void OnControlTimeStart()
    {
        // nop
    }

    /// <summary>
    /// クリップ退出時に呼び出されます。
    /// <see cref="ITimeControl.OnControlTimeStop"/> の実装
    /// </summary>
    public void OnControlTimeStop()
    {
        // nop
    }

    /// <summary>
    /// エディター上で編集モードの時だけタイムライン更新時に表示を更新します。
    /// <see cref="ITimeControl.OnControlTimeStart"/> の実装
    /// </summary>
    public void SetTime(double _time)
    {
#if UNITY_EDITOR
        if (!Application.isPlaying)
        {
            if (this.sequence == null)
            {
                return;
            }
            this.sequence.Goto((float)_time);
        }
#endif
    }

    //
    // Runtime impl
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// エディター上で編集モードの時だけ実行します。
    /// </summary>
    public void OnValidate()
    {
#if UNITY_EDITOR
        if (!Application.isPlaying)
        {
            this.setupSequence();
        }
#endif
    }

    //
    // Abstracts
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// 派生クラスで実装され、受けっとったパラメータに対して設定をすることでアニメーションを構築します。
    /// </summary>
    protected abstract void ImplementSequence(Sequence seq);

    //
    // Public Methods
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// プレビュー用のアニメーションを実行します。(スクリプト制御 and 実行時用)
    /// </summary>
    public void Play(Action<TimeLinePreviwer> completed = null)
    {
        var seq = this.setupSequence();
        if (completed != null)
        {
            seq.AppendCallback(() => completed(this));
        }
        seq.Play();
    }

    //
    // Private Methods
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// シーケンスを作成し作成したインスタンスを取得します。
    /// 返されるインスタンスはフィールド <see cref="sequence"/> にも設定されます。
    /// </summary>
    private Sequence setupSequence()
    {
        if (this.sequence != null)
        {
            this.sequence.Goto(0);
            this.sequence.Kill();
        }
        var seq = DOTween.Sequence();
        this.ImplementSequence(seq);
        this.sequence = seq;
        return seq;
    }
}

ほぼ参考サイトの通りですが、DOTweenのGotoメソッドがITimeLineのSetTimeにいい感じに一致することを利用してSetTimeが呼び出されたと時にシーケンスの特定の時刻にGotoすることで見た目の対応をしています。

MoveToAnimationクラス

上記クラスを継承してアニメーションを実装するクラスを以下の通り記述します(参考サイトほぼそのままですが…

ある程度インスペクター上から調整できるようにアニメーションパラメータの一部(もしくは全部)にSerializeFieldを付与します。また、実行時にスクリプト上からパラメータを調整できるようにプロパティで操作できるパラメータを公開しておきます。

// MoveToAnimation.cs

using DG.Tweening;
using UnityEngine;
using UniRx;

/// <summary>
/// 線形移動アニメーションを行うクラス
/// </summary>
public class MoveToAnimation : TimeLinePreviwer
{
    //
    // Fields
    // - - - - - - - - - - - - - - - - - - - -

    [SerializeField] private Vector3 toMovePosision;
    [SerializeField, Range(0.1f, 10.0f)] private float toMoveDuration = 1.0f;
    [SerializeField] private Ease easingType = Ease.Linear;

    //
    // Props
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// 移動先の座標を設定または取得します。
    /// </summary>
    public Vector3 ToMovePosision { get => this.toMovePosision; set => this.toMovePosision = value; }

    /// <summary>
    /// 実行時間を設定または取得します。
    /// </summary>
    public float ToMoveDuration  { get => this.toMoveDuration; set => this.toMoveDuration = value; }

    /// <summary>
    /// イージングの種類を設定または取得します。
    /// </summary>
    public Ease EasingType { get => this.easingType; set => this.easingType = value; }

    //
    // BaseTimeControl impl
    // - - - - - - - - - - - - - - - - - - - -

    // プレビュー対象のアニメーションの作成
    protected override void ImplementSequence(Sequence seq)
    {
        seq.Append(this.transform.DOLocalMove(this.ToMovePosision, 
            this.ToMoveDuration).SetEase(this.EasingType));
    }
}

プレビューの利用方法

上記スクリプトをプロジェクトに追加したら次にシーン上にアニメーションの対象を配置して上記のMoveToAnimationをアタッチします。

今回は2D Spriteをシーン上に以下の通り配置しました。

まず、Spriteという名前の2D Spriteを作成します。

f:id:Takachan:20200202231231p:plain

次にこのGameObjectにPlayableDirecotrを追加します。

追加したら"Play On Awake"はOFFにしておきましょう(仕組み上実害はないですが実行した瞬間タイムラインが動作するのを防ぎます)

f:id:Takachan:20200202231239p:plain

f:id:Takachan:20200202231254p:plain

f:id:Takachan:20200202231302p:plain

次にPlayableDirectorに設定するTimleLineをProjectに追加します。

f:id:Takachan:20200202231311p:plain

"MoveToTimeLine" という名前でプロジェクトに追加しています。

f:id:Takachan:20200202231318p:plain

次に先ほどのPlayable DirectorにTimeLineを設定します。

f:id:Takachan:20200202231325p:plain

次にMoveToTimeLineをダブルクリックしTimeLineウインドウを表示します。

TimeLine上に新規にControl Trackを追加します。

f:id:Takachan:20200202231333p:plain

追加したControl Trackへ最初に作成したゲームオブジェクトを指定します。

右端の3つの点をクリックするとメニューが出るのでそこからAdd From Game Objectを選択します。

f:id:Takachan:20200202231340p:plain

f:id:Takachan:20200202231347p:plain

そうするとこのように適当な長さのトラックが追加されます。

スクリプトもしくはインスペクターで指定した時間以上は動作しないのでトラックの長さはプレビューがいい感じになるように調整してください。

この状態でタイムラインの再生を押すとエディタ上でSpriteの動作がプレビューされます。

f:id:Takachan:20200202231416p:plain

f:id:Takachan:20200202232955g:plain

実行時にスクリプトからアニメーションを再生する

上記のままでは編集時にプレビューできるだけで実際の実行時に動作しないため、何らかのトリガーで起動するようにスクリプトを追加します。

Player.csというスクリプトを作成し、画面がクリックされた時にアニメーションを起動するようにしてみます。

// Player.cs

using DG.Tweening;
using Takap.Utility;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private bool isCustom;

    public void Update()
    {
        if (!Input.GetMouseButton(0))
        {
            return;
        }

        var anim = this.GetComponent<MoveToAnimation>();
        if (!this.isCustom)
        {
            // 画面をクリックしたときにアニメーションを実行する
            // ** 終了したらコールバックを呼び出す
            anim.Play(p => Debug.Log("completed"));
        }
        else
        {
            // パラメータを設定して移動を行う
            anim.ToMoveDuration = 1.5f;
            anim.ToMovePosision = new Vector3(3, 3);
            anim.EasingType = Ease.InBounce;
            anim.Play(p => Debug.Log("completed"));
        }
    }
}

上記スクリプトをGameObjectに設定します。

f:id:Takachan:20200202233250p:plain

そして実行すると以下のようになります。

f:id:Takachan:20200202234300g:plain

タイムラインで起動することもできますが、アニメーションの時間のバーとDOTweenの実行時間が一致しないことがあるためスクリプトから起動するときにタイムラインではなくPreviwerのほうからアニメーションを起動しています。

もしそれでも問題ないケースの方が多ければ、PreviwerスクリプトのSetTimeの中身を実行時も有効化したうえでインスタンスの存在を実行前に保証すればPlayerスクリプトのUpdate上で以下のようにコードを記述してTimeLine経由でアニメーションを実行しても良いかもしれません。実用的かどうかはよくわからないですが…ちょっと便利かどうかも判断できないです。

var pd = this.GetComponent<PlayableDirector>();
pd.Play();

以上です。

参考サイト

KAZUPON研究所:【Unity】DoTweenアニメーションをTimeLine上でPreviewする https://kazupon.org/unity-dotween-timeline-preview/

テラシュールブログ:【Unity】ITimeControlで、Timelineから"コンポーネント"を操作する

http://tsubakit1.hateblo.jp/entry/2017/09/21/234138