今回は再生中のAnimationControllerに、別のアニメーションを流し込む方法を見つけたので、こちらにメモします。
- AnimationControllerという機能
- Playable APIとAnimationControllerを融合!
- 再生中のAnimationControllerに別のAnimationClipをフェードしつつ再生する
- 既知の問題
- 関連
AnimationControllerという機能
Unity 5のMecanimはグラフィカルなアニメーションの制御を提供してくれます。この機能のお陰である程度複雑なアニメーションをプログラムを記述することなく制御出来ることが可能になりました。
この機能の理屈は「アニメーションを再生するキャラクター周辺の値をパラメータとして渡せば、AnimationControllerが最適なアニメーションを選択してくれる」という物です。
例えば現在の移動方向や上下の移動速度、前方の壁の有無など、状況に応じてアニメーションを選択してくれる…といった感じです。
その辺りを考えたりUnity 5実装前のデモを眺めてみると、MecanimってステートマシンではなくBlendTreeが主役なんだな~と感じてます。 Animator.SetFloatはdampTimeを使用して急激に遷移しないようにするのがポイント。
またBlendTreeでは実装が面倒な”順番を伴うアニメーション”の表現や、明確に状況が異なるものはステートマシンを利用することで表現することができます。
個人的には、”周囲の状況やスティック等の値を見て動かす” のがBlendTree、”ボタン操作や特別な状況” による変化、再生順のあるものはステート切替かな?と感じています。
周囲の状況はStateMachineBehaviourで収集する感じ。(StateMachineBehaviourでパラメータを更新)
AnimationControllerは突発的なアニメーションを挟むと複雑化する
こんな感じでアニメーションを表現するのがAnimationControllerなのですが、実際使ってみると非常に面倒な部分が多いと感じます。
これは個人的には、実際のゲーム開発では ”終了待ちする短期的なアニメーション” というものが非常に多い為かなと考えています。また割り込みアニメーションも非常に多く、その辺りも全てステートを用意すると複雑化するなといった印象です。
こういった個別のアニメーション毎にTriggerのパラメータを割り当てるのはかなり苦痛ですし、一つのステートに対して大量の矢印が引かれるのは美しくありません。*1
これを強引に解決するのがStateを作ってAnimator.CrossFadeというアプローチです。
事前にAnimationControllerに使用するAnimationClipを事前に登録しておきAnimationOverrideControllerで上書きするといったアプローチです。
ただし「AnimationControllerはAnimationClipのStateを動的に増やすことが出来ないため、AnimationControllerに事前に最大数のAnimationClipを登録しておく必要があります。さらにFadeを使用する場合、遷移はリニアに行われる点、ステートが多すぎるとパフォーマンス的に問題になる点も悪いポイントです。
「これ使うぐらいならAnimation使うわ!」というのは割と納得の理由です。
一応、動的にAnimationClipを差し替える事で幾つかの問題も解決出来なくはないのですが、まぁ…色々。
そうだ、Playable APIを使おう
ということで ”Playable APIを使用して全部スクリプトで管理してしまおう" という結論に至る訳ですが、実際これをやっているとAnimatorのシーケンス制御やBlendTreeの機能が欲しくなってきます。
失って分かるこの大切さ。
そう、ならばブレンドさせようAnimationControllerとPlayableを。
基本的な移動はAnimationController、突発的な動きをPlayableAPIで賄う感じです。
Playable APIとAnimationControllerを融合!
AnimationControllerとPlayable APIを融合してみます。
この場合のポイントは以下の二つです。
- AnimationControllerが既に再生中のPlayable Graphは変更できない
(変更しても即座に巻き戻される) - AnimationPlayableOutputのweightがブレンド率を決める
まずはシンプルに動作を確認してみる
この辺りを前提に、まず最も簡単にAnimationControllerにAnimationClipPlayableをブレンドしてみます。
コードはこんな感じです。
ポイントはPlayableGraphを自分で作っている点と、Outputのウェイト調整です。
実は他の幾つかの記事では面倒なのでAnimationControllerからPlayableGraphを取得していますが、稼働中のAnimationControllerのPlayableGraphは変更しても即治るという妙な動きをします。そのため、こういった形で合成する場合にはAnimationControllerとは別にPlayableGraphを用意しました。
なお、PlayableGraphを開放し忘れると、最悪エディタが落ちます。
もう一つはOutputのウェイト調整です。
この処理でAnimationControllerとPlayableの比率を設定して、上手い感じに合成出来ています。
この際、PlayableGraph経由でAnimationControllerもしくはPlayableGraphの動作を止めておくと、色々と効率的です。
合成比率をカーブで指定
この処理を作ってみた際に気づいた思わぬメリットの一つが、アニメーションのブレンド率をカーブで制御できる点です。
基本的にMecanimのブレンドはリニアに行われるのですが、Playableでウェイトを直で指定できるこのアプローチでは、カーブを経由してアニメーションのブレンド率を制御することが出来る感じです。
例えばブレンドの中間の時間を短く指定しておけば、ブレンド中に出てくる違和感のある部分をある程度見せないようにできるかもしれません。
ポイントは[SerializeField]でAnimationCurveを編集可能にしておき、AnimationCurve.Evaluateで値を補正してやることです。
これを活用するなら、AnimationClipを単純に取得するのではなくScriptableObjectのパラメータと一緒にして、”指定アニメーションならブレンドはこう” みたいにすると手間だけど面白そうな気配があります。
再生中のAnimationControllerに別のAnimationClipをフェードしつつ再生する
さて、今回の本題です。
これは要するに、AnimationClipPlayableを動的に作成・ウェイトを調整すれば行けます。
SampleのPlayをボタン経由で呼び出してみると、こんな感じのこともできます。
実行時にAnimationClipを流し込む事もできるので、キャラクター毎の特殊技や任意の場所でのアクション等にも使えるかもしれません。
もしAnimationClipPlayable再生中にキャンセルしてAnimationClipPlayableを再生するのもブレンドする…となると、AnimationMixerPlayableを使用するのが良さそうです。
下のGifアニメでは、キャラクターの移動系(周辺環境で動作が変わるもの)はAnimationController、攻撃などのアクションはPlayableの割り込みで行っています。
既知の問題
アプリケーションをバックグラウンドに回すとPlayableのアニメーションが止まる動作を確認しています。
関連
今回のやり方は、コレを見て思いつきました。
今回の内容の発展形
もうちょっと安定してくれたら最高なんだけど