总览
Smart Object 查询不是“找最近椅子”。源码里的 FindSmartObject 明确说返回的是空间分区里第一个有效对象,不保证最近。真实项目如果要最近、最优、队列最短、离目标最近,应该先拿一批候选,再自己排序或用 EQS/Mass 处理。
查询流水线
- 用
QueryBox定义搜索范围。 - 用空间结构取出范围内的 Smart Object 候选。
- 检查对象是否 enabled、是否通过 Predicate、对象级 Preconditions。
- 对每个 Definition 和 Slot 进行行为类型、ActivityRequirements、UserTags 过滤。
- 检查 Slot enabled、是否已被高优先级占用、Slot Preconditions。
- 返回
FSmartObjectRequestResult,包含对象句柄和 Slot 句柄。
使用案例
巡逻 NPC 想找 800cm 内能坐的椅子:
FSmartObjectRequestFilter Filter;
Filter.UserTags = UserTagsFromNPC;
Filter.ActivityRequirements = SitActivityQuery;
Filter.ClaimPriority = ESmartObjectClaimPriority::Normal;
FSmartObjectRequest Request;
Request.QueryBox = FBox::BuildAABB(NPC.GetActorLocation(), FVector(800.0));
Request.Filter = Filter;
TArray<FSmartObjectRequestResult> Results;
Subsystem->FindSmartObjects(Request, Results, FConstStructView::Make(FSmartObjectActorUserData(&NPC)));
Results.Sort([&](const FSmartObjectRequestResult& A, const FSmartObjectRequestResult& B)
{
return FVector::DistSquared(*Subsystem->GetSlotLocation(A), NPC.GetActorLocation())
< FVector::DistSquared(*Subsystem->GetSlotLocation(B), NPC.GetActorLocation());
});
上面示意里 GetSlotLocation 返回 TOptional<FVector>,生产代码要处理无效值。重点是:如果你需要“最近”,不要直接相信 FindSmartObject 的第一个结果。
架构分析
FSmartObjectRequestFilter 的几个字段非常关键:UserTags 给 Definition/Slot 的 UserTagFilter 使用;ClaimPriority 决定是否能抢占低优先级 Claimed Slot;ActivityRequirements 匹配 ActivityTags;BehaviorDefinitionClasses 可以限定只找含某类 BehaviorDefinition 的 Slot;bShouldEvaluateConditions 控制是否执行 World Conditions;bShouldIncludeClaimedSlots 和 bShouldIncludeDisabledSlots 用于调试或特殊逻辑。
空间索引层的实现由 USmartObjectSubsystem 维护,源码里有 SmartObjectHashGrid 和 SmartObjectOctree。你不需要直接操作它们,但要知道查询范围太大时不是免费的;大量 AI 每帧全图查找,会把好系统用坏。
EQS 接入
UEnvQueryGenerator_SmartObjects 可以从 EQS Origin 出发,用 QueryBoxExtent 和 FSmartObjectRequestFilter 生成 Smart Object Slot 项。它还有 bOnlyClaimable,用于只返回当前可 Claim 的 Slot。Behavior Tree 里配合 EQS 的好处是可以用现有 EQS Test 做距离、可见性、路径成本排序。
项目落地
把查询拆成两层:基础过滤交给 Smart Object,策略排序交给调用方。基础过滤回答“这个对象是否可以被这个用户做这件事”;策略排序回答“现在选哪个最好”。比如坐椅子按距离排序,找掩体按面向敌人排序,找工作台按任务优先级排序。
常见坑
- 每 Tick 查询:加冷却、缓存、事件触发或 StateTree Tick 间隔。
- QueryBox 太大:城市人群应配合 ZoneGraph/Mass 请求,不要所有实体都全局扫。
- 把 ActivityRequirements 当对象类型:Activity 是行为能力,不是 Mesh 类型。
- Predicate 里做昂贵逻辑:Predicate 会参与过滤流水线,保持轻量。
- 忽略 DebugRejections:UE5.8 的请求过滤有 Definition/Object/Slot 三类拒绝原因,排查时很有用。
源码依据
FSmartObjectRequest 由 QueryBox 和 FSmartObjectRequestFilter 组成。FSmartObjectRequestFilter 包含 UserTags、ClaimPriority、ActivityRequirements、BehaviorDefinitionClasses、bShouldEvaluateConditions、bShouldIncludeClaimedSlots、bShouldIncludeDisabledSlots 和 Predicate。USmartObjectSubsystem 提供 FindSmartObject、FindSmartObjects、FindSmartObjectsInList、FindSmartObjectsInTargetingRequest、FindSlots 和条件过滤 API。UEnvQueryGenerator_SmartObjects 是 EQS Generator。
源码路径索引
SmartObjectsModule/Public/SmartObjectRequestTypes.hSmartObjectsModule/Public/SmartObjectSubsystem.hSmartObjectsModule/Public/SmartObjectHashGrid.hSmartObjectsModule/Public/SmartObjectOctree.hSmartObjectsModule/Public/EnvQueryGenerator_SmartObjects.h