use bevy::prelude::*;
use bevy_fsm::{
fsm_observer, Enter, EnumEvent, Exit, FSMPlugin, FSMState, FSMTransition, StateChangeRequest,
Transition,
};
fn main() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_plugins(FSMPlugin::<LifeFSM>::default());
fsm_observer!(app, LifeFSM, on_enter_dying);
fsm_observer!(app, LifeFSM, on_exit_alive);
fsm_observer!(app, LifeFSM, on_transition_dying_dead);
fsm_observer!(app, LifeFSM, on_transition_dying_alive);
app.add_systems(Startup, setup)
.add_systems(Update, trigger_transitions)
.run();
}
#[derive(Component, EnumEvent, FSMState, Reflect, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[reflect(Component)]
enum LifeFSM {
Alive,
Dying,
Dead,
}
impl FSMTransition for LifeFSM {
fn can_transition(from: Self, to: Self) -> bool {
matches!(
(from, to),
(LifeFSM::Alive, LifeFSM::Dying)
| (LifeFSM::Dying, LifeFSM::Alive)
| (LifeFSM::Dying, LifeFSM::Dead)
) || from == to
}
}
#[derive(Component, Debug)]
struct DyingAnimation {
#[allow(dead_code)]
timer: f32,
}
#[derive(Component)]
struct TestEntity;
fn setup(mut commands: Commands) {
println!("=== Setting up Life FSM Example ===");
println!("Observers registered using fsm_observer! macro");
let entity = commands
.spawn((TestEntity, LifeFSM::Alive, Name::new("Hero")))
.id();
println!("Spawned entity {:?} in Alive state", entity);
}
fn trigger_transitions(
mut commands: Commands,
query: Query<(Entity, &LifeFSM, &Name), With<TestEntity>>,
time: Res<Time>,
mut elapsed: Local<f32>,
mut triggered_10: Local<bool>,
mut triggered_20: Local<bool>,
mut triggered_30: Local<bool>,
mut triggered_40: Local<bool>,
mut triggered_50: Local<bool>,
) {
*elapsed += time.delta_secs();
for (entity, &state, name) in query.iter() {
if *elapsed >= 1.0 && !*triggered_10 {
*triggered_10 = true;
println!("\n--- Triggering transition: {} Alive -> Dying ---", name);
commands.trigger(
StateChangeRequest {
entity,
next: LifeFSM::Dying,
},
);
}
if *elapsed >= 2.0 && !*triggered_20 && state == LifeFSM::Dying {
*triggered_20 = true;
println!(
"\n--- Triggering transition: {} Dying -> Alive (Resurrection!) ---",
name
);
commands.trigger(
StateChangeRequest {
entity,
next: LifeFSM::Alive,
},
);
}
if *elapsed >= 3.0 && !*triggered_30 && state == LifeFSM::Alive {
*triggered_30 = true;
println!("\n--- Triggering transition: {} Alive -> Dying ---", name);
commands.trigger(
StateChangeRequest {
entity,
next: LifeFSM::Dying,
},
);
}
if *elapsed >= 4.0 && !*triggered_40 && state == LifeFSM::Dying {
*triggered_40 = true;
println!("\n--- Triggering transition: {} Dying -> Dead ---", name);
commands.trigger(
StateChangeRequest {
entity,
next: LifeFSM::Dead,
},
);
}
if *elapsed >= 5.0 && !*triggered_50 {
*triggered_50 = true;
println!("\n=== Example complete! ===");
std::process::exit(0);
}
}
}
fn on_enter_dying(trigger: On<Enter<life_fsm::Dying>>, mut commands: Commands) {
let entity = trigger.event().entity;
println!(" [ENTER Dying] Entity {:?} is now dying!", entity);
commands
.entity(entity)
.insert(DyingAnimation { timer: 3.0 });
}
fn on_exit_alive(trigger: On<Exit<life_fsm::Alive>>, query: Query<&Name>) {
let entity = trigger.event().entity;
let name = query.get(entity).map(|n| n.as_str()).unwrap_or("Unknown");
println!(
" [EXIT Alive] Entity {} ({:?}) is no longer alive!",
name, entity
);
}
fn on_transition_dying_dead(
trigger: On<Transition<life_fsm::Dying, life_fsm::Dead>>,
mut commands: Commands,
query: Query<&Name>,
) {
let entity = trigger.event().entity;
let name = query.get(entity).map(|n| n.as_str()).unwrap_or("Unknown");
println!(
" [TRANSITION Dying -> Dead] {} ({:?}) has died. Removing DyingAnimation...",
name, entity
);
commands.entity(entity).remove::<DyingAnimation>();
}
fn on_transition_dying_alive(
trigger: On<Transition<life_fsm::Dying, life_fsm::Alive>>,
mut commands: Commands,
query: Query<&Name>,
) {
let entity = trigger.event().entity;
let name = query.get(entity).map(|n| n.as_str()).unwrap_or("Unknown");
println!(
" [TRANSITION Dying -> Alive] {} ({:?}) has been resurrected!",
name, entity
);
commands.entity(entity).remove::<DyingAnimation>();
}