環境
Unity2021.2.0f1
Arbor3 v3.8.8
UnityのAnimatorは使いづらい
Unityでアニメーションの遷移とかの管理、何を使っていますか?
公式はAnimatorControllerを使うように言ってきますし、Unity初心者講座とか見てもそう言ってます。
が、AnimatorControllerは、アニメーションが4つ5つ出てきたあたりから管理が怪しくなり、
ステートの数が増えるにつれて、遷移の可能性は級数的に増えていくので、割と早い段階で地獄の様相を呈してきます。
これはAnimatorControllerの公式マニュアルにある画像なんですが、
このステートの数でもう嫌です。
しかも、よく見ると地上以外で死ぬことを想定していないので多分バグです。
ステートが増えてきた場合、AnimatorControllerはレイヤー分けとかもできますが、それで解決できるかは怪しいところです。
これはAnimatorControllerがダメというより、
そもそもステートマシンというのが、ステートの数が多いと破綻するものなのです。
個人的な意見だと、ステートが3つまでなら問題無くて、5つを超えたら別の方法を検討した方がいい、と思っています。
アニメーションの場合、ステートが5つを超えることはよくあるでしょう。
つまり、アニメーションの管理をステートマシンで行うことが本当に適しているかは、割と疑問の余地があるのです。
ビヘイビアツリー
というわけで、ステートマシンの替わりとしてビヘイビアツリーを検討してみます。
ビヘイビアツリーは、一般的には敵キャラクターのAIなどで使用されるものです。
ですが、本質的にはただの「現在の状態を決定する仕組み、グラフ」ですから、AI以外に適応できてもおかしくないでしょう。
ちなみに、ビヘイビアツリーはUnreal Engine4にはデフォルトで入っていまして、その解説リンクを貼っておきます。
Unityでビヘイビアツリーを使うには?
自作するのは、できなくはないですがとても大変なので、Asset Storeから入手しましょう。
おそらくUnityのビヘイビアツリーのアセットで一番有名なのはBehaviour Designerでしょう。
が、今回はこちらのアセット
Arbor3を使用してみます。
Arbor3について
Arbor3は、Behaviour Designer同様ビヘイビアツリーを扱えるアセットです。
ちなみにステートマシンも扱えて、ステートマシンの中にビヘイビアツリー、みたいなこともできるようです。
ステートマシンとビヘイビアツリーの組み合わせは昨今のAI作成ではメジャーなようですから嬉しいですね。
また、日本語使用者としてはうれしいことに、作者の方が日本語使用者で、公式のマニュアルが日本語で書かれています。
サンプルが充実しているわけではないので、これ読めば完璧! というほどではないのですが、自分で調べながら動ける人なら十分有難いです。
ちなみに、Unity Japan公式がArbor3の紹介と使い方の説明をしています。
今回の記事ではArbor3やビヘイビアツリーの解説は行わないのですが、
1つ解説記事を紹介しておきます。
Arbor3でアニメーションの遷移を管理してみる
こちらの公式アセットを使用し、プレイヤーキャラクターとして操作するユニティちゃんのアニメーションを、Arbor3のビヘイビアツリーで作ってみましょう。
ParameterContainerを作る
Arbor3で使用するパラメータを外部から操作するときは、ParameterContainer
を使います。
ParameterContainer
は、Arbor3のエディタを開くと出てるパラメータ一覧です。
[SerializeField] private BehaviourTree _tree;
_tree.parameterContainer.SetFloat("パラメータ名", value);
のようにスクリプトからアクセスできます。
また、コンポーネントとしても追加でき、こちらを参照することもできます。
好みで使用してください。
[SerializeField] private ParameterContainer _arborParameterContainer;
_arborParameterContainer.SetFloat("パラメータ名", value);
ツリーを作る
これで、左右移動とジャンプのアニメーション制御が完成しています。
ビヘイビアツリーについて全く知らないとちょっと説明しづらいのですが、簡単に言うと
左から順番にノードを探索し、条件に合ってる最初の振る舞いノードを採用する
という感じで動きます。
今回の図で言うと、紫のノードが振る舞いノードで、他はただの分岐です。
(分岐にも条件判定を入れて、地上と空中のアニメーションをざっくり分けています)
AIの作成であれば、紫のノードのところが、敵に近づくとか逃げるとか攻撃するとかになりますが、
今回は全ての振る舞いノードが、ただのアニメーション再生になっています。
具体的には、Arbor3にデフォルトで入っている、AnimatorCrossFade
というノードです。
ノードを作る
一番左端の振る舞いノードを見てみましょう。
ここでは、水平方向の速度が4を超えていたら、Unitychan_Run
を再生することになっています。
さらに、このノードの前にSelectorノードが存在し、ここでは接地しているかを確認しています。
Unitychan_Run
のノードは一番左ですから、振る舞いノードの中では最初にチェックされます。
なので
- 接地している
- 水平方向の速さが4を超えている
の条件を両方満たせば、Unitychan_Run
が再生されるはずです。
同様に、左から順に
- 接地かつ水平の速さが4以上なら
Unitychan_Run
- 接地かつ水平の速さが0.1以上なら
Unitychan_Walk
- 接地なら
Unitychan_Idle
- 垂直の速度が+0.1より大きいなら
Unitychan_Jump_Up
- 垂直の速度が-0.1より小さいなら
Unitychan_Jump_Fall
- 無条件に
Unitychan_Jump_MidAir
となるようノードを並べました。
左側から判定されるので、ジャンプアニメーションに空中かどうかの判定は不要ですし、一番右は無条件で大丈夫なんですね。
もちろん判定を入れても同じ動きにはなります。
あと必要なこと
ツリーはこれで完成しましたが、実際に動かすには他にやることがあります。
条件分岐用のパラメータを、前述のParameterContainer
を通して渡しましょう。
方法はなんでもいいですが、自分はUniRx
のReactiveProperty
を使って、Rigidbody
や、接地判定コンポーネントから状態変更の通知が来たら即座にParameterContainer
に反映されるようにしてみました。
IObservable<bool> _isGround;
IObservable<Vector2> _velocity;
_isGround.Subscribe(value => paramContainer.SetBool("IsGround", value));
_velocity.Subscribe(value => paramContainer.SetFloat("HorizontalSpeedAbs", Mathf.Abs(value.x)));
_velocity.Subscribe(value => paramContainer.SetFloat("VerticalSpeed", value.y));
(こんな感じ)
また、反転も実装しましょう。反転はアニメーションとは無関係に実装すると良いでしょう。
カメラもCinemachineとか入れて楽して完成。
結果
ビヘイビアツリーでアニメーションの設定ができました。
ステートマシンだと2ステート間の関係を考える必要がありましたが、
ビヘイビアツリーならばその状態の条件だけを考えてノードを並べればよく、スッキリした構造を作ることができます。
アニメーションが10個とかになればその違いはより顕著になるでしょう。
改善できる点
状態管理用のパラメータや、再生するアニメーションは文字列で指定しているので、ここはもうちょっと何とかしたいです。
パラメータ管理だけに専念するクラスを分離すればそこまで問題ではないのですが、やっぱりちょっと抵抗感があります。
パラメータは、パラメータ管理クラスを自動生成したりでしょうか。
Animatorコンポーネントも不要にしたいところですね。