【Godot4.3】LimboAIを使用してビヘイビアツリーを構成する

GodotEngine

この記事では、Godot Engineのアドオン「LimboAI」を使用して、ビヘイビアツリーを構成する方法についてまとめていきます。

※同アドオンLimboAIを使ったステートマシンの作成については、前回の記事をご参照ください。

LimboAIの準備

まずはLimboAIのインストールと、作業環境用の設定を確認してきましょう。

LimboAIのインストール

LimboAIは、AssetLibからインストールが可能です。
 ① エディタ上部から[AssetLib]タブを開きます
 ➁ 「Limbo」等で検索をかけ、ヒットしたLimboAIを選択します
 ③ ダウンロードウィンドウのアセット名が、自身の使用しているGodotのバージョンと
   一致していることを確認します。(今回は4.3)
 ④ ダウンロードボタンをクリックし、ダウンロードを開始します。

ダウンロードが完了すると、インストール前の設定ウィンドウが開きます。

デフォルトでLimboAIにはデモが付属していますが、不要な場合はエディタ上部の[<]ボタンからアセットの内容を表示し、[demo/]フォルダのチェックを外します。

その他、競合やインストール先のフォルダに問題が無ければ[インストール]ボタンをクリックします。

インストール時には以下のようにエラーが出力される場合があります。

このような場合は、一度プロジェクトのリロードを行うと、解消できる場合があります。
エディタ上部メニューの[プロジェクト]>[現在のプロジェクトをリロード] から、
リロードを実行し、エラーが出力されず、タブにLimboAIが追加されていればOKです。

LimboAIのインストールが完了すると、エディタのタブに[LimboAI]が追加されます。
LimboAIでは、このタブのエディタでビヘイビアツリーの編集を行っていきます。

LimboAIの設定とフォルダ準備

LimboAIをインストール後、[プロジェクト]>[プロジェクト設定]>[一般]から、LimboAIの設定を確認・編集できます。

注意が必要なのは、ビヘイビアツリーやユーザータスク等のリソースを保存するパスの設定で、
 ・Behavior Tree Default Dir:ビヘイビアツリーの保存先
 ・User Task Dirs:ユーザータスクの保存先

の二つは、使用するプロジェクトのフォルダ構成で不都合がある場合は、作業に入る前にパスを再設定しておくことをお勧めします。

ここで、デフォルトの保存先設定を使用する場合は、参照エラーとならないように、フォルダを作成しておきます。

ビヘイビアツリーの基本

実際に構成の作業に入る前に、ビヘイビアツリーの基本について整理しておきましょう。

ビヘイビアツリー(BT: Behavior Tree)は、ゲーム AI の行動を制御するためのツリー構造 です。
主に敵キャラクターや NPCの行動を 論理的に整理し、管理しやすくする ために使われます。
特徴としては、以下のようなものが挙げられます。

  • 直感的な階層構造で行動を制御できる
  • 条件分岐や繰り返し処理を簡単に表現できる
  • 状態が変わった際に適切な行動を選択しやすい

タスク

ビヘイビアツリーは、特定の行動(処理)や分岐ルールを表す”タスク”という要素で構成されます。
タスクは大別すると、
 実行フローや動作方法を指定する制御タスク(control tasks)と、
 実際のアクション(処理)や、条件の判定を行うリーフタスク(leaf tasks)
の2種類があり、さらに分類すると以下4種類になります。

タスク名概要クラス例
コンポジット
(BTComposite)
・子タスクの実行フローを指定する制御タスク
・複数の子タスクを持つことができる
Sequence, Selector,Parallel
デコレーター
(BTDecorator)
・子タスクの動作方法を指定する制御タスク
・子として持てるのは1つのタスクのみ
AlwaysSucceed, Invert, TimeLimit
アクション
(BTAction)
・実際の処理を実行するリーフタスクPlayAnimation, Wait
コンディション
(BTCondition)
・条件判定をおこなうリーフタスク
・条件の可否によってSUCCESS/FAILUREを返す
CheckVar, InRange

LimboAIでは、BTTask というクラスで、ビヘイビアツリーの構成要素の基礎を提供しており、このようなタスクはBlackboardを使用してデータを共有する仕組みがあります。
(独自のアクションはBTActionクラス、条件はBTConditionクラスを継承して作成を行います)

ビヘイビアツリーの処理手順

ビヘイビアツリーは、階層の上から順に、以下のような手順で処理が進行していきます。

  1. 制御タスクによるフロー指定に従い、フレーム毎にツリー内の処理が実行されていきます
  2. 各タスクでは、仮想メソッド _tick() 内でタスクの処理を実行し、進捗状況を示す下記ステータスのいずれかを返します
    • SUCCESS(成功): タスク完了
    • FAILURE(失敗): タスク失敗
    • RUNNING(進行中): タスク継続中(完了に1ティック以上必要なタスク)
  3. 返されたステータスに応じて、次のタスクへ進むか、現タスクの継続かを決定します。
    →RUNNINGの場合は次のフレームでも同タスクの処理を続行

