概述
StateTree 是一种通用分层状态机,组合了行为树中的 选择器(Selectors) 与状态机中的 状态(States) 和 过渡(Transitions) 。用户可以创建非常高效、保持灵活且井然有序的逻辑。
StateTree包含以树结构布局的状态。状态选择可以在树中的任意位置触发,但它最初从根开始。在选择过程中,将对每个状态的 进入条件(Enter Conditions) 求值。如果通过,选择将前进到该状态的子状态(如果可用)。如果没有子状态可用,将激活当前状态。
选择状态将激活从根到叶状态的所有状态。每个状态由 求值器(Evaluators) 、 任务(Tasks) 和 过渡(Transitions) 组成。
求值器包含在状态选择期间以及在StateTree Tick期间执行的逻辑。求值器提供了决策所需的数据。StateTree中的数据从根流向叶。这样一来,任意状态所公开的数据都将可用于树中向下的所有其他状态。
选择某个状态时,所选状态及其所有父状态都将激活。为所有活动状态执行所有任务,执行方式为从根开始,直至所选的状态。
每个任务都向StateTree提供一个输出。常见输出示例包括选择目标、播放动画和查看对象。每个状态可以有多个任务,只要状态保持活动,该状态中的所有任务都会并发运行。完成执行的第一个任务将触发过渡,这可能导致选择新状态。
作为一个简单的例子,你可以使用StateTree创建昼夜变换系统。你会为每个时刻创建一个状态,并会在每个状态下创建单独的任务。每个任务可以处理不同的元素,例如雾密度、天空球体颜色,等等。
另一个例子是让AI艾真体在关卡中四处行走,并在环顾四周的同时持续检查是否有打击效果。你可以使用一个状态来表示四处行走和环顾四周,另一个状态来表示对打击做出反应。
StateTree中的过渡可以指向树中的其他任意状态。过渡包含 触发器条件(Trigger Conditions) ,需要满足这些条件,过渡才会触发状态选择过程。
如果状态选择成功,将选择一个新的状态。一些过渡会被持续监控,而其他过渡仅在状态完成时执行。过渡从叶状态开始进行求值,并朝根向上推进。在此过程中,将选择第一个成功并导致状态选择的过渡。此层级允许对常见过渡进行分组。
下面是StateTree的不同元素:
图例 | StateTree元素 | 说明 |
---|---|---|
1 | 根(Root) | StateTree开始运行时选择的第一个状态。 |
2 | 求值器(Evaluator) | 提供在状态选择过程中使用的数据。 |
3 | 选择器状态(Selector State) | 指的是有子状态的状态。该状态绝不会直接被选择,选择将继续到其子状态之一。 |
4 | 状态进入条件(State Enter Condition) | 指用于确定是否可以选择某个状态的条件列表。 |
5 | 任务(Task) | 指的是属于某个状态并且在该状态激活时执行的一组操作。 |
6 | 过渡(Transition) | 定义触发状态选择过程的条件。任务完成、成功或失败时,或者某个被监控的条件达成时,会触发过渡。 |
选择流
选择新状态
StateTree以类似于行为树的方式选择活动状态。状态选择从第一个Tick上的根开始,并在树中向下继续对每个状态的进入条件求值。
- 如果进入条件未通过,选择将继续到下一个同级状态。
- 如果进入条件通过,并且该状态是叶状态,则将其选择作为新状态。
- 如果该状态有子状态,将继续对第一个子状态执行相同过程,直到找到叶状态为止。
如果一个状态有子状态,但没有一个子状态可选择(其进入条件失败),那么即使该状态的所有进入条件都通过测试,也不会选择该状态。
行为树与状态机之间的一大区别是,状态机通常在执行沿树向下推进时提交状态选择,而行为树则尝试查找合适的叶节点。
一般来说,即使已经选择一个状态,行为树也会继续执行状态选择逻辑。这是在状态之间过渡的唯一方法。
StateTree基于过渡按需运行状态选择过程。在第一个Tick上,会隐式过渡到根状态,这将选择要运行的第一个状态。选择该状态之后,过渡会指示何时及在何处执行选择逻辑。
执行状态任务
选择一个状态之后,其所有任务都将开始并发执行。任务将一直执行,直到某个过渡触发选择过程并选择某个状态为止。所选状态可以是当前状态(该状态继续执行)或新状态。
最常见的过渡触发器是 完成(completion) ,它在活动状态的第一个任务完成后立即执行。其他过渡可能标记为 条件(conditional) 并在每个Tick上进行测试。如果条件过渡通过测试,将执行状态选择逻辑,并且选择过程在目标状态处开始。如果目标状态有子状态,选择过程会将子状态视为选择逻辑的一部分。
数据流
StateTree元素可以彼此共享数据。StateTree中的不同元素可以按以下方式绑定到数据:
StateTree 元素 | 元素可以绑定到的数据 |
---|---|
进入条件(Enter Conditions) | 它们可以绑定到当前状态及所有父状态中的求值器。 |
过渡条件(Transition Conditions) | 它们可以绑定到当前状态及所有父状态中的求值器和任务。 |
求值器(Evaluators ) | 它们可以绑定到当前状态及父状态中可用的其他求值器。 |
任务(Tasks) | 它们可以绑定到当前状态及父状态中的求值器和其他任务。 |
蓝图集成
StateTree设计为通过蓝图脚本编写进行扩展。你可以通过扩展以下蓝图类来创建自定义任务、求值器和条件:
基类 | 说明 |
---|---|
UStateTreeTaskBlueprintBase | StateTree任务的基类。 |
UStateTreeEvaluatorBlueprintBase | StateTree求值器的基类。 |
UStateTreeConditionBlueprintBase | StateTree条件的基类。 |
在上述例子之后,你可以使用StateTree和蓝图创建昼夜变换系统。此系统会根据时间更改关卡的雾密度。该系统还可以检查是否有风暴咒语,以便更改光照条件且不受时间影响。
对于这个例子,你可以创建以下蓝图类:
蓝图类 | 功能 |
---|---|
任务(Task) | 随时间推移逐渐更改雾密度。该任务可以添加到表示特定时刻的所有状态。 |
条件(Condition) | 检查是否施了风暴咒语。该条件可以在根状态处求值。如果为true,它会将执行移至特别的风暴状态。 |
求值器(Evaluator) | 将时间公开给过渡和进入条件。该求值器可以在根状态处求值,以确定要选择的正确状态。 |
常见模式
对类似任务分组

