17个版本 (11个破坏性版本)
0.11.0 | 2024年7月9日 |
---|---|
0.10.0 | 2024年2月21日 |
0.9.0 | 2024年1月13日 |
0.8.0 | 2023年11月12日 |
0.3.0 | 2022年11月13日 |
#57 in 游戏开发
每月147次下载
用于 2 crates
55KB
1K SLoC
seldom_state
seldom_state
是Bevy的组件化状态机插件。适用于AI、玩家状态以及其他处于各种状态的实体。它允许在实体之间更高效地复用状态逻辑,相比直接在系统中管理互斥组件。
状态是附加到实体的组件,定义其实际行为,如 Jumping
或 Stunned
。触发器是一个系统,它检查世界中的实体信息,如 near_position
或 health_below_threshold
。转换将两个状态连接起来:一个要转换的,一个要转换到的;一旦发生给定的触发器。状态机是附加到实体的组件,用于跟踪实体的转换,并根据这些转换自动更改实体的状态。
状态机的创建方式如下
commands.spawn((
// ... (other inserts)
MyInitialState::new(),
StateMachine::default()
.trans::<MyInitialState, _>(my_trigger_1, my_state_2)
.trans::<AnyState, _>(my_trigger_3, my_state_4)
.trans_builder(my_trigger_5, |my_state_6: &MyState6, trigger_data| {
make_state_7(my_state_6, trigger_data)
})
.on_enter::<MyState7>(move |entity| entity.insert(my_bundle.clone()))
.on_exit::<MyState7>(|entity| entity.remove::<MyBundle>())
// etc.
));
有关更完整的示例,请参阅 examples
目录。示例 chase.rs
写作指南风格,因此非常适合学习。如果您需要帮助,请随时在 Bevy Discord服务器 上联系我(@Seldom
)!如果您有任何改进意见,请随时提交问题或pr!
功能
- 具有用户定义状态和触发器的状态机组件
- 30个内置触发器
always
:始终触发NotTrigger
、AndTrigger
和OrTrigger:使用布尔逻辑组合触发器
done
:当将Done
组件添加到实体时触发- 由
leafwing_input
特性启用的24个更多触发器:action_data
、axis_pair
、axis_pair_length_bounds
、axis_pair_max_length
、axis_pair_min_length
、axis_pair_rotation_bounds
、axis_pair_unbounded
、clamped_axis_pair
、clamped_axis_pair_length_bounds
、clamped_axis_pair_max_length
、clamped_axis_pair_min_length
、clamped_axis_pair_rotation_bounds
、clamped_axis_pair_unbounded
、clamped_value
、clamped_value_max
、clamped_value_min
、clamped_value_unbounded
、just_pressed
、just_released
、pressed
、value
、value_max
、value_min
以及value_unbounded
on_event
:读取指定类型的事件时触发- Bevy的内置运行条件也用作触发器
AnyState
状态,可用于类型参数来表示任何状态- 允许从输出状态和触发器到输入状态的转换的转换构建器(
StateMachine::trans_builder
) - 在进入或退出状态时自动执行行为(
StateMachine::on_enter
、StateMachine::on_exit
、StateMachine::command_on_enter
和StateMachine::command_on_exit
)
与big-brain
的比较
有限状态机是游戏AI中古老且常用的模式,因此其优点和局限性都是众所周知的。它适用于以下实体:
- 没有大量相互连接的状态,因为转换的数量可以呈平方增长。然后很容易忘记添加转换,导致难以解决的错误。
- 行为僵硬,如Spelunky中的敌人,根据如被玩家跳上、等待5秒等清晰的触发器行动,对玩家来说是可预测的,而与Dwarf Fortress中的矮人不同,矮人在采取行动之前会权衡选择,显得生动。
seldom_state
是有限状态机的实现,因此可能不适合所有类型的游戏AI。如果您需要一个与更复杂的状态和转换一起工作的解决方案,那么您可能需要实现行为树(我在不分支的情况下将现有实现转换为Bevy插件时运气不佳)。如果您需要一个基于模糊逻辑的解决方案,并且不需要定义哪些转换应该允许,那么我推荐big-brain
。如果您需要模糊逻辑和离散转换,则可能需要实现模糊状态机。如果您需要离散转换,但不需要模糊逻辑,考虑使用seldom_state
!
seldom_state
不仅仅是一个AI包。因此,您可能希望为敌人的AI使用big-brain
,为玩家的状态管理使用seldom_state
,控制敌人的动画,等等。
设计模式
seldom_state
相当灵活,因此某些问题可能可以用多种方式解决。如果您感到困惑,这里有一些建议。
我有一个实体的动画取决于行为
针对这个问题,有几种解决方案。最直接的方法是将动画添加到具有 on_enter
的实体中。这对于严格遵循行为的动画系统有效,例如2D格斗游戏中的玩家控制器或基本敌人控制器。当然,这是刚性的,并且动画在状态之间必须记住的内容必须手动处理。在像Celeste这样的平台游戏中,单个状态有多个动画(假设状态有 Grounded
、Airborne
、Dashing
和 Climbing
),你可能通过 on_enter
来管理一些动画,比如冲刺动画,而通过系统来管理其他动画,比如行走循环。或者,你可以通过系统来管理所有动画。这取决于个人喜好。
另一方面,如果你的动画不太受行为限制,考虑使用多个状态机,如下一节所述。
我的实体需要同时处于多个状态
考虑一个2D平台游戏,玩家有一个剑。玩家可以跑步和跳跃,并且可以挥舞剑。所以无论你是跑步、跳跃还是冲刺,你都会以相同的方式挥舞剑,与运动状态无关。在这种情况下,你可能想要有一个运动状态机和一个攻击状态机。由于实体只能有一个状态机,你可以创建另一个具有自己状态机的实体(作为一个子实体,我建议这样做),并在 command_on_enter
和 command_on_exit
中使用闭包来捕获原始的 Entity
。
然而,也许你的状态并不那么独立。也许在冲刺时攻击会使玩家处于 PowerAttack
状态,或者攻击冷却时间在移动时不计数。根据依赖关系的规模,你可能只想让状态机通过命令和观察彼此的状态进行通信,或者你可能想将状态机组合起来,将相关状态排列成 DashAttack
和 IdleAttackCooldown
这样的状态。
此外,考虑通过状态机管理一组状态,而通过系统管理另一组状态。
我有一些其他问题,很难通过 StateMachine
API 表达
请记住,StateMachine
是基于组件的,因此你可以通过 Bevy 的 ECS 解决一些问题。你可以在系统中使用 on_enter::<MyState>
而不是 Added<MyState>
,甚至可以通过 remove
和 insert
命令手动更改状态。如果你手动更改状态,回调如 on_enter
将不会调用,并且你必须确保状态机每次只处于一个确切的状态。否则,它将引发恐慌。
使用方法
将以下内容添加到你的 Cargo.toml
# Replace * with your desired version
[dependencies]
seldom_state = "*"
有关进一步使用方法,请参阅 chase.rs
示例。
兼容性
Bevy | leafwing-input-manager |
seldom_state |
---|---|---|
0.14 | 0.14 | 0.11 |
0.13 | 0.13 | 0.10 |
0.12 | 0.11 | 0.8 - 0.9 |
0.11 | 0.10 | 0.7 |
0.10 | 0.9 | 0.5 - 0.6 |
0.9 | 0.8 | 0.4 |
0.9 | 0.3 | |
0.8 | 0.1 - 0.2 |
许可
seldom_state
可根据您的选择以 MIT 和 Apache 2.0 许可证双许可。
贡献
除非你明确说明,否则根据 Apache-2.0 许可证定义的,任何有意提交以包含在您的工作中的贡献,都将如上所述双许可,不附加任何额外条款或条件。
依赖项
~18–58MB
~1M SLoC