状态机 (v1.3.0)
v1.3.0 起编辑器内置状态机模式了,并且支持行为树与状态机的嵌套调用。
创建状态机
在 行为树编辑模式 下,将状态机任务 StateMachine
拖到 绘图区 即可创建一个 状态机 。
将入口任务改为状态机
编辑器的起始页面是行为树模式。
当我们需要将入口任务改为状态机时,可以简单地将 StateMachine
与 Entry
连接,使其成为 Entry
的子任务。
这样我们就得到一个顶层状态机,而非行为树了。
进入状态机编辑页面
双击 StateMachine
任务卡,进入状态机编辑页面。
设置默认启动页
加入这个选项的初衷是,当我们将入口任务设为状态机时,可以省去每次打开编辑器都要从初始页面手动跳转的步骤,直接显示状态机编辑页面。
[!Note] 当然,这只是一个例子。任何编辑页面都可以被设为启动页。编辑页面越多,越能体现出他的便利。
状态任务(State)
在 状态机编辑模式 下,将状态任务 State
拖到 绘图区 即可为状态机添加一个 状态 。
此时,状态任务是空的,我们还需要为其添加 行为任务 和 转换任务 。
状态的执行(Action)
一个状态只能设置一个行为任务,用于执行该状态具体的行为。
他可以是以下 3
种任务中的 1
种。
- 行动任务
ActionTask
,执行一个单一任务。 - 子树任务
Subtree
,以行为树的方式实现复杂的逻辑。 - 状态机任务
StateMachine
,即子状态机,实现状态机的层次化设计。
如何创建这个行为任务呢?
很简单,将他拖到绘图区里的 状态任务卡 上就可以了。
状态的转换(Transition)
一个状态可以有多个转换任务,当满足转换条件时,状态机的运行将会从当前状态,切换到转换任务所指向的目标状态。
转换任务接受一个条件任务 ConditionTask
作为测试对象,当条件任务返回成功时,触发状态转换。
如何创建这个转换任务呢?
很简单,将条件任务拖到绘图区里的 状态任务卡 上就可以了。
此时我们发现,刚刚创建的这个状态任务报了 2
个错误:
- 转换任务没有指向一个目标状态。
- 附加在转换任务上的条件任务本身报的一个错误:属性
lhs
没有被赋值。
接下来,我们再创建一个状态任务,并把转换目标指向他。
转换测试的执行时机
默认每帧执行转换测试。
根据实际需求,可以对其执行时机进行修改。
enum FsmTransitionPhaseType {
/**
* 默认值。
* 每帧执行。
*/
ALWAYS,
/**
* 当行为任务(Action)返回成功或失败时,执行。
*/
COMPLETE,
/**
* 当行为任务(Action)返回成功时,执行。
*/
SUCCESS,
/**
* 当行为任务(Action)返回失败时,执行。
*/
FAILURE,
/**
* 当行为任务(Action)返回运行时,执行。
*/
RUNNING,
/**
* 当行为任务(Action)不返回成功时,执行。
*/
NOT_SUCCESS,
/**
* 当行为任务(Action)不返回失败时,执行。
*/
NOT_FAILURE,
}
无条件转换
有这样一类转换任务,没有测试条件,当他被执行时,立即触发转换。
我们称之为无条件转换。
如何创建这个无条件转换任务呢?
很简单,将 Transition
任务拖到绘图区里的 状态任务卡 上就可以了。
这里唯一需要设置的就是 转换时机 了。
比如,当转换时机是 COMPLETE
时,表明行为任务(Action)结束后立即切换状态。
无限循环
状态机里的状态切换是自由的,只要满足转换条件就可以了,甚至可以切换到原状态本身。这带来一个问题,就是经常会一不小心就造了个无限循环的状态机出来。
例如
当一套组合拳在一帧内打完时,状态机极有可能会陷入状态转换的无限循环之中。
为方便开发者调试这个问题,状态机在运行时会对每次状态转换做一个简单的记录,当转换次数超过预设值时,强行退出状态机。
在 StateMachine
的属性检查器上可以修改这个预设值(默认值是 1000
)。
当状态机检测到疑似无限循环时,将在控制台打印日志:
此时,可以在属性检查器上开启 StateMachine
日志,
进一步查看状态切换历史栈:
预览
与行为树任务一样,状态机编辑模式里的每个任务也都有一个状态小图标,表示该任务的当前运行状态。
不同的是,状态机还需要能够表示状态的转换。
在 预览模式 中, 绿色箭头 表示最近一次状态的转换。
我们可以很直观地看出,当前状态是由哪个状态转换过来的。