ノートの端の書き残し

UnityやらC#やら。設計が得意かもしれない。

UnityのAnimationの管理はビヘイビアツリーが便利かもしれない

UnityのAnimatorは使いづらい

Unityでアニメーションの遷移とかの管理、何を使っていますか?
公式はAnimatorControllerを使うように言ってきますし、Unity初心者講座とか見てもそう言ってます。

が、アニメーションが4つ5つ出てきたあたりから管理が怪しくなり、ステートの数が増えるにつれて、遷移の可能性は級数で増えていくのでいつしか地獄の様相を呈してきます。

zenn.dev

これはUnityのAnimatorControllerがダメというより、
そもそもステートマシンというのが、ステートの数が多いと破綻するものなのです。
個人的な意見だと、ステートが3つまでなら問題無くて、5つを超えたら別の方法を検討した方がいい、と思っています。
アニメーションの種類は、多くのケースで5つは超えるでしょうね。

ビヘイビアツリー

というわけで、ステートマシンの替わりとしてビヘイビアツリーを検討してみます。
ビヘイビアツリーは、一般的には敵キャラクターのAIなどで使用されるものですが、本質的にはただの「現在の状態を決定する仕組み」ですから、AI以外に適応できてもおかしくないでしょう。

ちなみに、ビヘイビアツリーはUnreal Engine4にはデフォルトで入っていまして、UEの解説リンクを貼っておきます。

historia.co.jp

Unityでビヘイビアツリーを使うには?

自作するのは、できなくはないですがとても大変なので、Asset Storeから入手しましょう。

おそらくUnityのビヘイビアツリーのアセットで一番有名なのはBehaviour Designerでしょう。

assetstore.unity.com

が、今回はこちらのアセット

assetstore.unity.com

Arbor3を使用してみます。

Arbor3について

Arbor3は、Behaviour Designer同様ビヘイビアツリーを扱えるアセットですが、ステートマシンも扱えます。
また、日本語使用者としてはうれしいことに、製作者の方が日本語使用者で、公式のマニュアルが日本語で書かれています。

arbor.caitsithware.com

サンプルが充実しているわけではないので、これ読めば完璧!というほどではないのですが、自分で調べながら動ける人なら十分有難いです。

ちなみに、Unity Japan公式がArbor3の紹介と使い方の説明をしています。

www.youtube.com

丁寧な解説

qiita.com

Arbor3でアニメーションの遷移を管理してみる

こちらのアセットを使用し、ユニティちゃんのアニメーションをArbor3のビヘイビアツリーで作ってみましょう。

unity-chan.com

ParameterContainerを作る

Arbor3で使用するパラメータを外部から操作するときは、ParameterContainerを使います。
ParameterContainerは、Arbor3のエディタを開くと出てるパラメータ一覧です。
f:id:u_osusi:20211218235131p:plain

BehaviourTree _tree;
_tree.parameterContainer.SetFloat("パラメータ名", value);

のようにスクリプトからアクセスできます。

また、コンポーネントとしても追加でき、こちらを参照することもできます。
好みで使用してください。

f:id:u_osusi:20211218234935p:plain

ツリーを作る

全体像はこんな感じになっています。
f:id:u_osusi:20211218234854p:plain ビヘイビアツリーについて全く知らないとさすがに説明しづらいのですが、簡単に言うと、
左から順番に見て行って、条件に合ってる最初の振る舞いノードを採用する。
という感じです。
今回は全ての振る舞いノードが、ただのアニメーション再生になっています。

ノードを作る

一番左のノードを見てみましょう。
f:id:u_osusi:20211218235607p:plain

ここでは、水平方向の速度が4を超えていたら、Unitychan_Runを再生することになっています。
さらに、このノードの前に、Selectorノードを挟んでおり、ここでは接地しているかを確認しています。
f:id:u_osusi:20211218235659p:plain

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を通して渡しましょう。
方法はなんでもいいですが、自分はReactivePropertyで過不足なく反映させるようにしました。

IReadOnlyReactiveProperty<bool> _isGround;
IReadOnlyReactiveProperty<Vector2> _velocity;
_isGround.Subscribe(v => paramContainer.SetBool("IsGround", v));
_velocity.Subscribe(v => paramContainer.SetFloat("HorizontalSpeedAbs", Mathf.Abs(v.x)));
_velocity.Subscribe(v => paramContainer.SetFloat("VerticalSpeed", v.y));

また、反転も実装しましょう。反転はアニメーションとは無関係に実装すると良いでしょう。

結果

f:id:u_osusi:20211219002148g:plain

ビヘイビアツリーでアニメーションの設定ができました。
ステートマシンに比べて、6つの状態を管理しているにしてはシンプルなツリーではないかと思います。
ステートマシンだと、ジャンプで上昇中に坂に接地したらとか、2ステート間の関係を考える必要がありましたが、
ビヘイビアツリーならばその状態の条件だけを考えればよく(まぁ本当に考えないわけではないですが、少なくとも記述の上では現れないので)、スッキリした構造を作ることができます。
(正直思ったよりもビヘイビアツリーでやってみるのがいい感じでした。)

改善点

状態管理用のパラメータは文字列で渡しているので、ここはもうちょっと何とかしたいですね。
パラメータ管理だけに専念するクラスを分離すればそこまで問題ではないのですが、やっぱりちょっと抵抗感があります。
理想としては、ビヘイビアツリーそれぞれに対応したパラメータコンテナクラスを生成し、文字列ではなくフィールドを直接参照して値を設定できるようにできれば最高ですね。