总览
StateTree 真正难的不是“画几个状态”,而是“状态判断需要的数据从哪里来”。守卫要不要追玩家,得知道是否看见玩家、玩家位置、最后看见的位置、距离、巡逻点、警戒时间。这些数据不能都藏在 Task 里乱读,否则树看起来很漂亮,调试时却不知道每个条件用的值从哪来。
先分四类数据
| 数据来源 | 它是什么 | 守卫案例 |
|---|---|---|
| Parameters | 外部配置给整棵树的参数 | 视野距离、巡逻等待时间 |
| State Parameters | 某个状态自己的参数 | PatrolA 的目标点 |
| Evaluator | 每帧计算并暴露给树的数据 | CanSeePlayer、PlayerDistance |
| Global Task | 随树长期运行的后台任务 | 监听感知事件、缓存最后目标 |
新手先记:参数偏“配置”,Evaluator 偏“计算出来的事实”,Task 偏“要做的动作”。
Parameters 怎么用
在 StateTree 资产里添加 Global Parameters,例如:
| 参数名 | 类型 | 默认值 |
|---|---|---|
| SightRange | Float | 1500 |
| LoseSightRange | Float | 2200 |
| IdleDuration | Float | 2 |
| HomeLocation | Vector | 岗位位置 |
这些值可以在不同守卫实例里覆盖。比如普通守卫视野 1500,精英守卫 2200。不要把这种可调数值硬编码在 Task 里。
Evaluator 怎么理解
Evaluator 像一个数据计算器。它不表示一个状态,也不表示一个动作。它在树运行时更新数据,供 Condition、Task、Binding 使用。例如 GuardPerceptionEvaluator 每帧算:
| 输出 | 含义 |
|---|---|
| bCanSeePlayer | 视线或感知是否看到玩家 |
| PlayerLocation | 玩家当前位置 |
| LastSeenLocation | 最后一次看到玩家的位置 |
| DistanceToPlayer | 守卫到玩家距离 |
然后 Transition 可以用 bCanSeePlayer,Move To 可以用 PlayerLocation 或 LastSeenLocation。
源码依据
FStateTreeEvaluatorBase 注释说明 Evaluator 用来计算并暴露 StateTree 决策数据,提供 TreeStart、TreeStop、Tick。UStateTree 中有 GetGlobalEvaluatorsBegin、GetGlobalEvaluatorsNum、GetGlobalTasksBegin 等访问运行时节点区间的接口。FStateTreeExecutionContext::FStartParameters 支持初始 Global Parameters。UE5.8 里旧的 Start(const FInstancedPropertyBag*) 已 deprecated,推荐使用 FStartParameters。
架构分析
数据层可以按生命周期分三类:配置型数据放 Parameters,例如巡逻半径;每帧派生数据放 Evaluator,例如是否看见目标;执行过程中的临时状态放 Task InstanceData,例如等待剩余时间。不要把这三类混在一起,否则后期会分不清“这是设计师配置的值,还是运行时算出来的值”。
使用案例
Blueprint 版本的喂饭流程:
- 在 StateTree 里添加 Evaluator,命名
GuardPerception。 - 给 Evaluator 添加输出变量:
CanSeePlayer、PlayerLocation、LastSeenLocation。 - 在 Patrol 的 Transition 上添加 Bool Compare。
- 把
GuardPerception.CanSeePlayer接到 Bool Compare 的 Left。 - Right 设为 true。
- Target State 设为 Alert。
C++ 概念长这样:
USTRUCT()
struct FGuardPerceptionEvaluator : public FStateTreeEvaluatorCommonBase
{
GENERATED_BODY()
using FInstanceDataType = FGuardPerceptionData;
virtual const UStruct* GetInstanceDataType() const override
{
return FInstanceDataType::StaticStruct();
}
virtual void Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const override
{
FGuardPerceptionData& Data = Context.GetInstanceData(*this);
// 从感知组件或 Pawn 计算 CanSeePlayer、PlayerLocation。
}
};
项目落地
给团队定一条规则:Transition 使用的数据,优先来自 Evaluator 或参数,不要来自某个 Task 的隐藏临时变量。Task 的输出如果要被别处用,就显式做成 InstanceData 输出,再用 Binding 接出去。这样调试时能在 StateTree 面板看到数据流。
常见坑
不要把所有变量都做成 Global Parameter。运行时计算的数据应该放 Evaluator 或组件里。不要让 Evaluator 做昂贵查询,每帧 LineTrace 一百次会很快出问题。不要在多个 Evaluator 里重复算同一个玩家距离。不要忘记默认值,编辑器里没绑定时默认值会参与判断。
源码路径索引
StateTreeModule/Public/StateTreeEvaluatorBase.hStateTreeModule/Public/StateTreeTypes.hStateTreeModule/Public/StateTreeExecutionContext.hStateTreeModule/Public/StateTreeInstanceData.h