如何定制AI的移动寻路方式
初始化寻路过滤器
const FSharedConstNavQueryFilter& ATestMonsterAIController::GetOrCreateNavmeshQueryFilter()
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys != nullptr)
{
const ANavigationData* NavData = NavSys->GetNavDataForProps(GetNavAgentPropertiesRef(), GetNavAgentLocation());
if (NavData)
{
this->QueryFilter = NavData->GetDefaultQueryFilter()->GetCopy();
if (FNavigationQueryFilter* DataNavFilter = const_cast<FNavigationQueryFilter*>(this->QueryFilter.Get()))
{
for (uint8 i = 0; i < RECAST_MAX_AREAS; ++i)
{
DataNavFilter->SetExcludedArea(i);
}
}
}
}
return this->QueryFilter;
}
设置寻路成本
// 指定区域不能走
void ATestMonsterAIController::AddExcludedArea(TSubclassOf<UNavArea> AreaClass)
{
if (!this->QueryFilter.IsValid())
{
GetOrCreateNavmeshQueryFilter();
}
if (!this->QueryFilter.IsValid())
{
return;
}
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
const ANavigationData* NavData = (NavSys == nullptr) ? nullptr : NavSys->GetNavDataForProps(GetNavAgentPropertiesRef(), GetNavAgentLocation());
if (!NavData)
{
return;
}
FSharedConstNavQueryFilter& NavFilter = this->QueryFilter;
if (FNavigationQueryFilter* DataNavFilter = const_cast<FNavigationQueryFilter*>(NavFilter.Get()))
{
DataNavFilter->SetExcludedArea(NavData->GetAreaID(AreaClass));
}
}
// 指定哪些区域的移动成本
void ATestMonsterAIController::SetAreaCost(TSubclassOf<UNavArea> AreaClass, float Cost)
{
if (!this->QueryFilter.IsValid())
{
GetOrCreateNavmeshQueryFilter();
}
if (!this->QueryFilter.IsValid())
{
return;
}
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
const ANavigationData* NavData = (NavSys == nullptr) ? nullptr : NavSys->GetNavDataForProps(GetNavAgentPropertiesRef(), GetNavAgentLocation());
if (!NavData)
{
return;
}
FSharedConstNavQueryFilter& NavFilter = this->QueryFilter;
if (FNavigationQueryFilter* DataNavFilter = const_cast<FNavigationQueryFilter*>(NavFilter.Get()))
{
DataNavFilter->SetAreaCost(NavData->GetAreaID(AreaClass), Cost);
}
}
运行时修改寻路过滤器
UE4的AIControoler寻路的时候调用顺序:
MoveToLocation/MoveToActor → 构建 FAIMoveRequest
MoveTo → 验证请求,检查是否已到达
BuildPathfindingQuery → 构建寻路查询对象
FindPathForMoveRequest ⭐ → 执行寻路(主要重写点)
RequestMove → 开始路径跟随
PathFollowingComponent::TickComponent → 每帧更新移动
OnMoveCompleted → 移动完成回调
最佳实践:
✅ 重写 FindPathForMoveRequest 来应用自定义 QueryFilter
✅ 在构造函数中初始化 QueryFilter
✅ 使用 SetAreaCost 和 AddExcludedArea 配置导航行为
✅ 调用 Super::FindPathForMoveRequest 执行实际寻路
sequenceDiagram
participant BT as BTTask_MoveTo/Blueprint
participant AIC as AAIController
participant NavSys as UNavigationSystemV1
participant PFC as UPathFollowingComponent
Note over BT,PFC: 1. 发起移动请求
BT->>AIC: MoveToLocation() / MoveToActor()
Note right of AIC: 构建 FAIMoveRequest
Note over BT,PFC: 2. 执行移动
AIC->>AIC: MoveTo(FAIMoveRequest)
Note right of AIC: 验证请求有效性<br/>检查是否已到达目标
Note over BT,PFC: 3. 构建寻路查询
AIC->>AIC: BuildPathfindingQuery()
Note right of AIC: 获取导航数据<br/>设置起点/终点<br/>应用导航过滤器
AIC->>PFC: OnPathfindingQuery(Query)
Note right of PFC: 允许组件修改查询参数
Note over BT,PFC: 4. 执行寻路 (可重写)
AIC->>AIC: FindPathForMoveRequest()
Note right of AIC: ⭐ 虚函数,可重写<br/>应用自定义 QueryFilter
AIC->>NavSys: FindPathSync(Query)
NavSys-->>AIC: FPathFindingResult
Note right of AIC: 设置目标Actor观察<br/>启用路径重算
Note over BT,PFC: 5. 请求移动执行
AIC->>AIC: RequestMove(MoveRequest, Path)
AIC->>PFC: RequestMove(MoveRequest, Path)
Note right of PFC: 中止之前的移动<br/>存储路径和请求<br/>注册路径观察者<br/>开始路径跟随
PFC-->>AIC: FAIRequestID
AIC-->>BT: FPathFollowingRequestResult
Note over BT,PFC: 6. 路径跟随
loop 每帧更新
PFC->>PFC: TickComponent()
PFC->>PFC: UpdatePathSegment()
Note right of PFC: 检查是否到达<br/>更新移动方向
end
Note over BT,PFC: 7. 移动完成
PFC->>AIC: OnMoveCompleted()
AIC->>BT: OnRequestFinished
sequenceDiagram
participant BT as 行为树任务
participant AIC as AIController
participant NavSys as NavigationSystem
participant NavData as NavigationData
participant Path as PathFollowingComponent
BT->>AIC: MoveTo/请求移动
AIC->>AIC: FindPathForMoveRequest()
Note over AIC: 1. 设置自定义QueryFilter<br>2. 准备FPathFindingQuery
AIC->>NavSys: RequestPath()
NavSys->>NavData: FindPath()
NavData->>NavData: 执行寻路算法
NavData-->>NavSys: 返回路径结果
NavSys-->>AIC: 返回FNavPathSharedPtr
AIC->>Path: 开始路径跟随
Path->>Path: 逐帧移动更新
void ATestMonsterAIController::FindPathForMoveRequest(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query,
FNavPathSharedPtr& OutPath) const
{
if (this->QueryFilter.IsValid())
{
// 发起寻路,就用自己身上定制的cost来处理寻路成本
Query.QueryFilter = this->QueryFilter;
}
Super::FindPathForMoveRequest(MoveRequest, Query, OutPath);
}