UE5.8 StateTree 状态树专题系列

UE5.8 StateTree 专题(三):State、Task、Condition、Transition 四件套

用白话讲 State、Task、Condition、Transition 的职责、执行时机、父子状态和选择行为,让新手能看懂编辑器面板。

总览

StateTree 新手最容易把所有东西都叫“节点”。这会让你调试时完全乱掉。请先记住四件套:State 是状态,Task 是状态里做的事,Condition 是判断,Transition 是跳转规则。你可以把它想象成房间:State 是房间,Task 是你在房间里做的事,Condition 是门口检查,Transition 是什么时候去另一个房间。

UE5.8 StateTree 专题(三):State、Task、Condition、Transition 四件套 配图
State 是房间,Task 是房间里做的事,Condition 是门口检查,Transition 是什么时候去另一个房间。

State:我现在处于什么状态

State 是行为的大标题,例如 Idle、Patrol、Alert、Chase。一个 State 可以有子 State。父 State 适合放公共逻辑,例如 Combat 父状态里放“拔武器”或“锁定目标”的 Global-like Task,子状态再区分 Melee、Ranged、Retreat。

新手建议:状态名用动词或状态短语,不要叫 State1、State2。推荐:

好名字 不好名字
IdleAtPost Wait
PatrolRoute Move
ConfirmTarget Check
ChasePlayer Attack1

Task:进入状态后做什么

Task 是动作。Move To 是 Task,Wait 是 Task,播放蒙太奇可以是 Task,写一个变量也可以是 Task。Task 可以返回 Running、Succeeded、Failed。返回 Succeeded/Failed 后,StateTree 会认为这个状态的任务完成了,然后处理 OnStateCompleted 之类的 Transition。

初学时牢记:Task 不负责决定“下一步去哪”,它只负责把当前状态的事情做好。下一步去哪交给 Transition。

Condition:是否允许

Condition 是判断。比如“距离玩家小于 1200”“是否看到玩家”“血量是否小于 30%”。Condition 可以出现在进入状态时,也可以出现在 Transition 上。常见内置条件包括 Bool Compare、Float Compare、Integer Compare、Distance Compare、Gameplay Tag 相关条件。

Condition 不应该修改世界。源码也提醒 Condition 的实例数据在一些回调里是共享使用的,不应该随便改。

Transition:什么时候离开

Transition 是跳转规则。常用触发:

Trigger 意思 新手例子
On State Succeeded 当前状态成功完成 Wait 结束后去 Patrol
On State Failed 当前状态失败 Move To 失败后去 Search
On Tick 每次 Tick 都检查 如果看到玩家就去 Alert
On Event 收到事件时检查 听到声音事件去 Investigate

Transition 的目标可以是指定 State,也可以是 Next State、Parent、Succeeded、Failed。新手阶段尽量先用明确的 Goto State,等熟悉后再用 Next/Parent 简化流程。

源码依据

FStateTreeTaskBase 提供 EnterStateTickExitStateStateCompletedFStateTreeConditionBase 提供 TestCondition 和表达式 Operand。EStateTreeTransitionTrigger 定义 OnStateCompleted、OnStateSucceeded、OnStateFailed、OnTick、OnEvent、OnDelegate。EStateTreeTransitionType 定义 None、Succeeded、Failed、GotoState、Parent、NextState 等跳转类型。

架构分析

这四件套对应项目里的四种责任:State 负责流程分段,Task 负责执行副作用,Condition 负责只读判断,Transition 负责状态迁移。把责任分清后,代码 Review 会简单很多:如果 Condition 里改了变量,就越界;如果 Task 里偷偷决定下一个状态,也越界;如果 Transition 条件里写了一堆业务计算,就应该考虑 Evaluator。

使用案例

给守卫加入 Alert 和 Chase:

State Task Transition
Patrol Move To PatrolPoint OnTick + CanSeePlayer -> Alert
Alert Wait 0.5s,Face Player OnSucceeded + CanSeePlayer -> Chase
Alert Wait 0.5s,Face Player OnSucceeded + !CanSeePlayer -> Patrol
Chase Move To PlayerLocation OnTick + LostPlayer -> Search

这里 CanSeePlayer 是 Condition,不是 Task。Face Player 是 Task,不是 Condition。Patrol -> Alert 是 Transition,不是 State。

项目落地

每个状态先写一句话:“进入这个状态后,这个 AI 正在做什么?”如果一句话里出现“如果……就……否则……”,说明你把 Transition 或 Condition 混进了 State 描述。先拆清楚,树会自然变干净。

常见坑

不要用一个超大的 State 里面塞十几个 Task,再靠 Task 内部 if/else 做所有决策。不要让 Condition 做副作用,比如设置目标点。不要在每个子状态重复相同的 Transition,如果它是所有子状态都适用的规则,可以放到父状态上。不要忽略 Failed 路径,Move To 不可达时没有 Failed Transition,AI 很容易卡住。

源码路径索引

  • StateTreeModule/Public/StateTreeTypes.h
  • StateTreeModule/Public/StateTreeTaskBase.h
  • StateTreeModule/Public/StateTreeConditionBase.h
  • StateTreeModule/Public/StateTreeExecutionTypes.h