Sequence と Selector

ビヘイビアツリーの構成において重要なコンポジットタスクとして、SequenceとSelectorがあります。

タスク動作
Sequence子タスクのいずれか1つがFAILUREを返すか、すべてがSUCCESSを返すまで、子タスクを先頭から順に実行する。
→結果としては、すべての子タスクが成功して処理されるか、
 最初にFAILUREが出た時点で中断されるか
Selector子タスクのいずれか1つがSUCCESSを返すか、すべてがFAILUREを返すまで、子タスクを最初から順番に実行する。
→ 結果としては、最初にSUCCESSを返した1つの子タスクのみが処理されるか、
 すべて失敗してFAILUREを返すか

ビヘイビアツリーでは、主に上記コンポジットタスクと、条件判定によってSUCCESSかFAILUREいずれかを返すコンディションタスクを組み合わせて、意思決定の挙動を定義していきます。

LimboAIでのビヘイビアツリー構成

ここからは、LimboAIでビヘイビアツリーを構成する方法についてまとめていきます。
基本的な作業の流れは以下の通りになります。

  1. エージェント(ビヘイビアツリーを適用するノード)にBTPlayerノードを追加
  2. 新規ビヘイビアツリーリソースを作成し、BTPlayerノードへアタッチ
  3. ビヘイビアツリーエディタで各種タスク等を追加し、行動パターンを構築する

BTPlayerの準備

まずはビヘイビアツリーの適用するエージェントとなるノードを準備します。
CharacterBody2Dをルートとして、適当な画像を割り当てたSprite2Dを子に追加してください。

ルートには、とりあえず以下のようなスクリプトをアタッチしておきます。
(ビヘイビアツリーのタスクで移動させるために、move_and_slide()のみ実行しています)

extends CharacterBody2D

func _physics_process(delta):
	move_and_slide()

続けて、同じくルートのノードの子に、BTPlayerノードを追加します。

LimboAI タブを開き、[新規]ボタンをクリックし、新規ビヘイビアツリーを作成します。

