六畳の享楽

アニメ・ゲームの感想を書いたりするつもりです

Unityビジュアルスクリプティングアセット『Arbor3』を使おう

Arborとは

有限ステートマシン(FSM)とビヘイビアツリー(BT)からなるビジュアルスクリプティングツール。
類似アセットではPlayMaker(FSM)、BehaviorDesigner(BT)あたりが有名。グローバルで見ると、そっちの方がスタンダードなんじゃないかな。
assetstore.unity.com

Arborの特徴

  • 作者が日本人のため日本語サポートが充実している。
  • FSM、BTが単一のアセットで完結するため、たぶん楽。(UI習熟とか連携とか)
  • ちょっと安い。(Playmaker+BehaviorDesignerの$65+$80=$145に対して、Arborは$50)
  • 他ツールに対する機能的な優劣は不明。(Arborしか使ったことないので)

これ以降の対象読者について

上記が刺さった人は、とりあえず公式のチュートリアルを触るなり、よその紹介ブログを見るなりして大体の機能イメージを掴むのが良いと思う。無料の試用版も公開されているので環境は整っている。
これ以降の対象読者は、チュートリアル相当の内容は一通りなぞり、これから実際に使おうという人になる。詳細な機能にまで突っ込んだ話になっているので、初心者が見てもたぶん全くわからないと思う。
世間のアセット紹介によくあることだが、チュートリアルより先の実際の運用に関する情報が極端に少ない。ミニゲーム制作程度ならそれで十分かもしれないが、それ以上の規模のゲームへそのまま適用するにはちょっと心許ない。そんな思いから、以降の段ではより実践的な運用上の心構えやTipsを紹介したい。

ビジュアルスクリプティングツール使用時の心構え

スクリプトを書こう

ビジュアルスクリプティングツールを使う目的は、処理の見通しを良くして増改築をしやすくすることにあると思ってる。なので、死んでもコード書かないマンと化して既存のスクリプトを大量に連ねて複雑怪奇な実装をするのはあまり良くない。処理の組み換えが生じ得ない粒度であれば、さっさと専用スクリプトを書いてしまったほうが幸せになれると思う。

FSMとBTを使い分けよう

FSMは非常に柔軟で何でもできる反面、可読性が落ちやすい。ステートの接続数が簡単に爆発していく。それに対して、BTは木構造と優先度によってフロー制御に対して強い制約を設けるかわりに見通しの良さや増改築のしやすさを得られる。なので、将来的に組み替えたりするかもな~という感じの行動選択のおおまかなフローはBTで作って、その先のひとまとまりの挙動はより柔軟なFSMで作るのが良いと思う。
弊サークルにおけるザコ敵の行動AIを例に取ると、索敵、追跡、逃走、攻撃などの行動選択まではBTで作っておいて、それぞれの行動の詳細な処理(アニメーション制御、移動制御、効果音、攻撃判定etc.)はFSMで作っている。

グラフを階層化しよう

グラフを階層化すると、より見通しが良くなって複雑な挙動も作りやすくなる。先程のBTからFSMへの階層化もその一例。

Arbor使用時のTips

参照グラフを使おう?

Arborでは階層化のやり方として、親グラフが直接子グラフを持つパターンと、親グラフが外部のグラフを子グラフとして参照するパターンがある。(グラフの階層化 | Arbor Documentation
出来ることにはそれほど差が無いように思われるが、個人的には以下の理由でほぼ後者を使っている。

  • 複数の親グラフから共通のグラフを参照できるため、処理の共通化ができる。
  • グラフの中身を表示するまでに必要なクリック数が少ない。(前者のような直接の子グラフでは、一度親グラフを開いてから辿る必要がある。ツリー表示がなく直接の親子間でしか行き来ができないため、階層が深まるほどしんどい。)

普段から呼び出し元が変わりうることを考慮してスクリプトを書く必要があるものの、こちらの方が便利だと思う。

子グラフのヒエラルキーに気をつけよう

階層化において、直接子グラフを持つ場合は子グラフは親グラフと同じGameObjectにアタッチされていると思えば良い。これに対して、外部グラフを参照する場合は親グラフがアタッチされたGameObjectの子Objectにアタッチされる。
なので、普段からこれを踏まえてヒエラルキー上の位置に依存しないスクリプトを書くべき。nodeGraph.rootGraphプロパティによって親グラフを取得できるので、親グラフがアタッチされたGameObjectの別コンポーネントを取得するときなどはこれを経由すればいい。

FlexibleFieldを使おう

普段のように「public int」や「[SerializeField]int」といった形で変数を定義すると、その変数はインスペクタから設定できるようになるものの、ベタ書きするしかなく柔軟性に欠ける。
これに対して、ArborではFlexibleFieldという便利なものが用意されている。FlexibleFieldとして定義すると、インスペクタから変数を設定する際に「定数ベタ書き」「パラメータ」「データスロット」の3通りから選べるようになり、ノード間の連携が可能になる。(データフロー | Arbor Documentation
スクリプトを自作する際には、とりあえずFlexibleFieldとして定義しておけばいいと思う。後から直すの面倒なので……

パラメータ定義方法を使い分けよう?

パラメータの定義方法には、ParameterContainerを使うパターン(ParameterContainer | Arbor Documentation)と、グラフに直接定義するパターンの2つがある。(グラフの階層化 | Arbor Documentation)この使い分けについては未だに正解がよくわからないので、僕の使い分けを紹介する。
グラフに直接定義するパラメータにはSet、Getの権限設定ができる。そのため、親子グラフ間の引数の受け渡しに使うパラメータであればこちらを使うことになる。グラフ内でのみ有効な内部変数が欲しければ、Set、Get両方とも無効化すれば良い。
一方で、ParameterContainerを使うとパラメータを普段のインスペクタビューから簡単に閲覧・編集できるので、デザイナー向けの調整パラメータなんかはこちらを使うようにしている。

ちょっと困っていること

割り込み発生時のお片付け

FSM、BTいずれにおいても、割り込み処理というものがある。(例:攻撃挙動中に殴られると怯み挙動に移行する)このような割り込みによってステートを抜けたときのお片付け処理をスマートに記述できなくてちょっと困っている。
一応、それぞれのノードを抜けたときの処理を記述できるようになってはいるが、もう少し大きい粒度で記述できたらなあと思っている。(サブグラフ抜けたら、くらいの粒度)