实体组件系统中的有限状态机实现

时间:2016-08-27 19:49:31

标签: oop architecture entity state-machine

我想使用有限状态机来处理游戏中的Entity个状态。具体来说,就本文而言,我将引用Player实体。

我的Player将会有空闲,跑步,跳跃,摔倒等状态......并且需要一些方法来管理这些状态以及它们之间的转换。在OOP环境中,最简单的解决方案是使每个状态成为自己的类,并使用一个名为handleInput的方法接收输入并确定是否应该发生状态更改。例如,在IdleState中,如果发生move_right或move_left,则状态将更改为新的RunningState。这很容易并且有意义,因为状态的行为应该封装在状态中。

但是,在实体组件系统中使用FSM时,一切都会发生变化。状态不再是对象(因为这会违背组件系统的灵活性),而是组件的不同排列。 JumpState可能包含JumpComponentAirbornMovementComponent等组件......而AttackState可能包含代表SwingComponent,{{1}等攻击的组件}},DamageComponent等...这个想法是通过重新排列组件,可以创建新的状态。系统工作是单独处理这些组件,因为系统不关心状态,他们只关心单个组件。实际的FSM位于实体持有的SwordComponent中。

除了处理状态转换之外,这很有意义。现在我有FSMComponent查找具有InputSystemInputComponent的实体,并尝试根据当前输入更新FSM的状态。但是,这并没有那么好。

FSM处理输入的最佳方式(在我看来)是让每个状态确定它想要如何处理输入以及如何根据该输入转换到新状态。这可以追溯到实施FSM的OOP方式,违背了ECS的设计,其中组件只是数据包,系统完成所有逻辑。在ECS中,想法是让系统处理状态转换,但这会变得复杂,因为每个FSM可能具有不同的条件来在状态之间转换。

您不能简单地在FSMComponent"中说明,如果输入是向右移动,则将状态设置为运行"。这将特定于播放器,但可能不适用于 所有 实体。如果有一天我决定使敌人可控制,那么适用于InputSystem的输入将不会是Player的相同输入。

我的问题:如何让我的FSM在ECS中具有足够的通用性和足够的灵活性,以允许各种状态转换实现,而无需在系统本身进行明确的if / else检查?

我完全走错了路吗?如果是这样,那么在实体组件系统中实施FSM的更好的解决方案是什么?

2 个答案:

答案 0 :(得分:2)

刚刚发过@fallaciousreasoning的帖子(以及随后的评论)。

Ash实际上有两个FSM在不同的级别运行。

首先,FSM在实体级别运行,通过在从一个状态转换到另一个状态时更改实体上组件的组成来管理(实体)状态转换。

其次,FSM在引擎级别运行,通过更改引擎执行的系统组成来管理(引擎)状态转换。

它们结合起来构成了一个非常强大的FSM。

诀窍是确定您需要使用哪种类型的转换; “数据”组合驱动转换的一个,或“逻辑”组合驱动转换的一个。

因此,掌握这些新发现的知识,可以说明这将如何发挥作用。

在更天真的改变组件组成的方法中,我们使用了许多“标签”组件,以及一些冗长的switch语句(或if / else检查)来处理实体状态中的这些更改,最终导致系统崩溃这比他们应该做的更多。 Ash的实体FSM通过将给定的组件配置映射到标识符并提供可用于触发状态转换的管理器来改进这一点并避免那些冗长的switch语句(或if / else子句)。此管理器实例可以作为组件中的属性传递,也可以作为系统成员组合/注入。

或者,采用Engine FSM方法,我们将每个状态特定逻辑分解到它们自己的系统中,并根据给定(引擎)状态交换它们。这种方法并非没有缺点,因为缺少系统会影响与之相关的所有实体。但是,将系统专用于单个实体实例(例如,玩家角色)并不罕见,因此这可以在正确的上下文中证明是有用的。在某些情况下,可能需要考虑通过系统交换来影响全球实体。

注意:如果您的逻辑修改范围需要更窄,您可以将其限制在系统中而不涉及引擎FMS。这可以通过在系统中实现系统中的状态模式来实现可以通过基于状态委托给不同的子系统来改变其行为。

我已经看到一些ECS框架将系统组成标记为“阶段”,但它们从根本上充当FSM,其中ECS引擎使用与给定阶段关联的不同系统集来处理实体。

最后,数据构成只是等式的一半;在尝试在ECS中实现FSM时,如何编写逻辑位(或块)同样重要。

答案 1 :(得分:0)

ash框架有一个非常有趣的方法,作者写了here

他采用了与你所建议的方法类似的方法,认为实体的状态依赖于构成它的组件(这反过来决定了哪些系统处理实体)。

为了更容易地管理状态转换,他引入了一个FiniteStateMachine类,它跟踪实体处于不同状态所需的组件。

例如,如果敌人AI具有TransformPatrol组件,则可能处于巡逻状态,因此他会使用FiniteStateMachine注册该信息

 var fsm = new FiniteStateMachine(enemyEntity);

 fsm.CreateState("patrol")
    .WithComponent(new Transform())
    .WithComponent(new Patrol());

当有限状态机被告知要改变实体的状态时,它会移除并添加组件以达到所需的状态。任何需要更改实体状态的系统只需要引用有限状态机(类似fsm.ChangeState("patrol"))。

他还发布了它的源代码,你可以找到here(它也比我更好地解释它),并且在基本的小行星游戏中有一个实际的例子{ {3}}。代码是在ActionScript中(我认为),但你应该能够毫不费力地解密它。