ue-ai-navigation について
ue-ai-navigation は、UE 5.8 の AI とナビゲーション実装を支援する開発スキルです。コントローラー、ビヘイビアツリー、知覚、経路探索などの API を扱います。
主な機能
- 01UE 5.8 AI API ガイド
- 02AI コントローラーと移動リクエスト
- 03ブラックボードの値と監視
- 04ビヘイビアツリーノードの実装
- 05知覚とナビゲーションのガイド
- 06スマートオブジェクトと群衆システム
活用シーン
- 01カスタムビヘイビアツリータスクの実装
- 02AI の移動と経路クエリの設定
- 03ブラックボードキーによる AI 状態管理
- 04群衆やインタラクティブオブジェクト向けシステムの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-ai-navigationインストール後、Claude Code で/ue-ai-navigationを入力して読み込まれたか確認します。
同梱ファイルreferences/behavior-tree-patterns.mdreferences/eqs-reference.md
詳細
UE AI とナビゲーション
対象エンジン:UE 5.8。以下の API は 5.8 のヘッダーで確認済みです。古い形式は「非推奨 — 使用しないでください」に記載しています。
従来の UE AI は、ブレインコンポーネント(UBehaviorTreeComponent)、UBlackboardComponent、UAIPerceptionComponent、UPathFollowingComponent を所有するサーバー専用の AAIController 上で動作します。ビヘイビアツリー、知覚、EQS は AIModule に、ナビメッシュの生成とクエリは NavigationSystem に、ゲームプレイタスクをラップする BT タスクには GameplayTasks が必要です。Smart Objects(SmartObjectsModule、プラグイン SmartObjects)、ZoneGraph(ZoneGraph、5.8 では実験的機能)、MassAI(5.8 では実験的機能)、NavCorridor(5.8 では実験的機能)は、必要に応じて有効化するプラグインです。使用するものを .Build.cs の PublicDependencyModuleNames に追加してください。例:"AIModule", "NavigationSystem", "GameplayTasks"。
コンテキスト
.agents/ue-project-context.md が存在する場合は読み込んでください(モジュール名、規約、有効なプラグイン、GAS/ネットワーク設定)。ファイルがなくても作業を中断しないでください。
リクエストとコードベースから対象領域を特定してください。妥当な解釈が 2 つあり、それぞれで生成されるコードが異なる場合に限り、質問してください。
| リクエストの内容… | 参照先 |
|---|---|
| ポーンの操作権取得、ツリーの実行、移動リクエスト、フォーカス | AI Controller |
| AI の知識、キーセレクター、オブザーバーの読み書き | Blackboard |
| カスタムタスク、デコレーター、サービス、ノードメモリ | Behavior Tree Nodes |
| 視覚/聴覚/ダメージの感覚、刺激ソース | AI Perception |
| パスクエリ、投影、ランダム地点、インヴォーカー | Navigation and Pathfinding |
| エリアコスト、クエリフィルター、ジャンプ/オフメッシュリンク | Nav Areas, Filters and Off-Mesh Links |
| カバー/側面攻撃/パトロール地点の選択、カスタムコンテキスト | EQS Environment Query System |
| ビヘイビアツリーの代わりに State Tree を使用 | State Tree for AI |
| インタラクション可能なワールドオブジェクト、スロットの確保 | Smart Objects |
| レーンベースの群衆、大量のエージェント | ZoneGraph, MassAI and NavCorridor |
AI Controller
// MyAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Perception/AIPerceptionTypes.h"
#include "MyAIController.generated.h"
class UBehaviorTree;
UCLASS()
class MYGAME_API AMyAIController : public AAIController
{
GENERATED_BODY()
public:
AMyAIController();
protected:
UPROPERTY(EditDefaultsOnly, Category = "AI")
TObjectPtr<UBehaviorTree> BehaviorTreeAsset;
virtual void OnPossess(APawn* InPawn) override;
virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) override;
UFUNCTION()
void HandleTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
};
// MyAIController.cpp
#include "MyAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Navigation/PathFollowingComponent.h"
#include "Perception/AIPerceptionComponent.h"
AMyAIController::AMyAIController()
{
bStartAILogicOnPossess = true;
bStopAILogicOnUnposses = true;
}
void AMyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (BehaviorTreeAsset)
{
RunBehaviorTree(BehaviorTreeAsset); // calls UseBlackboard internally
}
if (UAIPerceptionComponent* Perception = GetAIPerceptionComponent())
{
Perception->OnTargetPerceptionUpdated.AddDynamic(this, &AMyAIController::HandleTargetPerceptionUpdated);
}
}
void AMyAIController::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
Super::OnMoveCompleted(RequestID, Result);
if (UBlackboardComponent* BB = GetBlackboardComponent()) { BB->SetValueAsBool(TEXT("ReachedGoal"), Result.Code == EPathFollowingResult::Success); }
}
void AMyAIController::HandleTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
UBlackboardComponent* BlackboardComp = GetBlackboardComponent();
if (BlackboardComp && Stimulus.WasSuccessfullySensed())
{
BlackboardComp->SetValueAsObject(TEXT("TargetActor"), Actor);
BlackboardComp->SetValueAsVector(TEXT("LastKnownLocation"), Stimulus.StimulusLocation);
}
}
// AIController.h — signatures you will call
EPathFollowingRequestResult::Type MoveToActor(AActor* Goal, float AcceptanceRadius = -1, bool bStopOnOverlap = true,
bool bUsePathfinding = true, bool bCanStrafe = true,
TSubclassOf<UNavigationQueryFilter> FilterClass = {}, bool bAllowPartialPath = true);
EPathFollowingRequestResult::Type MoveToLocation(const FVector& Dest, float AcceptanceRadius = -1, bool bStopOnOverlap = true,
bool bUsePathfinding = true, bool bProjectDestinationToNavigation = false, bool bCanStrafe = true,
TSubclassOf<UNavigationQueryFilter> FilterClass = {}, bool bAllowPartialPath = true);
virtual FPathFollowingRequestResult MoveTo(const FAIMoveRequest& MoveRequest, FNavPathSharedPtr* OutPath = nullptr);
virtual void SetFocus(AActor* NewFocus, EAIFocusPriority::Type InPriority = EAIFocusPriority::Gameplay);
virtual void SetFocalPoint(FVector NewFocus, EAIFocusPriority::Type InPriority = EAIFocusPriority::Gameplay);
virtual void ClearFocus(EAIFocusPriority::Type InPriority);
virtual bool RunBehaviorTree(UBehaviorTree* BTAsset);
bool UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent);
EPathFollowingStatus::Type GetMoveStatus() const; // Idle, Waiting, Paused, Moving
bool HasPartialPath() const;
virtual void SetGenericTeamId(const FGenericTeamId& NewTeamID) override; // IGenericTeamAgentInterface
| 必要なこと | 使用するもの |
|---|---|
| 呼び出しごとにオプションを指定する簡単な移動 | MoveToActor / MoveToLocation — EPathFollowingRequestResult::{Failed, AlreadyAtGoal, RequestSuccessful} を返します |
| 完全な制御(目標アクター + 半径 + フィルター + 部分パス)と結果のパス | MoveTo(FAIMoveRequest, FNavPathSharedPtr*) — MoveId と Code を含む FPathFollowingRequestResult を返します |
| BT/ブラックボードを持たないコントローラーの移動(クリック移動テストなど) | UAIBlueprintHelperLibrary::SimpleMoveToLocation(AController*, const FVector&) |
| 到着への対応 | OnMoveCompleted をオーバーライドするか、動的デリゲート ReceiveMoveCompleted(FAIRequestID、EPathFollowingResult::Type)をバインドします |
各種セッターで FAIMoveRequest を構築します:SetGoalActor、SetGoalLocation、SetAcceptanceRadius、SetUsePathfinding、SetAllowPartialPath、SetNavigationFilter、SetProjectGoalLocation、SetCanStrafe、SetReachTestIncludesAgentRadius。
ポーンで次のように設定します:AIControllerClass = AMyAIController::StaticClass(); AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
Blackboard
| 型 | 取得 | 設定 |
|---|---|---|
| Object | GetValueAsObject | SetValueAsObject |
| Vector | GetValueAsVector | SetValueAsVector |
| Bool | GetValueAsBool | SetValueAsBool |
| Float | GetValueAsFloat | SetValueAsFloat |
| Int | GetValueAsInt | SetValueAsInt |
| Enum | GetValueAsEnum (uint8を返します) | SetValueAsEnum (uint8を受け取ります) |
| Name / String / Class / Rotator | GetValueAsName / …String / …Class / …Rotator | 対応するSetValueAs… |
すべてのアクセサーはconst FName& KeyNameを受け取ります。ClearValue(FName)とIsVectorValueSet(FName)には、名前検索を省略するFBlackboard::FKeyオーバーロードがあります。
#include "BehaviorTree/BlackboardComponent.h"
UBlackboardComponent* BlackboardComp = GetBlackboardComponent();
BlackboardComp->SetValueAsObject(TEXT("TargetActor"), TargetActor);
BlackboardComp->ClearValue(TEXT("TargetActor"));
// Observers: the delegate returns EBlackboardNotificationResult, it is not void.
const FBlackboard::FKey KeyID = BlackboardComp->GetKeyID(TEXT("TargetActor"));
const FDelegateHandle Handle = BlackboardComp->RegisterObserver(KeyID, this,
FOnBlackboardChangeNotification::CreateUObject(this, &AMyAIController::HandleTargetKeyChanged));
BlackboardComp->UnregisterObserver(KeyID, Handle);
// Declared in AMyAIController — returning void will not bind:
EBlackboardNotificationResult HandleTargetKeyChanged(const UBlackboardComponent& BlackboardComp, FBlackboard::FKey ChangedKeyID);
登録を継続するにはEBlackboardNotificationResult::ContinueObservingを返し、登録を解除するにはRemoveObserverを返します。UnregisterObserversFrom(this)は、オブジェクトが登録したすべてのオブザーバーを解除します。
BT ノードでは、キー名をハードコードしないでください。 FBlackboardKeySelectorのUPROPERTYを公開し、SelectedKeyNameを読み取ります。ビヘイビアツリーはFBlackboardKeySelector::ResolveSelectedKey(const UBlackboardData&)を通じてアセットに照らしてキーを解決し、GetSelectedKeyID()はキャッシュされたFBlackboard::FKeyを返します。キーをInstance Synced(UBlackboardDataアセット内のFBlackboardEntry::bInstanceSynced)に設定すると、そのアセットを使用するすべての AI 間で値が共有されます。これにより、追加の設定なしで分隊全体に警告を共有できます。UBlackboardData::HasSynchronizedKeys()は、同期済みのキーがあるかどうかを返します。ホットパスではキーをキャッシュしてください。(const UBlackboardComponent&, FBlackboard::FKey)から構築するFBBKeyCachedAccessor<UBlackboardKeyType_Bool>は、Get()とSetValue(UBlackboardComponent&, Value)を公開し、呼び出しごとの名前検索を省略します。
ビヘイビアツリーノード
| 基底クラス | オーバーライド | シグネチャの参照元 |
|---|---|---|
UBTTaskNode | ExecuteTask、AbortTask、TickTask、OnTaskFinished、OnMessage | BehaviorTree/BTTaskNode.h |
UBTDecorator | CalculateRawConditionValue(const)、OnNodeActivation、OnNodeDeactivation、OnNodeProcessed | BehaviorTree/BTDecorator.h |
UBTService | TickNode、OnSearchStart、OnBecomeRelevant、OnCeaseRelevant | BehaviorTree/BTService.h(関連性フックはBTAuxiliaryNode.hから継承) |
| 任意のノード | GetInstanceMemorySize、InitializeMemory、CleanupMemory | BehaviorTree/BTNode.h |
次の内容をそのままコピーしてください(クラス名のみ変更します)。
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
virtual void OnTaskFinished(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTNodeResult::Type TaskResult) override;
virtual void OnMessage(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, FName Message, int32 RequestID, bool bSuccess) override;
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
virtual void OnNodeActivation(FBehaviorTreeSearchData& SearchData) override;
virtual void OnNodeDeactivation(FBehaviorTreeSearchData& SearchData, EBTNodeResult::Type NodeResult) override;
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
virtual void OnSearchStart(FBehaviorTreeSearchData& SearchData) override;
virtual uint16 GetInstanceMemorySize() const override;
virtual void InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const override;
virtual void CleanupMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryClear::Type CleanupType) const override;
EBTNodeResult::Typeの値は正確に4つです。Succeeded、Failed、Aborted、InProgressです。FBTNodeResultは存在しません。
ノードメモリを使うカスタムタスク
AI ごとのランタイム状態は、プレーンな構造体(例:FMyAttackTaskMemory { float ElapsedTime; TWeakObjectPtr<AActor> CachedTarget; })に格納し、ノードの UPROPERTY メンバーには決して格納しないでください。タスクは GetInstanceMemorySize() からその構造体の sizeof を返します。UMyBTTask_Attack のヘッダーと .cpp の全コード(FBlackboardKeySelector キー、ExecuteTask/TickTask/AbortTask)は、ビヘイビアツリーのパターンにあります。
ノードメモリのルール。 NodeMemory は GetInstanceMemorySize() でサイズが決まり、BT インスタンスごとに配置される生のバイトブロックなので、共有ノードオブジェクト上で AI ごとのランタイム状態を保持できる唯一の安全な場所です。CastInstanceNodeMemory<T>() でアクセスしてください。この関数は sizeof(T) <= GetInstanceMemorySize() をアサートします。自明でないメンバーがある場合は InitializeMemory/CleanupMemory をオーバーライドし、ヘルパーの InitializeNodeMemory<T>(NodeMemory, InitType)/CleanupNodeMemory<T>(NodeMemory, CleanupType) を呼び出してください。これらは適切に配置ニューを行い、破棄します。ティック間隔を使用するタスクでは、FBTTaskMemory ヘッダー(NextTickRemainingTime、AccumulatedDeltaTime)が使われ、GetSpecialNodeMemory<FBTTaskMemory>() からアクセスできます。エンジンはこれを NodeMemory の直前に配置するため(BTNode.h:390)、構造体と重なることはありません。
コンストラクターで INIT_TASK_NODE_NOTIFY_FLAGS()/INIT_DECORATOR_NODE_NOTIFY_FLAGS()/INIT_SERVICE_NODE_NOTIFY_FLAGS() を呼び出してください。これらは、実際にオーバーライドした仮想関数に基づいて bNotifyTick、bNotifyTaskFinished、bNotifyActivation などを設定します。これらを呼び出さず(または bNotify* フラグを手動で設定せず)にいると、タスクの TickTask/OnTaskFinished やデコレーター/サービスのアクティベーションおよび関連性のオーバーライドは呼び出されません(BTTaskNode.cpp:12-13)。bNotifyTick と bNotifyOnSearch がデフォルトで true になるのは UBTService だけです(BTService.cpp:11-12)。InProgress を返すと、タスクは潜在状態になります。FinishLatentTask(OwnerComp, Result) で完了させてください(中止時は FinishLatentAbort(OwnerComp) を使用します)。ティックする代わりに外部イベントを待つには、WaitForMessage(OwnerComp, FName) を呼び出し、OnMessage 内で完了させてください。送信側は FAIMessage::Send(Pawn, FAIMessage(TEXT("MontageCompleted"), Sender, true)) または UAIBlueprintHelperLibrary::SendAIMessage を使用します。
ツリーの外部から再評価を強制するには、UBehaviorTreeComponent::RequestExecution(const UBTCompositeNode* RequestedOn, int32 InstanceIdx, const UBTNode* RequestedBy, int32 RequestedByChildIndex, EBTNodeResult::Type ContinueWithResult, bool bStoreForDebugger = true) を使用してください。デコレーターからは、ConditionalFlowAbort(OwnerComp, EBTDecoratorAbortRequest::ConditionResultChanged) を優先して使用し、スコープは FlowAbortMode に決定させてください。
独自のノードを書く前に再利用を検討したい組み込みノード:タスクは UBTTask_MoveTo、UBTTask_MoveDirectlyToward、UBTTask_Wait、UBTTask_WaitBlackboardTime、UBTTask_RunEQSQuery、UBTTask_PlayAnimation、UBTTask_PlaySound、UBTTask_MakeNoise、UBTTask_RotateToFaceBBEntry、UBTTask_RunBehavior、UBTTask_RunBehaviorDynamic、UBTTask_SetKeyValueBool/UBTTask_SetKeyValueFloat/UBTTask_SetKeyValueObject(キーの型ごとに 1 つ)、UBTTask_FinishWithResult。デコレーターは UBTDecorator_Blackboard、UBTDecorator_CompareBBEntries、UBTDecorator_Cooldown、UBTDecorator_TagCooldown、UBTDecorator_Loop、UBTDecorator_LoopUntil、UBTDecorator_TimeLimit、UBTDecorator_DoesPathExist、UBTDecorator_IsAtLocation、UBTDecorator_ReachedMoveGoal、UBTDecorator_CheckGameplayTagsOnActor、UBTDecorator_ConeCheck、UBTDecorator_ForceSuccess。サービスは UBTService_DefaultFocus、UBTService_RunEQS。コンポジットとして提供されているのは UBTComposite_Selector、UBTComposite_Sequence、UBTComposite_SimpleParallel のみで、汎用の並列コンポジットはありません。
ツリーのトポロジー、完全なデコレーターとサービス、中止モード、メッセージ駆動型タスク:ビヘイビアツリーのパターン。
AI Perception
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Sight.h"
// In AMyAIController::AMyAIController():
UAIPerceptionComponent* Perception = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception"));
SetPerceptionComponent(*Perception);
UAISenseConfig_Sight* Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("Sight"));
Sight->SightRadius = 2000.f;
Sight->LoseSightRadius = 2500.f;
Sight->PeripheralVisionAngleDegrees = 60.f;
Sight->AutoSuccessRangeFromLastSeenLocation = 400.f;
Sight->DetectionByAffiliation.bDetectEnemies = true;
Perception->ConfigureSense(*Sight);
Perception->SetDominantSense(Sight->GetSenseImplementation());
UAISenseConfig_Hearing* Hearing = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("Hearing"));
Hearing->HearingRange = 3000.f;
Hearing->DetectionByAffiliation.bDetectEnemies = true;
Perception->ConfigureSense(*Hearing);
実行時にセンス設定を変更した場合、RequestStimuliListenerUpdate()を呼び出した後にのみ反映されます。
| デリゲート | シグネチャ | 発火タイミング |
|---|---|---|
OnTargetPerceptionUpdated | (AActor* Actor, FAIStimulus Stimulus) | 状態が変化した処理済みの刺激ごとに1回、つまりアクターとセンスの組み合わせごとに1回発火します(AIPerceptionComponent.cpp:593) |
OnTargetPerceptionInfoUpdated | (const FActorPerceptionUpdateInfo& UpdateInfo) | 同様ですが、TargetIdを含むため、アクターが破棄済みまたはnullでも発火します |
OnTargetPerceptionForgotten | (AActor* Actor) | ForgetActor/ForgetAllの後、またはすべての刺激が期限切れになったときに発火します。経過時間による忘却はUAISystem::bForgetStaleActorsが有効な場合のみです(AISystem.h:84) |
OnPerceptionUpdated | (const TArray<AActor*>& UpdatedActors) | 何らかの更新があった刺激処理パスごとに1回、まとめて発火します |
FAIStimulusには、Strength、StimulusLocation、ReceiverLocation、Tag、Type(FAISenseID)に加え、WasSuccessfullySensed()、IsActive()、GetAge()が含まれます。センスごとに分岐するには、Stimulus.Type == UAISense::GetSenseID<UAISense_Sight>()を使います。クエリには、GetCurrentlyPerceivedActors(TSubclassOf<UAISense> SenseToUse, TArray<AActor*>& OutActors)、GetPerceivedHostileActors(TArray<AActor*>&)、GetPerceivedHostileActorsBySense(TSubclassOf<UAISense>, TArray<AActor*>&)、HasActiveStimulus(const AActor&, FAISenseID)、ForgetActor(AActor*)、ForgetAll()があります。
イベントはポーリングではなく、プッシュ方式です。UAISense_Hearing::ReportNoiseEvent(WorldContextObject, NoiseLocation, Loudness, Instigator, MaxRange, Tag)とUAISense_Damage::ReportDamageEvent(WorldContextObject, DamagedActor, Instigator, DamageAmount, EventLocation, HitLocation, Tag)を使います。Sightには報告関数がなく、トレースによって検出され、登録済みのソースのみを認識します。UAISense_Prediction::RequestPawnPredictionEvent(APawn* Requestor, AActor* PredictedActor, float PredictionTime)を使うと、ターゲットがどこへ向かうかを問い合わせられます。
アクターを知覚可能にするには、UAIPerceptionStimuliSourceComponentを追加してbAutoRegisterAsSource = trueを設定する(またはRegisterWithPerceptionSystem()を呼び出す)とともに、センスごとにRegisterForSense(UAISense_Sight::StaticClass())を呼び出します。または、UAIPerceptionSystem::GetCurrent(World)->RegisterSource(*Actor)(インスタンス化済みのすべてのセンス)/RegisterSourceForSenseClass(UAISense_Sight::StaticClass(), *Actor)を呼び出します。どちらもAActor&を受け取ります(Perception/AIPerceptionSystem.h:139-141)。
その他のセンス設定:UAISenseConfig_Touch、UAISenseConfig_Team(IGenericTeamAgentInterfaceを介してチームメイト間で認識を伝播)、UAISenseConfig_Prediction。
ナビゲーションと経路探索
#include "NavigationPath.h"
#include "NavigationSystem.h"
UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(GetWorld());
if (!NavSys) { return; }
FNavLocation RandomPoint;
const bool bFoundRandom = NavSys->GetRandomReachablePointInRadius(Origin, 1500.f, RandomPoint);
FNavLocation Projected;
const bool bOnNavMesh = NavSys->ProjectPointToNavigation(WorldLocation, Projected, FVector(500.f, 500.f, 500.f));
// Blueprint-facing helpers, equally usable from C++ (all static, all take a world context):
UNavigationPath* Path = UNavigationSystemV1::FindPathToLocationSynchronously(GetWorld(), Start, End, GetPawn());
const bool bReachable = Path && Path->IsValid() && !Path->IsPartial();
FVector HitLocation;
const bool bBlocked = UNavigationSystemV1::NavigationRaycast(GetWorld(), Start, End, HitLocation);
// Native path, with the full FNavPathPoint list:
const FPathFindingQuery Query(this, *NavSys->GetDefaultNavDataInstance(), Start, End);
const FPathFindingResult Result = NavSys->FindPathSync(Query);
const int32 NumPoints = Result.IsSuccessful() ? Result.Path->GetPathPoints().Num() : 0;
UNavigationPath は PathPoints、GetPathLength()、IsValid()、IsPartial() を公開します。FPathFindingResult は Path、IsSuccessful()、IsPartial() を公開します。
非同期:FindPathAsync(const FNavAgentProperties&, FPathFindingQuery, const FNavPathQueryDelegate&, EPathFindingMode::Type) を使用します。デリゲートは DECLARE_DELEGATE_ThreeParams(FNavPathQueryDelegate, uint32 QueryID, ENavigationQueryResult::Type, FNavPathSharedPtr) です。そのため、これ以外のシグネチャを持つハンドラーはコンパイルできません。
| 状況 | 対応方法 |
|---|---|
| ナビメッシュがまったくない | ANavMeshBoundsVolume を配置します。これがないとタイルは生成されません |
| プロシージャル/ランタイムジオメトリ | ナビメッシュの Runtime Generation を Dynamic に設定します。ナビゲーションに関係するコンポーネントを登録または移動すると、変更されたタイルのみがダーティ化されて再構築されます(AddDirtyArea、NavigationSystem.h:973)。NavSys->Build() はすべてのナビゲーションデータを再構築するため、一括生成の後に一度だけ使用し、変更ごとには使用しないでください |
| オープンワールド/ストリーミングレベル | AI ポーンに UNavigationInvokerComponent を追加し、TileGenerationRadius/TileRemovalRadius(または SetGenerationRadii)を設定します |
| 飛行または遊泳するエージェント | 移動コンポーネントの NavAgentProps で FNavAgentProperties::bCanFly/bCanSwim を設定します。エージェントはフラグが一致するナビゲーションデータのみを使用します |
| 多数のエージェント間での群衆回避 | UCrowdFollowingComponent と SetCrowdSimulationState、SetCrowdAvoidanceQuality、SetCrowdSeparationWeight、SetCrowdCollisionQueryRange を使用します |
エージェントの生成設定は ARecastNavMesh にあります(Cast<ARecastNavMesh>(NavSys->GetDefaultNavDataInstance())):AgentRadius、AgentHeight、AgentMaxSlope、TileSizeUU、および解像度ごとのステップ高(GetAgentMaxStepHeight(ENavigationDataResolution)/SetAgentMaxStepHeight(ENavigationDataResolution, float))。タイルの識別には生のインデックスではなく FNavTileRef を使用します。現在のオーバーロードは GetNavMeshTileBounds(FNavTileRef) と GetNavMeshTileXY(FNavTileRef, int32&, int32&, int32&) です。
ナビゲーションエリア、フィルター、オフメッシュリンク
走行領域ごとに UNavArea をサブクラス化し、DefaultCost、FixedAreaEnteringCost、DrawColor を設定します。組み込みエリアは、UNavArea_Default、UNavArea_Null(歩行不可)、UNavArea_Obstacle、UNavArea_LowHeight、エージェントごとの置き換えに使う UNavAreaMeta_SwitchByAgent です。UNavModifierComponent(AreaClass、省略可能な AreaClassToReplace、FailsafeExtent、bIncludeAgentHeight、およびセッター SetAreaClass/SetAreaClassToReplace)を使って実行時にエリアを設定するか、レベル内に ANavModifierVolume を配置します。
メッシュごとの歩行可能エリア。 UNavCollisionBase には相互排他的なモードが2つあります。bIsDynamicObstacle は生成後に障害物モディファイアーを設定し、bUseSurfaceArea は UNavCollision::AreaClass をラスタライズに反映させるため、メッシュ自体の三角形がそのエリアになります。メッシュが地形の種類(泥、水、屋上など)そのものである場合は、地形上の障害物ではなく、サーフェスモードを使用してください。
クエリフィルターは UNavigationQueryFilter のサブクラスです。前方宣言ではなく、実際のクラス本体が必要です。
// MyNavFilter.h
#pragma once
#include "CoreMinimal.h"
#include "NavFilters/NavigationQueryFilter.h"
#include "MyNavFilter.generated.h"
UCLASS()
class MYGAME_API UMyNavFilter : public UNavigationQueryFilter
{
GENERATED_BODY()
public:
UMyNavFilter();
};
// MyNavFilter.cpp
#include "MyNavFilter.h"
#include "NavAreas/NavArea_Obstacle.h"
UMyNavFilter::UMyNavFilter()
{
FNavigationFilterArea& AvoidObstacles = Areas.AddDefaulted_GetRef();
AvoidObstacles.AreaClass = UNavArea_Obstacle::StaticClass();
AvoidObstacles.bIsExcluded = true;
}
FilterClass 引数として渡します。MoveToActor(Target, -1.f, true, true, true, UMyNavFilter::StaticClass());。各 FNavigationFilterArea エントリーでは、代わりに bOverrideTravelCost + TravelCostOverride、または bOverrideEnteringCost + EnteringCostOverride でコストを上書きできます。UNavigationQueryFilter::GetQueryFilter(NavData, Querier, FilterClass) は実行時の FSharedConstNavQueryFilter を解決します。動的なルールを設定するには、InitializeFilter(const ANavigationData&, const UObject*, FNavigationQueryFilter&) をオーバーライドします。
オフメッシュリンク。 レベルに ANavLinkProxy を配置します。その PointLinks 配列にはシンプルリンクが、SegmentLinks にはセグメントリンクが格納されます。また、UNavLinkCustomComponent(GetSmartLinkComp() 経由)でスマートリンクのパスを利用できます。SetLinkData(RelativeStart, RelativeEnd, ENavLinkDirection::Type)、SetEnabled(bool)、SetEnabledArea(TSubclassOf<UNavArea>)、SetMoveReachedLink(FOnMoveReachedLink const&) を使うと、エージェントがリンクに到達した際にコード(ジャンプやはしごを登る動作など)を実行できます。ANavLinkProxy::OnSmartLinkReached は、Blueprint から利用できる同等の機能です。ARecastNavMesh の FNavLinkGenerationJumpConfig 配列 NavLinkJumpConfigs を使うと、Recast でジャンプリンクを自動生成することもできます。
EQS 環境クエリシステム
#include "EnvironmentQuery/EnvQueryManager.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
// AMyAIController declares: TObjectPtr<UEnvQuery> FindCoverQuery and
// void HandleCoverQueryFinished(TSharedPtr<FEnvQueryResult> Result).
void AMyAIController::RunCoverQuery()
{
if (!FindCoverQuery) { return; }
// Querier = pawn; a controller querier puts the Querier context at the controller (EnvQueryContext_Querier.cpp:19-21)
FEnvQueryRequest Request(FindCoverQuery, GetPawn());
Request.SetFloatParam(TEXT("SearchRadius"), 1200.f);
Request.Execute(EEnvQueryRunMode::SingleResult, this, &AMyAIController::HandleCoverQueryFinished);
}
void AMyAIController::HandleCoverQueryFinished(TSharedPtr<FEnvQueryResult> Result)
{
UBlackboardComponent* BlackboardComp = GetBlackboardComponent();
if (BlackboardComp && Result.IsValid() && Result->IsSuccessful())
{
BlackboardComp->SetValueAsVector(TEXT("CoverLocation"), Result->GetItemAsLocation(0));
}
}
FQueryFinishedSignature は DECLARE_DELEGATE_OneParam(FQueryFinishedSignature, TSharedPtr<FEnvQueryResult>) です。実行モードは、SingleResult、RandomBest5Pct、RandomBest25Pct、AllMatching です。結果からは、Items、GetItemAsLocation(int32)、GetItemAsActor(int32)、GetItemScore(int32)、IsSuccessful()、IsAborted() を利用できます。その他のエントリーポイントは次のとおりです。UEnvQueryManager::GetCurrent(WorldContextObject)->RunQuery(Request, RunMode, FinishDelegate) は、キャンセルに使うクエリ ID を返します。RunInstantQuery(Request, RunMode) はブロックしてその場で結果を返します(必要な場合に限って使用してください)。UEnvQueryManager::RunEQSQuery(WorldContextObject, QueryTemplate, Querier, RunMode, WrapperClass) は Blueprint 用のラッパーです。
サブクラスで実装するフックは、すべて const です。UEnvQueryGenerator::GenerateItems(FEnvQueryInstance&)、UEnvQueryTest::RunTest(FEnvQueryInstance&)、UEnvQueryContext::ProvideContext(FEnvQueryInstance&, FEnvQueryContextData&)。Blueprint コンテキストは UEnvQueryContext_BlueprintBase を継承し、ProvideSingleActor、ProvideSingleLocation、ProvideActorsSet、または ProvideLocationsSet を実装します。
レベルに AEQSTestingPawn(EnvironmentQuery/EQSTestingPawn.h)を配置すると、PIE を実行せずに、エディター内でクエリのスコア付きアイテムをプレビューできます。QueryTemplate と QueryConfig を設定してください。
ジェネレーターとテストのプロパティテーブル、カスタム C++ コンテキスト、スコアリングのレシピ、すぐに使えるクエリ設定については、EQS リファレンスを参照してください。
AI 向け State Tree
ビヘイビアツリーは、優先度に基づく割り込みを伴うリアクティブな戦闘に適しており、State Tree は、よりフラットなステートマシンや Smart Object のフローに適しています。AI では、通常の UStateTreeComponent ではなく Components/StateTreeAIComponent.h にある UStateTreeAIComponent を使用してください。このコンポーネントは UStateTreeAIComponentSchema を返し、AAIController のコンテキスト値を保証します。Build.cs では StateTreeModule と GameplayStateTreeModule が必要です。
タスク、条件、エバリュエーター、スキーマの作成は ue-state-trees に属します。
Smart Objects
SmartObjects はオプトイン方式のプラグインです(SmartObjectsModule)。ワールド内のアクターに USmartObjectComponent を追加し、USmartObjectDefinition を設定してください(GetDefinition() / SetDefinition())。AI は USmartObjectSubsystem を通じてスロットを検索し、確保します。
#include "SmartObjectSubsystem.h"
#include "SmartObjectRequestTypes.h" // FSmartObjectRequest, FSmartObjectRequestFilter, FSmartObjectRequestResult
USmartObjectSubsystem* Subsystem = USmartObjectSubsystem::GetCurrent(GetWorld());
if (!Subsystem) { return; }
FSmartObjectRequestFilter Filter;
const FSmartObjectRequest Request(FBox::BuildAABB(Origin, FVector(500.f)), Filter);
const FSmartObjectRequestResult Result = Subsystem->FindSmartObject(Request, GetPawn());
if (Result.IsValid())
{
const FSmartObjectClaimHandle Claim = Subsystem->MarkSlotAsClaimed(Result.SlotHandle, ESmartObjectClaimPriority::Normal);
FVector SlotLocation;
if (Subsystem->GetSlotLocation(Claim, SlotLocation))
{
MoveToLocation(SlotLocation);
}
Subsystem->MarkSlotAsFree(Claim); // in real code, only once the interaction ends
}
ESmartObjectClaimPriority の値は Low、BelowNormal、Normal、AboveNormal、High です。FindSmartObjects はすべての一致候補を返し、FindSmartObjectsInList は、すでに取得しているアクターに検索範囲を限定します。GameplayBehaviorSmartObjects プラグイン(5.8 では Experimental)は、GameplayBehavior によるスロットのビヘイビアを追加します。また、UBlackboardKeyType_SOClaimHandle を使うと、BT でブラックボードキーにクレームハンドルを保持できます。
ZoneGraph、MassAI、NavCorridor
| プラグイン | 5.8 での成熟度 | 用途 | エントリーポイント |
|---|---|---|---|
| ZoneGraph | Experimental | 自由なナビメッシュ移動の代わりに、交通や歩行者の流れに対応するレーングラフを使用します | UZoneGraphSubsystem::FindNearestLane / FindOverlappingLanes、AZoneGraphData、UZoneShapeComponent、UE::ZoneGraph::Query ヘルパー |
| MassAI | Experimental | エージェントごとにアクターを用意せず、何千もの軽量エージェントを扱います | UMassStateTreeProcessor、FMassStateTreeExecutionContext、モジュール MassAIBehavior、MassNavigation、MassZoneGraphNavigation、MassNavMeshNavigation |
| NavCorridor | Experimental | ナビメッシュ経路のポリラインに沿うのではなく、経路周辺のコリドー内でステアリングを行います | FNavCorridor、FNavCorridorParams、FNavCorridorPortal、FNavCorridorLocation |
Mass エージェントの作成、フラグメント、プロセッサーは ue-mass-entity に属します。
非推奨 — 使用しないでください
| 使用しない | 5.8 で使用するもの | ソース |
|---|---|---|
EPointOnCircleSpacingMethod | EEnvQueryPointSpacingMethod | EnvironmentQuery/Generators/EnvQueryGenerator_OnCircle.h:15 の UE_DEPRECATED(5.8) |
USmartObjectComponent::SetSmartObjectEnabled (BlueprintPure) | K2_SetSmartObjectEnabled | SmartObjectComponent.h:101 の UE_DEPRECATED(5.8) |
USmartObjectComponent::SetSmartObjectEnabledForReason (BlueprintPure) | K2_SetSmartObjectEnabledForReason | SmartObjectComponent.h:118 の UE_DEPRECATED(5.8) |
FNavLinkGenerationJumpDownConfig | FNavLinkGenerationJumpConfig | NavMesh/LinkGenerationConfig.h:159 の UE_DEPRECATED(5.7) |
UAIPerceptionComponent::RefreshStimulus | ConditionallyStoreSuccessfulStimulus(FAIStimulus&, const FAIStimulus&) | Perception/AIPerceptionComponent.h:448 の UE_DEPRECATED(5.6) |
UPathFollowingComponent::SetMovementComponent(UNavMovementComponent*) | SetNavMovementInterface(INavMovementInterface*)(非推奨メッセージに記載されている SetNavMoveInterface は存在しません) | Navigation/PathFollowingComponent.h:282 の UE_DEPRECATED(5.5)、置き換え先は :286 |
int32 TileIndex を受け取る ARecastNavMesh のタイル関数 | FNavTileRef を受け取るオーバーロード | NavMesh/RecastNavMesh.h:1134,1141,1151,1161 の UE_DEPRECATED(5.5) |
ARecastNavMesh::AgentMaxStepHeight | GetAgentMaxStepHeight(ENavigationDataResolution) / NavMeshResolutionParams | NavMesh/RecastNavMesh.h:718 の UE_DEPRECATED(all) |
UBehaviorTreeComponent::RequestExecution(const UBTDecorator*) | RequestBranchEvaluation | BehaviorTree/BehaviorTreeComponent.h:153 の「replaced by」コメント |
UNavigationSystemV1::K2_GetRandomPointInNavigableRadius | K2_GetRandomLocationInNavigableRadius | NavigationSystem.h:1484 の DeprecatedFunction |
よくある間違い
通知フラグのマクロを忘れる: コンストラクターで INIT_TASK_NODE_NOTIFY_FLAGS() / INIT_SERVICE_NODE_NOTIFY_FLAGS() を呼び出さないと、TickTask や OnSearchStart のオーバーライドは実行されません。ツリーが呼び出す対象を決めるのはオーバーライドではなく、フラグです。
インスタンスごとの状態をメンバーにする: BT ノードオブジェクトはツリーを実行するすべての AI で共有されるため、UPROPERTY としての float ElapsedTime; はエージェント間のデータ競合を引き起こします。GetInstanceMemorySize() でサイズを指定した構造体に保存し、CastInstanceNodeMemory<T>() でアクセスしてください。
InProgress を返したまま完了しない: タスクが永久に停止します。遅延処理を行うすべての経路は、FinishLatentTask または FinishLatentAbort で終了させる必要があります。
ブラックボードでの間違い: FOnBlackboardChangeNotification の戻り値は EBlackboardNotificationResult なので、void を返すオブザーバーはバインドできません。また、BT ノードではキー名をハードコーディングせず、FBlackboardKeySelector を公開する必要があります。これにより、デザイナーがキーを再バインドでき、ResolveSelectedKey でアセットに対して検証できます。
ANavMeshBoundsVolume がない: Bounds Volume がないとタイルが生成されず、すべての MoveTo が何も通知せず失敗します。プロシージャルジオメトリでは、Dynamic ランタイム生成(大量生成の後に NavSys->Build() を1回呼び出す)または UNavigationInvokerComponent を使用してください。
AI がクライアントにも存在すると想定する: AAIController はサーバー専用です。結果はポーンの複製プロパティを通じて複製してください。ブラックボードとビヘイビアツリーは複製されません。
スロットリングせずにポーリングする: フレームごとのチェックより、FlowAbortMode を設定した UBTDecorator_Blackboard または WaitForMessage を優先してください。Service の Interval は RandomDeviation を設定したうえで0.5秒以上に保ち、EQS には UBTDecorator_Cooldown を適用してください。AllMatching より SingleResult を優先し、安価なフィルターテストをトレーステストやパスファインディングテストより前に配置してください。
関連スキル
ue-state-trees— State Tree のタスク、条件、評価器、スキーマ、Mass の動作ue-mass-entity— 大量のエージェントを対象とした Mass のフラグメント、プロセッサー、群衆シミュレーションue-character-movement— このスキルが要求するパスを実行する移動コンポーネントue-gameplay-framework— GameMode によるスポーン、コントローラーとポーンの所有関係、Possession のライフサイクルue-physics-collision— 視覚検知と EQS トレーステストで使用するトレースチャンネルとコリジョン応答ue-gameplay-tags-messaging—UBTDecorator_CheckGameplayTagsOnActorと EQS タグテストで使用するゲームプレイタグue-actor-component-architecture— コンポーネントの作成、所有関係、複製のパターンue-mover— Mover プラグイン:移動モード、レイヤー化された移動、ロールバックネットワークue-testing-debugging— 自動化テスト、ログ、アサーション、プロファイリング、デバッグ描画