use bevy::prelude::*;
use bevy_fsm::{EnumEvent, FSMState, FSMTransition, fsm_observer, Enter, FSMPlugin, StateChangeRequest};
fn main() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_plugins(FSMPlugin::<GameState>::default());
fsm_observer!(app, GameState, on_enter_main_menu);
fsm_observer!(app, GameState, on_enter_playing);
fsm_observer!(app, GameState, on_enter_paused);
fsm_observer!(app, GameState, on_enter_game_over);
app.add_systems(Startup, setup)
.add_systems(Update, drive_state_transitions)
.run();
}
#[derive(Component, EnumEvent, FSMState, Reflect, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[reflect(Component)]
enum GameState {
MainMenu,
Playing,
Paused,
GameOver,
}
impl FSMTransition for GameState {
fn can_transition(from: Self, to: Self) -> bool {
if matches!(
(from, to),
(GameState::MainMenu, GameState::Paused)
| (GameState::Playing, GameState::MainMenu)
| (GameState::GameOver, GameState::Playing)
| (GameState::GameOver, GameState::Paused)
) {
return false;
}
true
}
}
fn setup(mut commands: Commands) {
println!("=== Transition Rules Example ===");
println!("Blocked transitions: MainMenu -> Paused, Playing -> MainMenu, GameOver -> Playing, GameOver -> Paused\n");
commands.spawn((GameState::MainMenu, Name::new("Game")));
}
fn drive_state_transitions(
mut commands: Commands,
query: Query<(Entity, &GameState, &Name)>,
mut frame: Local<u32>,
) {
*frame += 1;
for (entity, &state, name) in &query {
println!(
"Frame {:02}: {} currently in {:?}",
*frame,
name.as_str(),
state
);
let next = match *frame {
1 => Some(GameState::Paused), 2 => Some(GameState::Playing), 3 => Some(GameState::MainMenu), 4 => Some(GameState::Paused), 5 => Some(GameState::GameOver), 6 => Some(GameState::Playing), 7 => Some(GameState::Paused), 8 => Some(GameState::MainMenu), 9 => {
println!("=== Example complete! ===");
std::process::exit(0);
}
_ => None,
};
if let Some(target) = next {
println!(" Attempting transition: {:?} -> {:?}", state, target);
let allowed = <GameState as FSMTransition>::can_transition(state, target);
println!(
" {}xpecting transition",
if allowed { "E" } else { "NOT e" }
);
commands.trigger(StateChangeRequest { entity, next: target });
}
}
}
fn on_enter_main_menu(_trigger: On<Enter<game_state::MainMenu>>) {
println!(" [ENTER MainMenu] Showing title screen");
}
fn on_enter_playing(_trigger: On<Enter<game_state::Playing>>) {
println!(" [ENTER Playing] Gameplay started");
}
fn on_enter_paused(_trigger: On<Enter<game_state::Paused>>) {
println!(" [ENTER Paused] Game paused");
}
fn on_enter_game_over(_trigger: On<Enter<game_state::GameOver>>) {
println!(" [ENTER GameOver] Game over screen displayed");
}