Expand description
Observer-driven finite state machine framework for Bevy ECS.
This crate provides a lightweight framework for modeling enum-based state machines using Bevy’s observer system. State machines are defined as enum components with automatic variant-specific event generation.
§Features
- Enum-based states: Keep your states as simple enum variants
- Observer-driven: React to state changes via Bevy observers
- Variant-specific events: No runtime state checks needed in observers
- Flexible validation: Per-entity and per-type transition rules
- Minimal API:
FSMPluginfor automatic setup - Organized hierarchy: Observers automatically organized in entity hierarchy
§Quick Start
use bevy::prelude::*;
use bevy_fsm::{FSMState, FSMTransition, FSMPlugin, StateChangeRequest, Enter, Exit, Transition, fsm_observer};
use bevy_enum_event::EnumEvent;
fn plugin(app: &mut App) {
// FSMPlugin automatically sets up the observer hierarchy on first use
app.add_plugins(FSMPlugin::<LifeFSM>::default());
// Use fsm_observer! macro for variant-specific observers
// This is functionally identical to a typed global observer but gets automatically parented
// into a custom FSMObservers/LifeFSM/on_enter_dying hierarchy that keeps observers nicely
// sorted by their respective FSM.
fsm_observer!(app, LifeFSM, on_enter_dying);
fsm_observer!(app, LifeFSM, on_exit_alive);
fsm_observer!(app, LifeFSM, on_transition_dying_dead);
}
// Zero boilerplate - just derive FSMTransition for "allow all" behavior!
#[derive(Component, EnumEvent, FSMTransition, FSMState, Reflect, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[reflect(Component)]
enum LifeFSM {
Alive,
Dying,
Dead,
}
// For custom transition rules, skip FSMTransition derive and manually implement:
// impl FSMTransition for LifeFSM {
// fn can_transition(from: Self, to: Self) -> bool {
// matches!((from, to),
// (LifeFSM::Alive, LifeFSM::Dying) |
// (LifeFSM::Dying, LifeFSM::Dead)) || from == to
// }
// }
#[derive(Component)]
struct DyingAnimation;
fn on_enter_dying(trigger: Trigger<Enter<life_fsm::Dying>>, mut commands: Commands) {
let entity = trigger.target();
commands.entity(entity).insert(DyingAnimation);
}
fn on_exit_alive(trigger: Trigger<Enter<life_fsm::Alive>>) {
let entity = trigger.target();
println!("Entity {} was unalived.", entity);
}
fn on_transition_dying_dead(
trigger: Trigger<Transition<life_fsm::Dying, life_fsm::Dead>>,
mut commands: Commands
) {
let entity = trigger.target();
commands.entity(entity).despawn()
}§Context-Aware Transitions
For more complex validation logic that requires access to the World (e.g., checking other components),
override can_transition_ctx:
use bevy::prelude::*;
use bevy_fsm::{FSMState, FSMTransition};
#[derive(Component)]
struct Energy(f32);
#[derive(Component, Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum ActionFSM {
Idle,
Casting,
Exhausted,
}
impl FSMState for ActionFSM {}
impl FSMTransition for ActionFSM {
fn can_transition(from: Self, to: Self) -> bool {
// Basic state-only rules
matches!((from, to),
(ActionFSM::Idle, ActionFSM::Casting) |
(ActionFSM::Casting, ActionFSM::Idle) |
(ActionFSM::Casting, ActionFSM::Exhausted) |
(ActionFSM::Exhausted, ActionFSM::Idle)) || from == to
}
fn can_transition_ctx(world: &World, entity: Entity, from: Self, to: Self) -> bool {
// First check basic rules
if !<Self as FSMTransition>::can_transition(from, to) {
return false;
}
// Additional context-aware validation
if matches!(to, ActionFSM::Casting) {
// Only allow casting if entity has enough energy
if let Some(energy) = world.get::<Energy>(entity) {
return energy.0 >= 10.0;
}
return false;
}
true
}
}§Observer Hierarchy
The first FSMPlugin added to your app automatically creates a hierarchical
organization for all FSM observers:
FSMObservers (root)
├─ LifeFSM
│ ├─ apply_state_request
│ ├─ on_fsm_added
│ ├─ on_dying
│ └─ on_dead
├─ BlockFSM
│ ├─ apply_state_request
│ └─ ...
└─ ...This hierarchy is created automatically when you add your first FSM plugin, with no additional setup required.
Macros§
- fsm_
observer - Macro for registering FSM observers sorting them into the per-FSM hierarchy.
Structs§
- Enter
- Event fired when an entity enters a state.
- Exit
- Event fired when an entity exits a state.
- FSMOverride
- Component for optional per-entity state machine configuration.
- FSMPlugin
- Generic plugin for FSM types that automatically sets up core observers.
- State
Change Request - Event requesting a state change for an entity.
- Transition
- Event fired for state transitions.
Enums§
- Rule
Type - Configuration mode for FSM transition validation set in the
FSMOverridecomponent.
Traits§
- FSMState
- Core FSM trait implemented automatically by
#[derive(FSMState)]. - FSMTransition
- Trait for defining transition logic.
Functions§
- apply_
state_ request - Observer that applies state change requests.
- attach_
observer_ to_ group - Attaches an observer entity to the hierarchy for the FSM type
S. - on_
fsm_ added - Observer that triggers enter events when an FSM component is first added.
Derive Macros§
- Enum
Event - Derive macro that generates Bevy event types from enum variants.
- FSMState
- Derive macro for generating FSM state infrastructure.
- FSMTransition
- Derive macro for generating a default
FSMTransitionimplementation.