总览
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 提供 EnterState、Tick、ExitState、StateCompleted。FStateTreeConditionBase 提供 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.hStateTreeModule/Public/StateTreeTaskBase.hStateTreeModule/Public/StateTreeConditionBase.hStateTreeModule/Public/StateTreeExecutionTypes.h