状态机与行为树
全库目录221
本文目录11
守卫从追击切到攻击时,旧移动需要停止,攻击才能取得资源。本页分别展开独立状态机和模式内行为树的生命周期;两者共享决策输入与动作交接中的命令、动作实例和取消协议。独立配置由状态直接登记动作,组合配置由模式内的树叶登记动作。
状态机与层次切换
状态进入、更新与退出
本页以守卫巡逻、交战和撤退为例。有限状态机(FSM)用活动状态和转移条件表达待机、巡逻、追击或攻击。守卫从巡逻转向追击,改变的是活动路径及其动作所有权;敌人的位置更新则留在当前追击状态的普通更新里。状态进入时建立本次执行上下文,更新观察同一执行,退出负责终止它。下面以独立配置展开这次转移。规则脚本也可经动作适配层执行:按明确优先级选择首个满足条件的规则,再交给动作账本。
层次状态机(HFSM)把共享行为放在父状态。以下配置中,战斗父状态负责目标跟踪,追击和攻击状态分别经适配层拥有移动与武器请求。
根
├─ 平静
│ ├─ 巡逻
│ └─ 安全待机
└─ 战斗
├─ 追击
├─ 攻击
├─ 搜寻
└─ 撤退在这份独立配置中,从战斗/追击到战斗/攻击,最近公共祖先是战斗。先让追击取消移动,等待资源释放,再完成追击退出;战斗保持活动,最后进入攻击。等待期间HFSM保存目标路径并处于转移中,旧状态不再发新请求,新状态尚未活动。紧急事件可以修改待进入目标,退出已经开始的状态仍须完成清理。
转移记录保存实际活动路径、正在退出的状态和最新目标路径。目标改变只替换目标;正在退出的状态继续清理,完成后才从活动路径末端删除。随后以剩余活动路径与最新目标重新求公共前缀,逐个退出公共前缀之后的状态,再从外到内进入目标后缀。每完成一次退出或进入都重新检查目标代次,已经完成的退出不会重复调用。
例如追击正在退出,目标由战斗/攻击改为平静/安全待机。追击清理完成后活动路径是根/战斗,与新目标的公共前缀只有根,因此继续退出战斗,再进入平静和安全待机。若目标改回追击,旧追击也先退出,再创建新实例;取消中的动作不能恢复所有权。目标在Enter等待期间改变时,先让该Enter成功或失败清理结束;成功状态记入实际路径,再按最新目标决定是否立即退出。调度入口串行处理目标变化与完成消息,使这份路径始终对应实际持有的资源。
进入失败与恢复路径
独立FSM配置从平静/巡逻转到战斗/攻击,公共祖先是根。退出顺序为巡逻、平静,进入顺序为战斗、攻击。这份配置的Enter可通过适配层等待动作初始化,每个成功进入的状态记入本次清单;Enter失败的状态先撤销自身已取得的部分资源,异步撤销完成以前保持转移中。只有成功进入的状态才调用配对Exit,失败Enter使用自己的失败清理,避免同一资源释放两次。
假设战斗进入成功,攻击因目标销毁而失败。先完成攻击的部分初始化清理,再退出本次成功进入的战斗,回到仍活动的根。若指定的失败目标是平静/安全待机,就依次进入平静和安全待机。原巡逻的移动已被取消,因此不能把旧巡逻标记重新设为活动来假装恢复原状。安全待机自身无外部预约,作为这条路径的确定终点;它也不可用时保留根的错误状态并报告配置问题。
同一父状态内失败时,清单更短:追击已退出,攻击进入失败,公共祖先战斗仍活动。失败策略选择战斗/搜寻,只需进入搜寻。清理只撤销本次进入部分,公共祖先是否退出由实际路径决定,不能一律退到根或保留已经退出的旧叶。
行为树与节点生命周期
在组合配置中,层次状态机负责模式,行为树负责模式内部步骤。交战模式确定后,守卫仍需依次检查目标、接近和攻击。状态标签本身没有保存“接近还没完成,攻击尚未开始”这份进度,行为树把它放在活动节点与子节点索引中。
组合语义
行为树把条件和动作放在叶节点,由组合节点决定遍历顺序。顺序节点需要所有子节点成功;遇失败即结束。选择节点按优先级尝试子节点,失败才尝试下一个,成功即结束。两者遇到运行中的子节点都保留该分支。装饰节点可以增加门禁、循环或冷却,但同样遵守子节点的进入和取消规则。
记忆型顺序节点在动作跨帧运行时保存索引;反应式顺序节点每轮从前面的条件重新检查。反应式检查若改变了活动路径,先中止原动作,再进入新的冲突分支。只把索引归零会让旧追击继续占用移动器,同时再次创建另一次追击。
叶节点的四种状态
节点生命周期和对父节点的结果分别保存。生命周期为Idle、Running、Cancelling、Finished;终态结果为Success、Failure或Cancelled。Running与Cancelling对外都表示尚未结束,父节点还要看自身是否正在中止,不能把“等待取消”当成继续执行原计划。
| 生命周期 | 接收普通更新 | 接收中止 | 进入下一状态 |
|---|---|---|---|
| Idle | 调用一次Enter,登记命令后保存命令ID | 直接以Cancelled结束,无外部资源 | 登记后到Running,先等待接纳;接纳后保存实例ID |
| Running | 等待同一命令的接纳,或查询已接纳实例;终态时结清一次 | 对原命令或实例发一次幂等取消,停止普通更新 | 正常完成到Finished;中止到Cancelling |
| Cancelling | 只处理取消、迟到完成与资源释放 | 合并重复请求,不重复创建动作 | 确认资源已释放后完成一次清理,到Finished,保存取消或进入失败结果 |
| Finished | 返回缓存终态,不再Enter或Exit | 保持已有终态 | 父节点消费终态后显式Reset到Idle |
Running内的等待接纳阶段没有实例ID,普通更新只查询同一命令,不再调用Enter。此时中止也要进入Cancelling,向宿主提交针对原命令ID的取消;宿主若尚未接纳,就保存该命令的取消终态,迟到的原命令取得同一终态;已经接纳则取消原实例。确认取消回执以前不复位节点,避免原命令迟到接纳后成为无人跟踪的动作。
Enter被拒绝时必须保证没有残留预约。若初始化已经部分取得资源,节点进入Cancelling并保存待结束结果Failure,沿同一执行账本撤销并等待释放;清理完成才进入Finished(Failure),不能先报失败让父节点启动下一动作。失败Enter执行自身失败清理,成功Enter的实例才调用配对Exit;独立FSM的动作适配层也沿这条规则撤销部分初始化。普通运行中止的待结束结果则是Cancelled。
动作在取消请求之前已经成功时,可以正常结清Success;父节点若已进入Cancelling,也不会据此启动下一动作。取消后到来的成功只说明底层已停止,由取消路径统一结清为Cancelled。事件处理在调度入口串行排序,这个先后关系才可重放。
顺序节点的父子推进
下面是记忆型顺序节点的机制伪代码。叶节点Tick执行上表,组合节点在Idle进入Running时完成无外部资源的Enter;Finish缓存结果,并为已经成功进入的本节点执行Exit一次。未进入就被中止的节点直接保存Cancelled,不调用Exit。每个实例一次只运行一个孩子,组合节点自身不操作角色。
TickSequence:
if state == Finished: return savedResult
if state == Idle:
state = Running; index = 0
if state == Cancelling:
if active child exists:
poll its cancellation until child is Finished
if child is not Finished: return Running
reset every Finished child to Idle
index = 0
return Finish(Cancelled)
while index < childCount:
result = child[index].Tick()
if child[index] is not Finished: return Running
if result != Success:
reset every Finished child to Idle
index = 0
return Finish(result) // Failure与Cancelled原样向上传播
index += 1
reset every Finished child to Idle
index = 0
return Finish(Success)
AbortSequence:
if state == Finished or state == Cancelling: return
if state == Idle:
savedResult = Cancelled; state = Finished
return
state = Cancelling
request Abort on the active child, if any组合节点中止先标记自己Cancelling,再向活动子节点传播。子节点内部若还是组合节点,就继续向下传播;结束则由叶到父逐层确认。父节点等待整个活动子树终止后才复位已结束孩子,自己变为Finished,最后由更上层消费并复位。未进入的Idle兄弟保持Idle,已成功的兄弟只复位而不重复Exit。选择节点沿同样生命周期推进,但在正常运行中遇Failure时递增索引,遇Success结束;Cancelled表示执行被打断,结束当前选择,不把它当成普通失败立刻启动下一孩子。
即使父节点仍为Running,子节点也可能因执行器撤销而返回Cancelled。顺序节点原样传播这个终态,选择节点同样结束当前选择。只有Failure允许选择节点尝试下一孩子;条件重评估由上层显式复位后发起。因此执行器撤销、祖先中止和普通失败不会混成同一条备用动作路径。
例如顺序为“目标有效、接近、攻击”。接近处于Running时目标失效,父节点先进入Cancelling;接近叶等待移动器取消完成,期间攻击始终Idle。完成后接近叶Exit、父节点复位已结束子节点并输出Cancelled。上层重新进入这条顺序时,目标条件才会重新求值,接近叶不会沿用上一次Finished结果。
条件观察与调度
每帧遍历能发现条件变化,代价随NPC数量和树规模增长。观察者可在相关黑板键变化时请求重评估,Running和Cancelling节点仍须收到执行结果。事件派发与本轮快照在固定边界交接,条件变化不会直接从任意线程重入树。
Unreal的Observer Aborts把中止范围分为Self、Lower Priority、Both和None,分别作用于自身分支、较低优先分支、两者或关闭观察中止;具体执行仍由引擎调度。这些选项说明要停哪条路径,动作实例和资源协议说明何时真正停完。Epic装饰节点说明可对照宿主语义。
实现与证据
本文为机制设计与状态推演,完整NPC框架、异步动作和持久恢复尚未接入运行。相关机制片段的局部验证见效用评分与目标规划。
相关主题
参考资料
- Epic Behavior Tree Node Reference:Decorators:Observer Aborts的Self与Lower Priority语义;具体实现随引擎版本核对。
本页节点生命周期和取消结果为显式参考语义,宿主动作交接见决策输入与动作交接。