Unreal Engine 4におけるAIシステムの構成
UE4でのAIは主に「ビヘイビアツリー(Behavior Tree)」と「ブラックボード(Blackboard)」を用いて構築される。AIの意思決定ロジックはAIControllerによって管理され、その結果がブラックボードのキーに書き込まれる。ビヘイビアツリーはこの値を参照し、ノード構造に基づいて適切なアクションを実行する。
ビヘイビアツリーの基本ノード構造
- Selector(選択ノード): 左から順に子ノードを評価し、最初に成功したノードで停止する。失敗した場合は次のノードへ進む。条件分岐的な振る舞いに使用される。
- Sequence(順次ノード): 子ノードを左から順に実行し、すべて成功しなければ全体として失敗となる。途中で一つでも失敗すれば以降のノードは実行されない。
タスクノード
タスクはAIが実行する具体的な行動であり、UE4にはMoveToやWaitなどの標準タスクが用意されている。カスタムタスクを作成する場合、C++ではUBTTaskNodeを継承し、ExecuteTaskメソッドをオーバーライドすることで実装できる。パラメータはブラックボード経由で渡すことが一般的である。
デコレータ(Decorator)
デコレータはノードの実行条件を判定する役割を持つ。例えば、PerformConditionCheckAIまたはCalculateRawConditionValueをオーバーライドすることで、特定の条件(例:プレイヤーが視界内か)に基づいてノードの実行可否を制御できる。条件がfalseになると、対応するタスクはスキップされる。
AI感知システム(AIPerception)
AIに「視覚」「聴覚」などの知覚能力を持たせるために、AIControllerにUAIPerceptionComponentを追加する。代表的なモジュールにSightがあり、視認距離や角度、障害物の有無などを設定可能である。
感知情報の取得と処理
感知されたアクターの情報を取得するには、以下のように記述する。
const FAIStimulus* Stimulus = &AIPerception->GetActorStimulus(TargetActor);
if (Stimulus->WasSuccessfullySensed())
{
Blackboard->SetValueAsObject(FName("TargetActor"), TargetActor);
}
これにより、ブラックボードの値が更新され、ビヘイビアツリー内で敵の追跡や回避などの行動を切り替えられる。
C++によるカスタムタスクの実装
アニメーション再生中にタスクを進行中状態に保ちたい場合、非同期処理が必要になる。特に攻撃アニメーション(Montage)の終了を待つケースでは、以下のパターンが有効である。
遅延完了タスクの実装
まずExecuteTaskでEBTNodeResult::InProgressを返し、アニメーション終了時にFinishLatentTaskを呼び出して完了を通知する。
// タスククラス内
FSimpleMulticastDelegate OnFinish;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override
{
AXAI_Character* Character = Cast<AXAI_Character>(OwnerComp.GetAIOwner()->GetPawn());
if (Character)
{
Character->OnAttackMontageEnded.AddDynamic(this, &UXBTTask_Attack::OnAnimationFinished);
Character->PlayAttackMontage();
return EBTNodeResult::InProgress;
}
return EBTNodeResult::Failed;
}
void OnAnimationFinished()
{
UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(GetOuter());
FinishLatentTask(*BTComp, EBTNodeResult::Succeeded);
}
アニメーション終了通知の実装
キャラクター側では、アニメーションインスタンスのOnMontageEndedデリゲートを使用して終了を検出する。
void AXAI_Character::PlayAttackMontage()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
AnimInstance->OnMontageEnded.Clear();
AnimInstance->OnMontageEnded.AddDynamic(this, &AXAI_Character::OnMontageComplete);
AnimInstance->Montage_Play(AttackMontage);
}
}
void AXAI_Character::OnMontageComplete(UAnimMontage* Montage, bool bInterrupted)
{
if (Montage == AttackMontage)
{
OnAttackMontageEnded.Broadcast();
}
}
環境クエリシステム(EQS)の活用
EQSはAI周囲の環境から条件に合致する位置を動的に抽出する機能である。例えば、「敵の周囲5m以内で障害物のない移動可能な位置」などを取得できる。
C++でのコンテキスト定義
カスタムコンテキストクラスを生成し、ProvideContextをオーバーライドして基準点を設定する。
void UEQS_Context_EnemyCenter::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
{
UObject* Querier = QueryInstance.Owner.Get();
AAIController* AIController = Cast<AAIController>(Querier);
if (AIController)
{
AXAI_Character* Pawn = Cast<AXAI_Character>(AIController->GetPawn());
AActor* Target = Pawn ? Pawn->GetCurrentTarget() : nullptr;
UEnvQueryItemType_Actor::SetContextHelper(ContextData, Target);
}
}
UEエディタ上でEQSクエリを作成し、サンプリング範囲やフィルタ(例:Distance Test)を設定することで、円環状のポイント群を生成できる。これにより、AIが敵の周囲を自然に旋回するような行動が実現できる。
サービスタスク(Service)
サービスはビヘイビアツリーの各ノードに周期的に実行される補助処理を提供する。例えば、一定間隔で敵の存在を確認したり、ステータスを監視したりする用途に使う。Tick間隔を設定でき、条件変化の検出に適している。