Ctrl+S でビヘイビアツリーを保存します。この時、デフォルトで指定される保存先ディレクトリは、プロジェクト設定から変更が可能です。(デフォルトは、res://ai/trees)

作成したビヘイビアツリーリソースを、作成したBTPlayerノードへアタッチします。

ビヘイビアツリーの構成

ここからは、実際にビヘイビアツリーを構成していきます。
まずはルートとなるSelectorの子に、Sequenceを追加します。
ルートのSelectorを選択し、エディタ右側のComposites内にあるSequenceボタンをクリックします。

追加したタスクは、インスペクターの Custom Name で任意の名前を付けることができます

ビヘイビアツリーは基本的に上記のような形でタスクを追加していき、制御フローを構築していきます。

カスタムタスクの作成

自作のタスクを追加したい場合は、ビヘイビアツリーエディタ上部の[New Task]ボタンから作成を行います。

この時、デフォルトで指定される保存先ディレクトリは、プロジェクト設定から変更が可能です。(デフォルトは、res://ai/tasks)

ユーザータスクディレクトリ内の各サブディレクトリは、カテゴリとして扱われます。そのため、「motion_and_physics 」というサブディレクトリを作成すると、そのディレクトリ内のカスタムタスクは自動的に 「Motion And Physics 」に分類されます。

タスクの作成が完了すると、以下のような状態でgdscriptファイルが開きます。
カスタムタスクを作成する場合、以下基本クラスのいずれかを継承します。 (例はBTAction)
 各種タスクの基本クラス:BTAction、BTCondition、BTDecorator、BTComposite

テスト用に以下のようなコードを作成します

@tool
extends BTAction

# タスク用のパラメータ―
@export var move_parameter : int


# タスクの初期化時(タスクの最初の実行ティックの前に 1 回だけ)呼び出される仮想メソッド
func _setup() -> void:
	print("タスク: ", custom_name, "の初期化処理を実行します")


# タスクが開始された(RUNNIG ステートではない)時に呼び出される仮想メソッド
func _enter() -> void:
	print("タスク: ", custom_name, "が開始しました")


# タスクが終了した(_tickがSUCCESSまたはFAILUREステータスを返した)後に呼び出される仮想メソッド
func _exit() -> void:
	print("タスク: ", custom_name, "が終了しました")


# タスク実行時に呼び出され、Statusを返す仮想メソッド。
func _tick(delta: float) -> Status:
	agent.velocity.x = move_parameter	# エージェントのプロパティを編集
	return SUCCESS


# custom_nameが未設定の場合に、タスクの表示名を生成するために呼び出される仮想メソッド
# -> 使用する場合は、スクリプトファイルの先頭行に @tool の記述が必要になります
func _generate_name() -> String:
	return "Move Test Task"


# エディタ上で警告メッセージを表示するための仮想メソッド。
# -> 使用する場合は、スクリプトファイルの先頭行に @tool の記述が必要になります
func _get_configuration_warnings() -> PackedStringArray:
	var warnings := PackedStringArray()
	
	if move_parameter == 0:
		warnings.append("移動用パラメーターが未設定(=0)です。")
	
	return warnings

作成したカスタムタスクは、デフォルトで[その他]カテゴリ内に追加されているため、
Sequenceの子に作成したカスタムタスクを追加します。

_generate_name()と_get_configuration_warnings()の記述によって、タスクの表示名と警告メッセージの表示があることをかくにんできます。

追加したタスクを選択し、パラメータを設定します。
また、設定を行うと、警告メッセージの出力も消えることを確認できます。

F6でシーンを実行するとエージェントとなるノードが右方向へ移動していく様子と、コンソールの出力から各仮想メソッドの実行タイミングを確認できます。

↑タスク名が空欄であることから、_generate_name() はcustom_nameを上書きしているわけでは言ことが確認できます。

また、実行中は デバッガー > LimboAI を開き、アクティブな動作ツリーを選択することで、ビヘイビアツリーの動作状況を確認することができます。

Blackboard によるデータ共有

ビヘイビアツリーには、Blackboardという機能があります。
これは、異なるタスクやステート間でデータを共有するためのもので、ビヘイビアツリーやステートマシンの各インスタンスごとに専用のBlackboardを持ち、各タスクやステートが変数の保存や取得をおこなうリポジトリとして機能します。

以下は、ブラックボードにおける変数の基本的な取り扱いの例です。

extends BTAction

@export var bb_test_var : StringName = &"bb_test_var"

func _tick(delta) -> Status:
	# ブラックボードに変数を作成・初期化 (既存の変数を指定した場合は上書き)
	blackboard.set_var(bb_test_var, 100)
	
	# ブラックボードの変数を取得
	print("ブラックボードの変数test_bb_varの値:",blackboard.get_var(bb_test_var))
	
	# 親スコープを含め、ブラックボードに指定した変数が存在するかを確認
	if blackboard.has_var(bb_test_var):
		print("変数test_bb_varはブラックボード内に存在します。")
		
	return SUCCESS

重要な点として、StringNameを格納する変数[bb_test_var]のように、変数名の末尾に[_var]をつけることで、LimboAIはそれをブラックボード用の変数として認識し、エディタから各種操作や警告文の表示が可能になります。

(左は末尾が_varでないもの、右は末尾が_var であるもの)

_var をつけた場合に表示される+ボタンからは、BlackboardPlanの管理画面を開くことができます。
(BTPlayer>Blackboardリソース>EditBase>Manage からも同編集画面に遷移できます)

LimboAIには、Blackboard変数の参照や値の保持をより簡単におこなうために、”BB~”から始まる専用のデータ型も用意されています。
例: BBIntBBBoolBBStringBBFloatBBNode など

詳細は以下クラスリファレンスをご参照ください。

BBParam — LimboAI 1.0 documentation

BlackBoardPlan

BlackboardPlanは、Blackboardを初期化する方法を指定するための仕組みで、
初期化に必要なデフォルト値(変数)、型情報、ヒントに関する設定が可能です。

追加した変数は、BTPlaye>BlackboardPlanのインスペクターから、初期値やバインド設定が可能です
(ここでの設定値はBTPlayerシーン固有で、同じビヘイビアツリーを使う他シーンに影響はしません)

また、追加済みの変数をタスクでインスペクター表示させると、右側にチェックマークがつきます。

Godotにおけるデータバインドとは、特定の変数が変更されたとき、それに関連する処理を自動的にトリガーする仕組みです。
主に指定の変数が関連する他のプロパティ等へ同時に値を反映するような場合に使用します。

おわりに

この記事では、LimboAIを使用したビヘイビアツリーの構成方法について、基本的な内容に絞ってまとめました。
より詳細な内容や、使用した各種ノード・メソッド等で使用可能なオプションについては、以下公式ドキュメントページ等をご参照ください。

LimboAIによるステートマシンの作成については以前の記事でまとめています。
よりしければこちらもご参照ください。

おすすめの本①
おすすめの本➁(Godot)
GodotEngine
ひらまめ(hiramame)をフォローする
ひらまめゲーム制作研究室

コメント

タイトルとURLをコピーしました