【Unity】Playables APIの要点まとめ!色んなものを再生・ブレンドするためのAPI

色んなものを再生・ブレンドするためのPlayables APIについてまとめました。

Unity2018.3.1

Playables APIとは?

Playables APIとは、UnityのAnimator Controller(など)で使われている内部的な仕組みのことです。
言い方を変えると、Playables APIを使うとAnimator Controllerのようなものが作れます。

試しにPlayables APIを使ってアニメーションを再生してみます。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class SimpleAnimationPlayableExample : MonoBehaviour
{
    [SerializeField]
    private AnimationClip _animationClip;

    private PlayableGraph _playableGraph;

    private void Start()
    {
        // Playable Graphを生成(名前は任意)
        _playableGraph = PlayableGraph.Create("Example Playable");

        // 一つのAnimationを表すもの(= Playable)を作成
        // Playableは必ずPlayable Graphに所属するので引数にPlayable Graphを渡す
        var clipPlayable = AnimationClipPlayable.Create(_playableGraph, _animationClip);

        // Playable Graphで計算されたアニメーションデータはAnimatorで使われてキャラなどが動く
        // そのためにPlayable GraphとAnimatorを紐づけるのがPlayableOutput
        var playableOutput = AnimationPlayableOutput.Create(_playableGraph, "Animation", GetComponent<Animator>());
        // 最終的に出力するアニメーションをPlayable Outputに登録
        playableOutput.SetSourcePlayable(clipPlayable);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.P)) {
            // Playable Graphを再生する
            _playableGraph.Play();
        }
        if (Input.GetKeyDown(KeyCode.S)) {
            // Playable Graphの再生を止める
            _playableGraph.Stop();
        }
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}

これをAnimatorをアタッチしたキャラクターのFBXにアタッチすると指定したアニメーションが再生されます。

やっていることはコメントの通りですが下記の通りです。

  1. Playable Graphというものを生成して
  2. それにPlayableを登録して
  3. Playable OutputでPlayable GraphとAnimatorを紐づける

なぜこんな面倒な実装をするのか?Playable Graphの必要性

それではアニメーションを再生するためだけになぜわざわざPlayable Graphを使うような回りくどいことをするのでしょうか。

ここでAnimator Controllerについて考えてみます。
Animator Controllerでは2つのモーションをブレンドできます。
この実装を考えると、PlayableとあるPlayableを混ぜ合わせるPlayableを作り、それをOutputに接続することになります。

f:id:halya_11:20190216235055p:plain

さらにいくつかのモーションをブレンドしたり、それをさらにほかのモーションとブレンドしたり、マスクを掛けたり・・
といったことを考えていくとPlayable同士のつながりはどんどん複雑化していきます。
こうして複雑化していくPlayableの関係を管理するのがPlayable Graphということになります。

ちなみに上図はPlayable Graphを可視化できるPlayableGraph Visualizerを使用しています。
導入方法や使い方は簡単なので割愛しますが便利なのでお勧めです。

github.com
【2019/6/7追記】Package ManagerからGraph Visualizerがインストールできるようになっていました(Unity2018.4で確認)

AudioやTimelineにも使われている汎用的なAPI

わざわざ回りくどい実装をするもう一つの理由として、
「AudioやTimelineなど他の機能も同じインタフェースで使えるようにする」という狙いがあります。

たとえばAudioの再生はPlayables APIを使って下記のように実装できます。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Audio;

[RequireComponent(typeof(AudioSource))]
public class SimpleAudioPlayableExample : MonoBehaviour
{
    [SerializeField]
    private AudioClip _audipClip;

    private PlayableGraph _playableGraph;

    private void Start()
    {
        _playableGraph = PlayableGraph.Create("Example Audio Playable");
        
        var clipPlayable = AudioClipPlayable.Create(_playableGraph, _audipClip, true);

        var playableOutput = AudioPlayableOutput.Create(_playableGraph, "Audio", GetComponent<AudioSource>());
        playableOutput.SetSourcePlayable(clipPlayable);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.P)) {
            _playableGraph.Play();
        }
        if (Input.GetKeyDown(KeyCode.S)) {
            _playableGraph.Stop();
        }
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}

