Skip to content

Instantly share code, notes, and snippets.

@NEO97online
Created July 19, 2022 00:55
Show Gist options
  • Save NEO97online/03a3e17ac174a0b4f93cec69b053249e to your computer and use it in GitHub Desktop.
Save NEO97online/03a3e17ac174a0b4f93cec69b053249e to your computer and use it in GitHub Desktop.
Bevy FSM
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