任务可以分组在一个公共状态下。在上述例子中,有一个状态用于处理世界中的智能对象。该状态包含用于处理"伸展智能对象"和"使用智能对象"的子状态。
其中每个子状态都包含在选择该状态时执行的任务。"伸展"子状态包含用于查找智能对象并将AI艾真体移至智能对象的任务。
当你使用此分组策略时,所有任务都通过该状态共享相同的过渡。
序列

StateTree包含 下一个(Next) 过渡,它简化了状态序列的创建和布局。
在上述例子中,选择 伸展(Reach) 状态时,将执行 查找SO目标(Find SO Target) 、 移至SO(Move to SO) 和 查看(Look) 任务。这些任务完成后 ,下一个(Next) 过渡会将执行移至下面的 使用(Use) 状态,其中的任务可以开始执行。
失败处理

StateTree以分层方式处理任务完成失败,从活动任务开始,并沿树向上推进。
在上述例子中, 伸展插槽(Reach Slot) 状态会在成功时将执行移至下一个状态(等待),或在失败时将执行移至其父状态(在相交处等待)。 在相交处等待(Wait at Intersection) 状态会在其任意子状态失败时触发过渡到 空闲(Idling) 状态。
等待(Wait) 状态会在成功或失败时将执行无限期移至自身,直到其父状态选择不同的状态为止。
分层数据

任务可以彼此共享数据。任务公开的数据将可用于属于活动状态的其他任意任务。这可提高StateTree中资源处理的效率。
在上述例子中,群体声明等待插槽(Crowd Claim Wait Slot) 任务将尝试为AI艾真体声明智能对象插槽,如果成功,它会将执行传递给 移至等待插槽(Move To Wait Slot) 任务。该任务将使用父任务中的插槽位置。如果成功,它会将执行传递给 在插槽处等待(Wait At Slot) 任务,它还将使用其父任务中的插槽位置。
优化行为

StateTree提供了一种组织任务的方法,以便轻松实现上下文行为。
在上述例子中,等待(Wait) 状态处理AI艾真体的站立移动——AI艾真体环顾四周并在被击中时做出反应。默认情况下,将执行 等待查看(Wait Look) 状态。如果该状态成功,它会将执行返回给父状态。但是,如果失败,它会将执行移至 等待打击(Wait Hit) 状态。
等待打击(Wait Hit) 状态将执行 质量查看(Mass LookAt) 和 质量上下文动画(Mass Contextual Anim) 任务。然后,这些任务将播放打击的相应动画。