Animationと同じインタフェースで実装できていることがわかります。

「DeltaTimeを与えて結果が計算できるもの」に使える

ここまでの例ではUnityがAnimationやAudioのために用意しているPlayableを使った実装を紹介してきました。
では自分でそれ以外の機能を作るときにどんなものであればPlayables APIを使えるのかという疑問が生まれます。
これについては「DeltaTimeを与えて結果が計算できるもの」であるといえそうです。

Playable Graphは下記のように書くと自前で更新方法を制御できます。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class SimpleAnimationPlayableExample : MonoBehaviour
{
    [SerializeField]
    private AnimationClip _animationClip;

    private PlayableGraph _playableGraph;

    private void Start()
    {
        _playableGraph = PlayableGraph.Create("Example Playable");

        var clipPlayable = AnimationClipPlayable.Create(_playableGraph, _animationClip);

        var playableOutput = AnimationPlayableOutput.Create(_playableGraph, "Animation", GetComponent<Animator>());
        playableOutput.SetSourcePlayable(clipPlayable);

        // 更新モードを手動に変更
        _playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
    }

    private void Update()
    {
        // 2倍の速度で再生する
        var timeScale = 2;
        _playableGraph.Evaluate(Time.deltaTime * timeScale);
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}

逆に考えれば、このようなインタフェースに耐えられる設計のものであれば、
Playables APIを使えるということになります。

ScriptPlayableでPlayableを自作する

最後にScriptPlayableを使ってPlayableを自作してみます。
今回は試しにParticle Systemを再生するPlayableを作ってみます。

まずはPlayableBehaviourというクラスを継承したクラスを作成します。

using UnityEngine;
using UnityEngine.Playables;

public class ParticleSystemPlayable : PlayableBehaviour
{
    private ParticleSystem _particleSystem;

    // 初期化する
    public void Initialize(ParticleSystem particleSystem)
    {
        _particleSystem = particleSystem;
        _particleSystem.Stop();
        if (_particleSystem.useAutoRandomSeed) {
            _particleSystem.randomSeed = 0;
            _particleSystem.useAutoRandomSeed = false;
        }
    }

    // Playableが再生されたときの処理
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        _particleSystem.Play();
    }

    // Playableの再生が止まった時の処理
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        _particleSystem.Stop();
    }

    // フレーム毎の処理
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        _particleSystem.Simulate(info.deltaTime, false, false);
    }
}

PlayableBehaviourは一つのPlayableの動作を定義するための基底クラスです。
今回オーバーライドしたものの他にもいろいろなメソッドが定義されていますが、今回は割愛します。

次にこれをPlayable Graphに登録して再生できるようにします。

using UnityEngine;
using UnityEngine.Playables;

public class ScriptPlayableExample : MonoBehaviour
{
    [SerializeField]
    private ParticleSystem _particleSystem;

    private PlayableGraph _playableGraph;
    
    private void Start()
    {
        _playableGraph = PlayableGraph.Create();

        // ScriptPlayableを作る
        var particleSystemPlayable = ScriptPlayable<ParticleSystemPlayable>.Create(_playableGraph, 0);

        // Playable Behaviourを取得して初期化
        particleSystemPlayable.GetBehaviour().Initialize(_particleSystem);

        // Playable Outputを作ってPlayableを登録
        var output = ScriptPlayableOutput.Create(_playableGraph, "ParticleSystem");
        output.SetSourcePlayable(particleSystemPlayable);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.P)) {
            _playableGraph.Play();
        }
        if (Input.GetKeyDown(KeyCode.S)) {
            _playableGraph.Stop();
        }
    }

    private void OnDestroy()
    {
        _playableGraph.Destroy();
    }
}

ポイントはScriptPlayable.Create()でPlayableを作る点と、ScriptPlayable.GetBehaviour()でPlayableBehaviourを取得する点です。
それ以外は普通のPlayableの使い方と変わりません。

これを適当なGameObjectにアタッチしてParticle Systemの参照を持たせると、Playableによる再生が行われます。

参考

docs.unity3d.com

labs.gree.jp

github.com

tsubakit1.hateblo.jp