LoginSignup
4
2

More than 1 year has passed since last update.

[UE] BehaviorTree の再利用について考える

Last updated at Posted at 2021-12-01

BehaviorTree を BT、Blackboard を BB と略します

この記事で伝えたいこと

  • BT 上で定数が定義できるよ
  • BT がデータドリブンな設計になるよ、サブツリーの再利用性が上がるよ

BT_Test_1-Behavior Tree.png
まず用意したのはこれ。Target に向かって移動を続ける、単純な思考ツリー。
Acceptable Distance | Acceptable Radius どちらも 300.0 という数値が入ってますが、これを変数化して共用にできないか、というのが動機の始まりです。


グラフで使用した BT ノードの説明BTT_MoveTo ... 目標に対して AI 移動する。
BTT_MoveTo-EventGraph.png
BTD_CloseEnough ... 対象との水平距離が N 以下であるか判定する。
BTD_CloseEnough-PerformConditionCheckAI.png

Blackboard で変数化する

79d41bb2df076e38e321a8775a91bc96.png
変数定義なら Blackboard があるじゃない?
でもデフォルト値はセットできないんだよな。BP ではセットできるんだけどな~~~。
_人人人人人人人人人人人人人人人人人_
> そうだ、Service でセットしよう  <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y  ̄

Service ノードを作成する

BTS_ScopedBBEntry-EventGraph.png
Activation AI で値をセットして、Deactivation AI でリセットする。処理がノードを通ると子ノード全域で値が有効になります。
BT_Test-Behavior Tree.png
親ノードにサービスをくっつけて、Distance キーに値をセットするように変更。
BTT_MoveTo-EventGraph_0.png
既存の実装も Blackboard Key Selector を使うように変更します。

Task や Decorator でもよくない?

  • ノードの処理開始/終了時で値を管理したいので、タスクで初期化するだけだと NG(兄弟、同じ階層ノードで運用する、とした時に値がリセットされてないと使いにくい)。
  • デコレータとはノードの見た目を分けたかった。
  • サブツリー化したときに内包ノードを表示したくない(デコレータはサブツリーのトップ階層が親階層に表示される性質がある)。 68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f34313130352f34386432363733302d626433342d386164652d366432352d3233643834663431633539362e706e67.png

BT のサブツリー化

処理が多くなってきたときに一つのグラフで管理してると、果てしなく長くなり大変です。
そんな時はまとめてサブツリー化とかよくやります。バトルとパトロールとかね、もっと細かい単位でも。
BT_Test-Behavior Tree_0.png
先ほどのツリーを修正しましょう。中身をサブツリー化して RunBehavior で実行する。サービスは親タスクにアタッチします。ついでに Distance を 500 に変更しました、これで間合いを遠くにとるように。

サブツリーの共通化、再利用

ReferenceViewer_BT_Child_0.png
サブツリーは他のツリーでも再利用、BT ごとに変数を設定することができるようになりました。セットできる変数は float に限りません。bool や GameplayTag などセットして AI 思考のスイッチも可能になります。なかなか夢が広がったのでは?

値入力のインターフェースが使いにくい問題

上で実装した例だと、Service と値定義が対になっていて、複数をセットするには複数アタッチする必要があります。型も予め Service 側で固定する必要があり、Enum などは用途ごとに Service が増えていきそうです。
856724cf7abc874f91ffb67d2bacaab1.png
汎用的に配列で作った例。BlackboardComponent の SetValueAs~ と同様に用意する感じ。

C++ で BTNode を作るときの TIPS

ここからはオマケで、C++ で FBlackboardKeySelector を扱うときの TIPS をいくつか紹介。まぁエンジンで用意されてる BTNode たちを見るのが一番確実ですけど。。

KeySelector の型制限

BP で KeySelector を定義すると、BT 側で BBKey を選択するとき全てのキーが表示されてしまいます。キーの種類が増えると結構不便ですが、C++ で追加した KeySelector であれば型を制限することができます。

ActorToCheck.AddObjectFilter( // Actorのみ
    this, GET_MEMBER_NAME_CHECKED( UBTDecorator_CloseEnough, ActorToCheck ), AActor::StaticClass() );
AcceptableDistanceKey.AddFloatFilter( // floatのみ
    this, GET_MEMBER_NAME_CHECKED( UBTDecorator_CloseEnough, AcceptableDistanceKey ) );

KeySelector の None を許可

こちらも同じく C++ のみで、BBKey に None を指定できるようになります(指定しない場合でもプロパティ欄のリセットボタンを押せば None にできますが、カーソルが外れたときに None 以外が自動でアサインされます)。
利点は、キーによる指定とデフォルト値の二通りの運用ができることです。

// キーがセットされていれば BB から値を取得、そうでなければデフォルト値を使う
float Distance = AcceptableDistanceKey.IsSet() ?
    BlackboardComp->GetValueAsFloat( AcceptableDistanceKey.SelectedKeyName ) :
    AcceptableDistance;

BlackboardKeySelector 変数の初期化

C++ で定義した KeySelector は InitializeFromAsset で全て初期化する必要があります。
ResolveSelectedKey を処理しないと値が取得できない。

void UBTDecorator_CloseEnough::InitializeFromAsset( UBehaviorTree& Asset )
{
    Super::InitializeFromAsset( Asset );

    UBlackboardData* BBAsset = GetBlackboardAsset();
    if ( ensure( BBAsset ) )
    {
        ActorToCheck.ResolveSelectedKey( *BBAsset );
        AcceptableDistanceKey.ResolveSelectedKey( *BBAsset );
    }
}
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2