C++幼女先輩

プログラミング成分多め

UNITYタイムライン PlayableTrackとBinding

PlayableTrack

プログラマとしては本題!
独自の処理をするトラックを作ります
用途はほんと色々あります
だいたい仕事では8割以上はこのカスタムトラックでタイムライン作るかも

Track、Asset、Behaviour、Mixerの4つで構成されます

PlayableTrack

カスタムのClipを入れる事のできるトラックです
UNITYから用意されているPlayableTrackをそのまま使う事も可能ですが
独自のバインディングやら初期化処理などを行う場合には自分で作ります
というか、自分で作りましょう

PlayableAsset

クリップ
実行時にインスタンス化するための情報を保持したりしている
実行時に下記のBehaviourをCreateする

PlayableBehaviour

インスタンス化したあとの実際のふるまいを定義する
カスタムトラックの肝

Mixer

上記のBehaviourの一種
クリップ同士(複数のBehaviour)をブレンドする事ができます

実際に作ってみる

Assets->Create->Playables
で、BehaviourとAssetのスクリプトのテンプレートが作成できる

f:id:murasame-labo:20210603160157p:plain

PlayableAsset

[System.Serializable]
public class NewPlayableAsset : PlayableAsset
{
    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        return Playable.Create(graph);
    }
}

上記のような何もしないテンプレートが作成される

PlayableBehaviour

public class NewPlayableBehaviour : PlayableBehaviour
{
    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable)
    {
        
    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable)
    {
        
    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        
    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        
    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info)
    {
        
    }
}

こちらは、コールバックのみのBehaviourが作成されました

BehaviourとAssetを連結させる

とりあえずPlayableTrackを作りクリップを作成する
今回はNewPlayableAssetという名前なのでそれを作成します

先ほどのBehaviourのコールバックにログを仕込んでも、まだ連結させてないのでコールバックが来ません
なので接続しましょう

AssetのCreatePlayableにて、今は汎用的なPlayableを作成していますが、それを自作のBehaviourにします

    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        return ScriptPlayable<NewPlayableBehaviour>.Create(graph);
    }

これで、今回作ったNewPlayableBehaviourが呼ばれるようになり、ちゃんとコールバックが呼ばれるように

ScriptPlayable

    public struct ScriptPlayable<T> : IPlayable, IEquatable<ScriptPlayable<T>> where T : class, IPlayableBehaviour, new()
    {
        public static ScriptPlayable<T> Null { get; }

        public static ScriptPlayable<T> Create(PlayableGraph graph, int inputCount = 0);
        public static ScriptPlayable<T> Create(PlayableGraph graph, T template, int inputCount = 0);
        public bool Equals(ScriptPlayable<T> other);
        public T GetBehaviour();
        public PlayableHandle GetHandle();

        public static implicit operator Playable(ScriptPlayable<T> playable);
        public static explicit operator ScriptPlayable<T>(Playable playable);
    }  

IPlayableBehaviourを持つBehaviourをCreateしたり。
GetBehaviourとかGetHandleとかもしかして使うかもね

PlayableTrack

TrackAssetから派生する

[TrackClipType(typeof(NewPlayableAsset))]
public class NewPlayableTrack : TrackAsset
{
    protected override Playable CreatePlayable(PlayableGraph graph, GameObject go, TimelineClip clip)
    {
        Playable playable = base.CreatePlayable(graph, go, clip);
        return playable;
    }
}  

TrackClipTipeにカスタムのAssetを指定する
先ほどつくったPlayableTrackを削除し、今度は今作ったカスタムTrackを作成 そのトラック上で右クリックをすると、先ほど作ったNewPlayableAssetのクリップを作成できる

TrackBinding

カスタムトラックにオブジェクトをバインディングできます アトリビュートを追加

[TrackBindingType(typeof(GameObject))]
[TrackClipType(typeof(NewPlayableAsset))]

他にもトラックの色を変えたりアトリビュートがある
TrackBindingを設定すれば指定したオブジェクトに対して影響を与えるトラックが作れます
今回はGameObject型にしていますが、Animatorを設定したり出来る
TrackBindingにシーン上のCubeを指定しよう

f:id:murasame-labo:20210603202430p:plain

TrackBindingの取得

ここが非常に昔から不満点ですが
UNITYのTimelineではBehaviourからTrack情報を取得するのが難しいです

クリップの開始は OnBehaviourPlayのコールバックで取得しますがそのプロトタイプは

  public override void OnBehaviourPlay(Playable playable, FrameData info)

となっており、トラックの情報が取れません
なので以下の3パターンで取得する事になります

Behaviour#ProcessFrameで取得

ProcessFrameだけはプロトタイプが

public virtual void ProcessFrame(Playable playable, FrameData info, object playerData);

となっており、playerDataにBindingが入っているのでCastすればよいです

    public GameObject target;

    public virtual void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        target = playerData as GameObject;
    }

ただし、ProcessFrameは毎フレーム呼ばれる処理です。最初のフレーム時のみtargetを取得するように改造が必要ですが
そもそも初期化時に設定すればいいものを毎フレーム処理したくありません

Mixerを作りMixerのOnBehaviourPlay時に渡す

今回はMixerを作っていないので省略しますが
わりと見ますね
ただし、Mixerを作る必要があるのでMixer不要な場合は手間です

TrackのCreatePlayable時に渡す

今回はこれでいきます
Trackのコードを

    protected override Playable CreatePlayable(PlayableGraph graph, GameObject go, TimelineClip clip)
    {
        var playable = ScriptPlayable<NewPlayableBehaviour>.Create(graph, 1);
        var trackBinding = go.GetComponent<PlayableDirector>().GetGenericBinding(this) as GameObject;
        playable.GetBehaviour().target = trackBinding;
        
        return playable;
    }

CreatePlayableで渡ってきたGameObjectからPlayableDirectorを取得し、そこからBindingを取得して
作成したBehaviour(Playable)に渡す

これでOK