Created
July 19, 2022 00:55
-
-
Save NEO97online/03a3e17ac174a0b4f93cec69b053249e to your computer and use it in GitHub Desktop.
Bevy FSM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::core::FixedTimestep; | |
use bevy::prelude::*; | |
#[derive(PartialEq, Clone, Copy)] | |
enum Phase { | |
Enter, | |
Process, | |
Exit, | |
} | |
#[derive(Component)] | |
struct StateMachine<T> { | |
current: (T, Phase), | |
previous: Option<T>, | |
} | |
impl<T: Clone + Copy + PartialEq> StateMachine<T> { | |
fn new(initial_state: T) -> StateMachine<T> { | |
StateMachine::<T> { | |
current: (initial_state, Phase::Enter), | |
previous: None, | |
} | |
} | |
// sets to a new state, saving the old state in "previous" | |
fn change_to(&mut self, new_state: T) { | |
self.previous = Some(self.current.0); | |
self.current = (new_state, Phase::Enter); | |
} | |
// runs a state handler for all states | |
fn handle_all<F: Fn(T, Phase) -> Option<T>>(&mut self, handler: F) { | |
// Exit previous state | |
if let Some(prev_state) = self.previous { | |
handler(prev_state, Phase::Exit); | |
self.previous = None; | |
} | |
// Enter/Process current state | |
if let Some(new_state) = handler(self.current.0, self.current.1) { | |
self.change_to(new_state); | |
} else if self.current.1 == Phase::Enter { | |
self.current.1 = Phase::Process; | |
} | |
} | |
// runs a state handler for one specific state. if the state is inactive, it will not run. | |
fn handle_state<F: Fn(Phase) -> Option<T>>(&mut self, desired_state: T, handler: F) { | |
// Exit previous state | |
if let Some(prev_state) = self.previous { | |
if prev_state == desired_state { | |
handler(Phase::Exit); | |
self.previous = None; | |
} | |
} | |
// Enter/Process current state | |
if self.current.0 == desired_state { | |
if let Some(new_state) = handler(self.current.1) { | |
self.change_to(new_state); | |
} else if self.current.1 == Phase::Enter { | |
self.current.1 = Phase::Process; | |
} | |
} | |
} | |
} | |
#[derive(Clone, Copy, PartialEq)] | |
enum PlayerState { | |
Idle, | |
Run, | |
Dash, | |
Attack, | |
} | |
// Monolithic player state | |
fn player_state_logic(keys: Res<Input<KeyCode>>, mut query: Query<&mut StateMachine<PlayerState>>) { | |
for mut state_machine in query.iter_mut() { | |
state_machine.handle_all(|state, transition| { | |
use Phase::*; | |
use PlayerState::*; | |
match (state, transition) { | |
(Idle, Enter) => { | |
println!("Enter Idle"); | |
} | |
(Idle, Process) => { | |
println!("Process Idle"); | |
if keys.pressed(KeyCode::Space) { | |
println!("Space pressed"); | |
return Some(PlayerState::Run); | |
} | |
} | |
(Idle, Exit) => { | |
println!("Exit Idle"); | |
} | |
(Run, Enter) => { | |
println!("Enter Run"); | |
} | |
(Run, Process) => { | |
println!("Process Run"); | |
if keys.pressed(KeyCode::Space) { | |
return Some(PlayerState::Idle); | |
} | |
} | |
(Run, Exit) => { | |
println!("Exit Run"); | |
} | |
(Dash, Enter) => {} | |
(Dash, Process) => {} | |
(Dash, Exit) => {} | |
(Attack, Enter) => {} | |
(Attack, Process) => {} | |
(Attack, Exit) => {} | |
} | |
None | |
}) | |
} | |
} | |
fn idle_state(keys: Res<Input<KeyCode>>, mut query: Query<&mut StateMachine<PlayerState>>) { | |
for mut state_machine in query.iter_mut() { | |
state_machine.handle_state(PlayerState::Idle, |phase| { | |
match phase { | |
Phase::Enter => { | |
println!("Enter Idle"); | |
} | |
Phase::Process => { | |
println!("Process Idle"); | |
if keys.pressed(KeyCode::Space) { | |
println!("Space pressed"); | |
return Some(PlayerState::Run); | |
} | |
} | |
Phase::Exit => { | |
println!("Exit Idle"); | |
} | |
} | |
None | |
}); | |
} | |
} | |
fn run_state(keys: Res<Input<KeyCode>>, mut query: Query<&mut StateMachine<PlayerState>>) { | |
for mut state_machine in query.iter_mut() { | |
state_machine.handle_state(PlayerState::Run, |phase| { | |
match phase { | |
Phase::Enter => { | |
println!("Enter Run"); | |
} | |
Phase::Process => { | |
println!("Process Run"); | |
if keys.pressed(KeyCode::Space) { | |
println!("Space pressed"); | |
return Some(PlayerState::Idle); | |
} | |
} | |
Phase::Exit => { | |
println!("Exit Run"); | |
} | |
} | |
None | |
}); | |
} | |
} | |
fn setup_player(mut commands: Commands) { | |
commands | |
.spawn() | |
.insert(StateMachine::<PlayerState>::new(PlayerState::Idle)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment