#状态机 #行为 #极简主义 #bevy #实体 #组件 #游戏引擎

moonshine-behavior

Bevy游戏引擎的极简主义状态机

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游戏开发

Download history 1/week @ 2024-05-17 3/week @ 2024-06-28 127/week @ 2024-07-05 2/week @ 2024-07-12 120/week @ 2024-07-19 29/week @ 2024-07-26 1/week @ 2024-08-02

150 每月下载次数

MIT 协议

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()

⚠️ 警告
初始行为可能永远无法停止。这样做将触发错误。

转换

使用 BehaviorRefBehaviorMut 世界查询可以查询带有 BehaviorBundle 生成的新实体。

  • BehaviorRef 可用于读取当前/之前的行为。
  • BehaviorMut 可用于读取当前/之前的行为并请求行为转换。

要访问当前行为,请使用 Deref/DerefMutget/get_mut 操作,对 BehaviorRefBehaviorMut 中的任何一个进行操作。要访问之前的行为,请使用 .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 之外,还携带停止的行为数据。

对于 StartedEventResumedEvent,行为存在于实体本身。您可以使用正常查询(例如 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