UE5.8 StateTree 状态树专题系列

UE5.8 StateTree 专题(十):StateTree 和 Behavior Tree 怎么配合

解释 BTTask_RunStateTree、BTTask_RunDynamicStateTree 的使用方式,什么时候 BT 外层调度 StateTree,什么时候完全换成 StateTree。

总览

很多老项目已经有 Behavior Tree。看到 StateTree 后,第一反应通常是:要不要把旧 BT 全删了?先别急。更现实的做法是把 StateTree 当成“可复用的一段状态流程”,让 Behavior Tree 继续做外层调度,StateTree 接管某个分支里的细节。

一句话版本:BT 适合做“现在应该进哪个大行为”,StateTree 适合做“进入这个大行为以后,里面要按哪些状态一步一步运行”。

UE5.8 StateTree 专题(十):StateTree 和 Behavior Tree 怎么配合 配图
Behavior Tree 可以继续做高层决策,StateTree 承接一段可复用的行为状态流程。

先看一个迁移场景

你的旧守卫 AI 可能长这样:

Behavior Tree 分支 原来的做法 适合交给 StateTree 的部分
Patrol Sequence: MoveTo -> Wait -> FindNextPoint Idle/Patrol 子状态循环
Investigate MoveTo LastSeenLocation -> Wait -> Scan Search 状态流程
Combat RotateToTarget -> UseAbility -> Strafe -> Reposition Combat 子状态机
Flee FindCover -> MoveTo -> Wait 逃跑流程

不要一次迁移整棵树。先挑一个 BT 分支,比如 Combat。BT 判断“现在进入战斗”,StateTree 负责“战斗里瞄准、开火、换弹、找掩体、重新压上”。

混用时的运行边界

最重要的一条:同一时间只让一个系统控制移动。BT 的 MoveTo 和 StateTree 的 Move To Task 不要同时跑。否则你会看到 AI 一会儿被 BT 拉向巡逻点,一会儿被 StateTree 拉向玩家,调试器里每个系统都觉得自己没错。

推荐边界:

  1. Behavior Tree 负责黑板、感知大入口、团队已有 Decorator/Service。
  2. StateTree 负责一个局部行为的状态推进。
  3. Blackboard 只传少量入口数据,例如 TargetActor、LastSeenLocation、CombatStyle。
  4. StateTree 内部用 Parameters、Evaluator 和 Binding 组织细节数据。
  5. StateTree 完成后,返回 BT,让 BT 决定下一段大行为。

源码依据

UBTTask_RunStateTreeUBTTask_RunDynamicStateTree 位于 GameplayStateTree 插件。前者适合在 BT 节点里固定引用一个 StateTree,后者适合运行时从外部选择 StateTree。它们内部依赖 FStateTreeReference 和实例数据,让 Behavior Tree Task 可以启动并等待 StateTree 的运行结果。StateTree 资产侧仍然使用 StateTreeAIComponentSchema 这类 Schema 来保证 AIController/Pawn 上下文。

架构分析

把混用架构想成三层:

层级 谁负责 数据
感知与高层决策 AIController、Perception、Behavior Tree Blackboard: TargetActor、ThreatLevel
局部状态流程 StateTree Parameters、Evaluator 输出、Task InstanceData
具体执行系统 MoveTo、GAS、Montage、EQS、SmartObject GameplayTask、Ability、Animation

StateTree 不应该直接取代所有系统。它更像一个流程编排层:状态切换归它,移动还是交给导航,技能还是交给 GAS,动画还是交给 AnimBP/UAF,感知还是交给 AI Perception。

使用案例

案例:旧 BT 有一个 Combat 分支,现在把战斗细节拆进 ST_GuardCombat

编辑器步骤:

  1. 创建 ST_GuardCombat,Schema 选择 AI Component 相关 Schema。
  2. 加 Parameters:TargetActorPreferredDistanceLowHealthThreshold
  3. 添加状态:FaceTargetShootBurstReloadRepositionExitCombat
  4. 在旧 BT 的 Combat Sequence 中添加 Run StateTree Task。
  5. Task 里选择 ST_GuardCombat
  6. 把 Blackboard 的 TargetActor 绑定到 StateTree 参数。
  7. 设置 StateTree 完成后 BT 继续执行后续节点。

一个可落地的状态表:

State Task Transition
FaceTarget 面向 TargetActor 完成 -> ShootBurst
ShootBurst 激活射击 Ability 或播放射击逻辑 弹药为空 -> Reload,目标丢失 -> ExitCombat
Reload 播放换弹/等待换弹完成 成功 -> FaceTarget
Reposition Move To 掩体点或侧移点 到达 -> FaceTarget,失败 -> FaceTarget
ExitCombat 清理战斗临时状态 成功 -> StateTree Succeeded

代码演示

如果你不想在 BT 里直接写很多分支,可以用黑板只传入口数据。伪代码如下:

void AGuardAIController::EnterCombat(AActor* Target)
{
    UBlackboardComponent* BB = GetBlackboardComponent();
    BB->SetValueAsObject(TEXT("TargetActor"), Target);
    BB->SetValueAsBool(TEXT("ShouldRunCombatStateTree"), true);
}

StateTree 里不要再四处读 Blackboard。把 TargetActor 作为参数或上下文输入接进来,后续 Task 只依赖 StateTree 内部的显式数据流。这样以后从 BT 迁到纯 StateTree 时,迁移成本会低很多。

项目落地

旧项目迁移路线建议:

  1. 第一周只迁移一个局部分支,比如 Search 或 Combat。
  2. 保留原 BT 作为总控,避免一次性重写 AI。
  3. 每个 StateTree 分支都定义清楚输入、输出和完成状态。
  4. 不在 StateTree 内部直接改一堆 Blackboard Key。
  5. 跑稳定后,再考虑把多个 BT 分支合并成更大的 StateTree。

团队规范可以写成一句话:BT 决定“要不要进入这段流程”,StateTree 决定“这段流程内部怎么走”。

常见坑

不要让 BT Service 每帧改 StateTree 正在用的目标,同时 StateTree Evaluator 又自己算目标。不要把 BT Decorator 和 StateTree Condition 写成两套相反规则。不要让 BT Abort 中断 StateTree 时忘记清理 MoveTo、Montage 或 GameplayTask。不要把 StateTree 的失败一律当作 BT 失败;有些失败只是“这段流程不可用,回 BT 选别的分支”。

源码路径索引

  • GameplayStateTreeModule/Public/BehaviorTree/Tasks/BTTask_RunStateTree.h
  • GameplayStateTreeModule/Public/BehaviorTree/Tasks/BTTask_RunDynamicStateTree.h
  • GameplayStateTreeModule/Public/BehaviorTree/GameplayStateTreeBTUtils.h
  • StateTreeModule/Public/StateTreeReference.h