6个版本
0.1.5 | 2024年7月21日 |
---|---|
0.1.4 | 2024年7月5日 |
0.1.3 | 2024年2月20日 |
0.1.2 | 2023年11月6日 |
0.1.0 | 2023年4月19日 |
#278 在 游戏开发
150 每月下载次数
200KB
554 行
🎚️ Moonshine Behavior
Bevy游戏引擎的极简主义状态机。
概述
这个Crate旨在为Bevy实体提供简单、基于堆栈的状态机实现。
特性
- 简单:定义和设置行为时的开销最小。
- 行为可以启动、暂停、恢复和停止。
- 事件驱动API,允许系统对实体上的行为变化做出反应。
- 同一实体上可以存在多个不同类型的行为,以定义复杂的状态机。
用法
行为通常实现为一个枚举(enum
),是一个Component
,代表其实体的某种状态。每个行为都与一个堆栈相关联。当启动下一个行为时,当前行为将被推入堆栈(如果可恢复)并暂停。
设置
1. 定义你的行为数据为一个Component
use bevy::prelude::*;
use moonshine_behavior::prelude::*;
#[derive(Component, Default, Debug, Reflect, FromReflect)]
#[reflect(Component)]
enum Bird {
#[default]
Idle,
Fly,
Sleep,
Chirp,
}
由于行为代表一组有限的状态,它们通常实现为枚举(enum
)。这不是强制要求。任何struct
都可以用来表示行为数据,例如
#[derive(Component, Default, Debug, Reflect, FromReflect)]
#[reflect(Component)]
struct Bird {
flying: bool,
sleeping: bool,
chirping: bool,
}
你甚至可以使用嵌套枚举或结构体来表示复杂的状态机
#[derive(Component, Default, Debug, Reflect, FromReflect)]
#[reflect(Component)]
enum Bird {
#[default]
Idle,
Fly(Fly),
Sleep(Sleep),
Chirp(Chirp),
}
#[derive(Default, Debug, Reflect)]
enum Fly {
#[default]
Normal,
Hunt,
Flee,
}
#[derive(Default, Debug, Reflect)]
struct Sleep {
duration: f32,
}
#[derive(Default, Debug, Reflect)]
struct Chirp {
count: usize,
}
2. 实现Behavior trait
impl Behavior for Bird {
fn allows_next(&self, next: &Self) -> bool {
use Bird::*;
match self {
Idle => matches!(next, Sleep | Fly | Chirp),
Fly => matches!(next, Chirp),
Sleep | Chirp => false,
}
}
}
这个trait定义了你的行为的可能转换。在这个例子中
- 一只鸟在空闲时可以睡觉、飞翔或鸣叫
- 一只鸟在飞翔时可以鸣叫
- 当睡觉或鸣叫时,一只鸟可能不会做其他任何事情
3. 注册Behavior及其转换
将BehaviorPlugin<T>
添加到你的App
中,以注册行为事件和类型。使用transition()
系统在需要时触发行为转换。
app.add_plugins(BehaviorPlugin::<Bird>::default())
.add_systems(Update, transition::<Bird>);
你可以在transition系统之前或之后定义你的系统。通常,导致行为改变的系统应该在转换之前运行,而处理行为逻辑的系统应该在转换之后运行。
4. 生成BehaviorBundle
要使行为系统运行,您必须使用 BehaviorBundle
插入您的行为。此包还会插入您行为的一个实例。这被称为 初始行为。
fn spawn_bird(mut commands: Commands) {
commands.spawn(BehaviorBundle::<Bird>::default());
}
要生成具有特定初始行为的小鸟,请使用 BehaviorBundle::<B>::new()
。
⚠️ 警告
初始行为可能永远无法停止。这样做将触发错误。
转换
使用 BehaviorRef
和 BehaviorMut
世界查询可以查询带有 BehaviorBundle
生成的新实体。
BehaviorRef
可用于读取当前/之前的行为。BehaviorMut
可用于读取当前/之前的行为并请求行为转换。
要访问当前行为,请使用 Deref
/DerefMut
或 get
/get_mut
操作,对 BehaviorRef
或 BehaviorMut
中的任何一个进行操作。要访问之前的行为,请使用 .previous()
。
例如
fn is_chirping_while_flying(bird: Query<BehaviorRef<Bird>>) -> bool {
let behavior = bird.single();
matches!(*behavior, Chirp) && matches!(behavior.previous(), Some(Fly))
}
要开始某些后续行为,请使用 .try_start()
fn chirp(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().try_start(Chirp);
}
要停止当前行为并恢复上一个行为,请使用 .stop()
fn stop(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().stop();
}
要停止当前行为并恢复初始行为,请使用 .reset()
fn reset(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().reset();
}
当请求转换时,它不会立即调用。相反,它将在注册的 transition()
系统运行时调用。您可以在 transition()
之前或之后注册您的系统,以执行所需的任何逻辑。
⚠️ 警告
请注意,每个实体在每个应用程序更新中只能调用一次转换。这是一个有意的设计选择。如果在同一更新周期内对同一实体请求多个转换,则仅调用最后一个,并记录警告。
事件
每次调用转换时,都会发出相关事件。这些事件可以被其他系统用来对行为变化做出反应。
每个事件(除了 StoppedEvent
)仅携带启动、暂停或恢复行为所涉及的实体 ID。 StoppedEvent
除了实体 ID 之外,还携带停止的行为数据。
对于 StartedEvent
和 ResumedEvent
,行为存在于实体本身。您可以使用正常查询(例如 Query<&Bird>
)或使用 BehaviorRef
来访问它。
fn on_chirp_started(mut events: Started<Bird>, query: Query<BehaviorRef<Bird>>) {
for event in events.iter() {
let entity = event.entity();
let behavior = query.get(entity).unwrap();
if let Chirp = *behavior {
info!("{entity:?} has started chirping!");
}
}
}
fn on_chirp_resumed(mut events: Resumed<Bird>, query: Query<BehaviorRef<Bird>>) {
for event in events.iter() {
let entity = event.entity();
let behavior = query.get(entity).unwrap();
if let Chirp = *behavior {
info!("{entity:?} is chirping again!");
}
}
}
对于 PausedEvent
,暂停的行为是数据上的上一个行为,可以通过 .previous()
访问。
fn on_chirp_paused(mut events: Paused<Bird>, query: Query<BehaviorRef<Bird>>) {
for event in events.iter() {
let entity = event.entity();
let behavior = query.get(entity).unwrap();
if let Chirp = behavior.previous() {
info!("{entity:?} is no longer chirping.");
}
}
}
对于 StoppedEvent
,停止的行为可以通过事件本身访问
fn on_chirp_stopped(mut events: Stopped<Bird>) {
for event in events.iter() {
let entity = event.entity();
let behavior = event.behavior();
if let Chirp = *behavior {
info!("{entity:?} has stopped chirping.");
}
}
}
激活/挂起
在某些情况下,如果某个行为被暂停或停止(挂起),或开始或恢复(激活),可能需要运行一些逻辑。
为了处理激活和挂起,您可以使用标准的 Changed
查询
fn on_chirp_activated(query: Query<BehaviorRef<Bird>, Changed<Bird>>) {
if let Ok(behavior) = query.get_single() {
if let Chirp = *behavior {
info!("{entity:?} is chirping!");
}
}
}
fn on_chirp_suspended(query: Query<BehaviorRef<Bird>, Changed<Bird>>) {
if let Ok(behavior) = query.get_single() {
if let Chirp = behavior.previous() {
info!("{entity:?} is not chirping.");
}
}
}
示例
请参阅 bird.rs 以查看 Bird
行为的完整实现。
支持
在 Bevy Discord 服务器上找到我,或发布一个问题。
依赖项
~11MB
~195K SLoC