Minimalistic state machine for Bevy Game Engine.

Overview

🎚️ Moonshine Behavior

Minimalistic state machine for Bevy game engine.

Overview

This crates is designed to provide a simple, stack-based, lightweight implementation of state machines for Bevy entities.

Features

  • Simple. Minimal overhead for defining and setting up behaviors.
  • Behaviors can be started, paused, resumed, and stopped.
  • Event driven API which allows systems to react to behavior changes on entities.
  • Multiple behaviors with different types may exist on the same entity to define complex state machines.

Usage

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.

Setup

1. Define your behavior data as an enum:

use bevy::prelude::*;
use moonshine_behavior::prelude::*;

#[derive(Component, Default, Debug, Reflect, FromReflect)]
#[reflect(Component)]
enum Bird {
    #[default]
    Idle,
    Fly,
    Sleep,
    Chirp,
}

2. Implement the 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,
        }
    }
}

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

3. Spawn a 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.

fn spawn_bird(mut commands: Commands) {
    commands.spawn(BehaviorBundle::<Bird>::default());
}

Transitions

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

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

fn chirp(mut bird: Query<BehaviorMut<Bird>>) {
    bird.single_mut().try_start(Chirp);
}

To stop current behavior and resume the previous behavior, use .stop():

fn stop(mut bird: Query<BehaviorMut<Bird>>) {
    bird.single_mut().stop();
}

To stop current behavior and resume the initial behavior, use .reset():

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.

Events

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.

fn on_chirp_started(mut events: StartedBehaviors<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: ResumedBehaviors<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!");
        }
  }
}

For PausedEvent, the paused behavior is the previous behavior on the data, which is accessible using .previous():

fn on_chirp_paused(mut events: PausedBehaviors<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.");
        }
  }
}

For StoppedEvent, the stopped behavior is accessible through the event itself:

fn on_chirp_stopped(mut events: StoppedBehaviors<Bird>) {
    for event in events.iter() {
        let entity = event.entity();
        let behavior = event.behavior();
        if let Chirp = *behavior {
            info!("{entity:?} has stopped chirping.");
        }
  }
}

Activation/Suspension

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:

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.");
        }        
    }
}

Examples

See bird.rs for a complete implementation of the Bird behavior.

Support

Find me on Bevy Discord server, or post an issue.

You might also like...
Crossterm plugin for the bevy game engine
Crossterm plugin for the bevy game engine

What is bevy_crossterm? bevy_crossterm is a Bevy plugin that uses crossterm as a renderer. It provides custom components and events which allow users

Concise Reference Book for the Bevy Game Engine

Unofficial Bevy Cheat Book Click here to read the book! Concise reference to programming in the Bevy game engine. Covers useful syntax, features, prog

Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions
Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions

Introduction Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions. (hand interaction with boxes missing

A physics lib for the bevy game engine based on physme

physimple Physimple aims to be the simplest(and capable) physics engine(currently for bevy) WARNING Beware for breaking changes with each update for n

An immediate mode 2D drawing API for Bevy game engine

Bevy Canvas API prototype An unofficial immediate mode 2D drawing API for Bevy game engine. Check the documentation or the examples to know how to use

Simple RUST game with the Bevy Engine

Simple RUST Game using the Bevy Engine YouTube videos for this code base: Episode 1 - Rust Game Development tutorial from Scratch with Bevy Engine Epi

A vdom-free reactive UI lib for the bevy game engine

ui4 ui4 is my fourth major attempt at making a UI dataflow library for the Bevy game engine. More specifically, it's a vdom-less UI library which uses

Hanabi — a particle system plugin for the Bevy game engine.

Hanabi — a particle system plugin for the Bevy game engine

Brine is my attempt at writing a Minecraft client in Rust using the Bevy game engine.
Brine is my attempt at writing a Minecraft client in Rust using the Bevy game engine.

Brine Brine is my attempt at writing a Minecraft client in Rust using the Bevy game engine. It's EXTREMELY work-in-progress. The thing that makes Brin

Owner
null
State machine engine

orga Deterministic state machine engine written in Rust Orga is a stack for building blockchain applications powered by Tendermint consensus. Status:

Nomic 103 Dec 21, 2022
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Sélène Amanita 11 May 28, 2023
A framework for saving and loading game state in Bevy.

Bevy_save A framework for saving and loading game state in Bevy. Features bevy_save is primarily built around extension traits to Bevy's World. Serial

Hank Jordan 10 Jan 24, 2023
2d Endless Runner Game made with Bevy Game Engine

Cute-runner A 2d Endless Runner Game made with Bevy Game Engine. Table of contents Project Infos Usage Screenshots Disclaimer Project Infos Date: Sept

JoaoMarinho 2 Jul 15, 2022
A game of snake written in Rust using the Bevy game engine, targeting WebGL2

Snake using the Bevy Game Engine Prerequisites cargo install cargo-make Build and serve WASM version Set your local ip address in Makefile.toml (loca

Michael Dorst 0 Dec 26, 2021
A game made in one week for the Bevy engine's first game jam

¿Quien es el MechaBurro? An entry for the first Bevy game jam following the theme of "Unfair Advantage." It was made in one week using the wonderful B

mike 20 Dec 23, 2022
A Client/Server game networking plugin using QUIC, for the Bevy game engine.

Bevy Quinnet A Client/Server game networking plugin using QUIC, for the Bevy game engine. Bevy Quinnet QUIC as a game networking protocol Features Roa

Gilles Henaux 65 Feb 20, 2023
Minimalistic implementation of entity kinds for Bevy ECS.

Bevy ?? Kindly This crate is a minimalistic implementation of Kinded Entities for Bevy game engine. In summary, it allows the user to define, construc

null 10 Jan 26, 2023
Basic first-person fly camera for the Bevy game engine

bevy_flycam A basic first-person fly camera for Bevy 0.4 Controls WASD to move horizontally SPACE to ascend LSHIFT to descend ESC to grab/release curs

Spencer Burris 85 Dec 23, 2022
Inspector plugin for the bevy game engine

bevy-inspector-egui This crate provides the ability to annotate structs with a #[derive(Inspectable)], which opens a debug interface using egui where

Jakob Hellermann 517 Dec 31, 2022