Minimalistic state machine for Bevy game engine.
This crates is designed to provide a simple, stack-based, lightweight implementation of state machines for Bevy entities.
A behavior, typically implemented as an enum
, is a Component
which represents some state of its entity. Each behavior is associated with a stack.
When the next behavior is started, the current one is pushed onto the stack (if resumable) and paused.
enum
:```rust use bevy::prelude::; use moonshine_behavior::prelude::;
enum Bird { #[default] Idle, Fly, Sleep, Chirp, } ```
Behavior
trait:rust
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,
}
}
}
This trait defines the possible transitions for your behavior.
In this example:
- a bird may sleep, fly, or chirp when idle
- a bird may chirp when flying
- a bird may not fly or chirp when sleeping
BehaviorBundle
:For behavior system to work, you must insert your behavior using a BehaviorBundle
.
This bundle also inserts an instance of your behavior. This is referred to as the initial behavior.
rust
fn spawn_bird(mut commands: Commands) {
commands.spawn(BehaviorBundle::<Bird>::default());
}
An entity spawned with a BehaviorBundle
may be queried using BehaviorRef
and BehaviorMut
world queries.
BehaviorRef
may be used to read the current/previous behaviors.BehaviorMut
may be used to read the current/previous behaviors and request behavior transitions.To access current behavior, use Deref
on either BehaviorRef
or BehaviorMut
.
To access previous behavior, use .previous()
:
rust
fn is_chirping_while_flying(bird: Query<BehaviorRef<Bird>>) -> bool {
let behavior = bird.single();
matches!(*behavior, Chirp) && matches!(behavior.previous(), Some(Fly))
}
To start some next behavior, use .try_start()
:
rust
fn chirp(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().try_start(Chirp);
}
To stop current behavior and resume the previous behavior, use .stop()
:
rust
fn stop(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().stop();
}
To stop current behavior and resume the initial behavior, use .reset()
:
rust
fn reset(mut bird: Query<BehaviorMut<Bird>>) {
bird.single_mut().reset();
}
When a transition is requested, it is not invoked immediately. Instead, it is invoked whenever the registered transition()
system is run.
You may register your systems before or after transition()
to perform any logic as required.
Warning
Be mindful that only one transition may be invoked per application update, per entity. This is an intentional design choice.
If multiple transitions are requested on the same entity within the same update cycle, only the last one is invoked, and a warning is logged.
Any time a transition is invoked, an associated event is dispatched. These events may be used by other systems to react to behavior changes.
Each event (except StoppedEvent
) carries only the entity ID for which the behavior was started, paused, or resumed.
For StartedEvent
and ResumedEvent
, the behavior exists on the entity itself.
You may access it either using a normal query (e.g. Query<&Bird>
), or using BehaviorRef
.
```rust
fn onchirpstarted(mut events: StartedBehaviors
fn onchirpresumed(mut events: ResumedBehaviors
For `PausedEvent`, the paused behavior is the previous behavior on the data, which is accessible using `.previous()`:
rust
fn onchirppaused(mut events: PausedBehaviors
For `StoppedEvent`, the stopped behavior is accessible through the event itself:
rust
fn onchirpstopped(mut events: StoppedBehaviors
In some cases, it may be necessary to run some logic if a behavior is paused OR stopped (suspension), or started OR resumed (activation).
To handle activation and suspension, you may use a simple Changed
query:
```rust
fn onchirpactivated(query: Query
}
}
fn onchirpsuspended(query: Query
}
}
```
See bird.rs for a complete implementation of the Bird
behavior.
Find me on Bevy Discord server, or post an issue.