【Unity】公式の新パッケージ「Behavior」を使ってみた
こんにちは、Unityユーザーの皆さん!
この記事では、Unity公式がリリースした新しいビヘイビアツリーパッケージ「Unity Behavior」を使ってみたので、概要や使ってみた感想を紹介します。
対象読者
これから新たにNPC・CPU挙動を考えているUnityユーザー向けに書いています。後述の感想で詳しく述べますが、すでに完成している・完成途中のシステムに組み込もうとすると大変なので、まっさらな状態の時に導入するのを強くお勧めします!(筆者はうまく統合できませんでした。)
ビヘイビアツリーとは?
筆者のプロジェクトで作ろうとしているビヘイビアツリー
ビヘイビアツリーは、AIやキャラクターの挙動を制御するためのノードベースのフレームワークです。各ノードが特定のアクションや条件分岐を表し、それらを組み合わせて柔軟なロジックを構築します。主にゲーム内のAI挙動やシナリオ制御に用いられます。
Behaviorパッケージとは?
Unity Behaviorは、Animator Controllerと同様のGUIツールで、ノードベースのグラフを使ってキャラクターやオブジェクトの挙動を制御できるシステムです。Animator Controllerがアニメーションの状態をノードとして繋ぐように、Unity Behaviorでは行動や条件分岐をノードとして繋ぐことで、プログラムの実行フローを視覚的に設計できます。
シーンでの使用方法もAnimatorと同じような感覚です。
- AnimatorにAnimator Controllerを設定するように、Behavior AgentにBehavior Graphを設定して動作させます。
- 「Sequence」や「Selector」などのノードを使って挙動のフローを設計し、条件分岐やアクションを組み合わせることで、AIの挙動やカットシーンの制御を効率的に実装できます。
セットアップ方法
- パッケージのインストール
-
- Unityの「Package Manager」からUnity Registry内のBehaviorを追加します。
-
Behavior Graphの作成
- プロジェクトビューで右クリック → 「Create」→ 「Behavior」→ 「Behavior Graph」を選択。
-
Behavior Agentの設定
- シーン内のGameObjectにBehavior Agentを追加し、作成したBehavior Graphをドラッグ&ドロップで設定すれば準備完了!
公式のデモもあるので、実際にどんなふうに動くかみることができます。
注意!:デモはプロジェクトがそのまま入っているのでもしプロジェクトに同名のフォルダがあるとそこにファイルが入ってしまいます!また、パッケージの依存も多いので、アップグレード・ダウングレードが起こってもいいか慎重に確認してください。
Blackboard Variablesについて
Behavior Graphの中核となるのがBlackboard Variablesです。これにより、複数のノード間でデータを共有し、外部から変数を操作することで動的な挙動を実現できます。
対応するデータ型と制約
Blackboard Variablesは以下のような代表的な型に対応しています:
- 基本型: Float, Int, Bool, String
- ベクトル型: Vector2, Vector3, Vector4
- Unityオブジェクト: GameObject, Transform
- カスタムクラス型: MonoBehaviourを継承した独自のクラスのインスタンスも利用可能です。これにより、自作のスクリプトやコンポーネントを直接参照できます。
ただし、以下のような制約があります:
- Character Controller型など、使えないコンポーネント型がある: Blackboard Variablesに直接保持することはできません。それぞれのノードでGetComponentしてキャッシュしてもいいのですが、設計としてあんまり綺麗じゃない気もします。
- インターフェース型は対応していない:シリアル化できるクラスを前提にしてるため、インターフェースをBlackboard変数として定義することはできません。
設定方法
-
変数の追加
- Behavior Graphエディタ内のBlackboardパネルで「+」ボタンをクリックし、必要な型の変数を追加します。
-
ノードとの連携
- ノードのプロパティでBlackboard Variableを参照することで、挙動を制御可能です。例として、敵の移動速度や攻撃間隔を変数で調整できます。
-
Black Board VariableのExposeとShared
-
Expose(公開)
- 有効にするとBehavior GraphAgentがついているGame ObjectのInspector上で変数を操作することができます。そこで操作された変数はOverrideという印がつき、Behavior Graphで設定されている値を上書きをします。
-
Shared(共有)
- 設定を使えば複数のBehavior Graph間でデータを共有可能です。
- 例えばキャラクターが死亡しているかを調べるisDeadというBooleanがSharedだった場合、あるキャラクターがisDeadをtrueにすると、同じBehaviorGraphを設定している他のAgentでもisDeadがTrueになります。
-
Expose(公開)
Blackboard変数の外部操作
Blackboardの変数は、BehaviorGraphAgentコンポーネントのBlackboard Variablesから値を変更できます。
また、スクリプトから以下のように変数の値を変更することも可能です:
using Unity.Behavior;
using UnityEngine;
public class Example : MonoBehaviour
{
[SerializeField]
private BehaviorGraphAgent agent;
private bool isDead;
private void Start()
{
// Blackboardの値を書き換える
// 変数名をstringで管理するので、誤字がないように注意
agent.BlackboardReference.SetVariableValue("WaitSec", 10.0f);
// Blackboard Variableの変数の値を取得
agent.BlackboardReference.GetVariableValue("IsDead", out isDead);
}
ノードの種類と代表的なノード
Behavior Graphではさまざまな種類のノードが提供されており、柔軟な挙動を構築することができます。以下は代表的なノードの種類とその役割です:
1.Action Node
特定の動作や処理を実行します。NPCやCPUに持たせたい機能が簡単に作れます。
- Patrol:チェックポイントのTransformのリストを渡せばそのチェックポイントを巡回してくれます。
- Find Closest With Tag: 指定のタグを持つ最も近いGameObjectを探す。アイテムとか敵を探すのに
2. Flow Node
挙動の流れを管理します。
- Parallel: 複数のノードのブランチを同時に実行します。
3. Events Node
イベントを発生させたり、発生を待つことができます。イベントには引数を持たせて、その引数の値によって条件分岐させることもできるみたい。Event ChannelというScriptable Objectを作ってどんなイベントにするかをカスタムできるらしい。あんまりよくわからなかったので、もしご存知の人がいたら教えてください…
4. Subgraphs node
Behavior Graphの中にBehavior Graphを入れ込んんで、子のBehaviorGraph(Subgraph)の状態に合わせて挙動を設定できます
カスタムアクションノードの作り方
Behavior Graphに独自のカスタムアクションノードを追加することで、特定の挙動をさらに細かく制御できます。
-
新しいスクリプトを作成する
- プロジェクトビューで右クリック → 「Create」→ 「C# Script」→ スクリプト名を入力(例:
CustomActionNode
)。
- プロジェクトビューで右クリック → 「Create」→ 「C# Script」→ スクリプト名を入力(例:
-
必要なクラスを継承する
using System; using Unity.Behavior; using Action = Unity.Behavior.Action; using Unity.Properties; [Serializable, GeneratePropertyBag] [NodeDescription(name: "Example", story: "Agent compares [IsDead] and calls [Health] methods", category: "Action", id: "324b9df9c9a4a578f28af81b23e70197")] public partial class ExampleAction : Action { [SerializeReference] public BlackboardVariable<bool> IsDead; [SerializeReference] public BlackboardVariable<Health> Health; protected override Status OnStart() { //Status.Failureで失敗として次のノードへ if(Health == null) return Status.Failure //Status.Runningでこのノードにとどまり、OnUpdateが走り続ける else return Status.Running } protected override Status OnUpdate() { //Status.Successで成功として次のノードへ return Status.Running; } protected override void OnEnd() { //ノードを抜ける時の処理 } }
-
ノードのロジックを実装する
-
OnStart
: ノードが開始されたときに一度だけ呼び出されます。 -
OnUpdate
: ノードの実行中に毎フレーム呼び出され、挙動の状態(Success, Failure, Running)を返します。 -
OnStop
: ノードが終了したときに呼び出されます。
-
使ってみての感想
Blackboard Variablesの初期化が大変
シーンの中のBehavior Graph Agentが参照するBlackboard VariablesはExposedにしてInspectorで初期化しない限り、Blackboardに保存されている値に初期化されます。
そこで問題なのが初期化のタイミング。おそらくUnityライフサイクルのStart()ぐらいでBlackboardに保存している値を読んで初期化をしています。しかしそれよりも早い段階でBehavior Graphの外から値を初期化する場合、デフォルトの値に戻されてしまいます。依存性の注入やファクトリ・ビルダーなどのデザインパターンで組み立てようとすると、このタイミングの差に悩まされることになります。
考えられる対策
-
Behavior Graph Agent コンポーネントにInpsectorで渡す
- 必要なコンポーネントが多いと全部設定するのがめんどくさい…!
-
GetComponentでノードの中で動的にインスタンスを取得する
- その時にインスタンスが用意できてるか保証できない…!
そんなこんなでたくさん色々調べたんですが、自分のプロジェクトだと、すでにキャラクターの生成のフローがガチガチに固まっていたため、うまく活用することができませんでした…
ただ、これから新しくNPC,CPUを作るんだ!という人にはとてもわかりやすくて強力なツールなので、そういう人はぜひ触ってみるのをお勧めします!
Discussion