Skip to content

Commit

Permalink
Improve state transitioning (#637)
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 authored Jul 23, 2023
1 parent 2844a2a commit 4fdf8e0
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 79 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ iyes_progress.workspace = true
nalgebra.workspace = true
parry2d.workspace = true
parry3d.workspace = true
paste.workspace = true
serde.workspace = true
thiserror.workspace = true
50 changes: 16 additions & 34 deletions crates/core/src/gamestate.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
use bevy::prelude::*;
use iyes_progress::prelude::*;
use iyes_progress::ProgressPlugin;

use crate::{
gconfig::GameConfig,
state::AppState,
transition::{DeStateTransition, StateWithSet},
};
use crate::{gconfig::GameConfig, nested_state, state::AppState};

pub(super) struct GameStatePlugin;
pub(crate) struct GameStateSetupPlugin;

impl Plugin for GameStatePlugin {
impl Plugin for GameStateSetupPlugin {
fn build(&self, app: &mut App) {
app.add_state_with_set::<GameState>()
.configure_sets((AppState::state_set(), GameState::state_set()).chain())
.add_plugin(ProgressPlugin::new(GameState::Loading).continue_to(GameState::Playing))
.add_system(setup.in_schedule(OnEnter(AppState::InGame)))
.add_system(cleanup.in_schedule(OnExit(AppState::InGame)));
app.add_plugin(GameStatePlugin)
.add_plugin(ProgressPlugin::new(GameState::Loading).continue_to(GameState::Playing));
}
}

/// Phase of an already started game. The game might be still loading or
/// finishing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, States)]
pub enum GameState {
#[default]
None,
Loading,
Playing,
}

impl StateWithSet for GameState {
type Set = GameStateSet;

fn state_set() -> Self::Set {
GameStateSet
nested_state!(
AppState::InGame -> GameState,
doc = "Phase of an already started game. The game might be still loading or finishing.",
enter = setup,
exit = cleanup,
variants = {
Loading,
Playing,
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
pub struct GameStateSet;
);

fn setup(mut next_state: ResMut<NextState<GameState>>) {
next_state.set(GameState::Loading);
}

fn cleanup(mut commands: Commands, mut next_state: ResMut<NextState<GameState>>) {
fn cleanup(mut commands: Commands) {
commands.remove_resource::<GameConfig>();
next_state.set(GameState::None);
}
4 changes: 2 additions & 2 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use baseset::GameSetsPlugin;
use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
use cleanup::CleanupPlugin;
use gamestate::GameStatePlugin;
use gamestate::GameStateSetupPlugin;
use iyes_progress::prelude::*;
use state::AppState;
use visibility::VisibilityPlugin;
Expand Down Expand Up @@ -33,7 +33,7 @@ impl PluginGroup for CorePluginGroup {
PluginGroupBuilder::start::<Self>()
.add(ProgressPlugin::new(AppState::AppLoading).continue_to(AppState::InMenu))
.add(GameSetsPlugin)
.add(GameStatePlugin)
.add(GameStateSetupPlugin)
.add(VisibilityPlugin)
.add(CleanupPlugin)
}
Expand Down
76 changes: 76 additions & 0 deletions crates/core/src/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ use bevy::{
ecs::schedule::{run_enter_schedule, FreeSystemSet},
prelude::*,
};
pub use paste;

pub trait DeStateTransition {
/// This method is almost equal to Bevy's [`App::add_state`]. The only
/// difference is that the state transition is added to an associated
/// state. See [`StateWithSet`].
fn add_state_with_set<S: States + StateWithSet>(&mut self) -> &mut Self;

fn add_child_state<P: StateWithSet, S: States + StateWithSet>(&mut self) -> &mut Self;
}

pub trait StateWithSet {
Expand Down Expand Up @@ -53,4 +56,77 @@ impl DeStateTransition for App {

self
}

fn add_child_state<P: StateWithSet, S: States + StateWithSet>(&mut self) -> &mut Self {
self.add_state_with_set::<S>();
self.configure_sets((P::state_set(), S::state_set()).chain());
self
}
}

/// Creates a Bevy state and a Bevy plugin.
///
/// The child state is bound to a given parent state with this syntax:
/// `ParentState::ParentVariant -> ChildState`.
///
/// Transitions of the child state are scheduled after transitions of the
/// parent state.
///
/// Variant `None` is automatically added to the child state. Additional
/// variants are configurable with `variants` argument.
///
/// `None` variant of the child state is entered when the given variant of the
/// parent state is exited. The `enter` system is called on enter of the parent
/// variant and the `exit` system is called on exit of the parent variant.
#[macro_export]
macro_rules! nested_state {
(
$parent:ident::$parent_variant:ident -> $name:ident,
doc = $doc:expr,
$(enter = $enter:ident,)?
$(exit = $exit:ident,)?
variants = {
$($variant:ident),* $(,)?
}
) => {
use $crate::transition::{DeStateTransition, paste::paste, StateWithSet};

paste! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
pub struct [<$name Plugin>];

impl Plugin for [<$name Plugin>] {
fn build(&self, app: &mut App) {
app.add_child_state::<$parent, $name>()
.add_system(go_to_none.in_schedule(OnExit($parent::$parent_variant)))
$(.add_system($enter.in_schedule(OnEnter($parent::$parent_variant))))?
$(.add_system($exit.in_schedule(OnExit($parent::$parent_variant))))?;
}
}


#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, States)]
#[doc = $doc]
pub enum $name {
#[default]
None,
$($variant),*
}

impl StateWithSet for $name {
type Set = [<$name Set>];

fn state_set() -> Self::Set {
[<$name Set>]
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
pub struct [<$name Set>];

fn go_to_none(mut next_state: ResMut<NextState<$name>>) {
next_state.set($name::None);
}
}
};
}
58 changes: 15 additions & 43 deletions crates/menu/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use aftergame::AfterGamePlugin;
use bevy::{app::PluginGroupBuilder, prelude::*};
use create::CreateGamePlugin;
use de_core::{
gresult::GameResult,
state::AppState,
transition::{DeStateTransition, StateWithSet},
};
use de_core::{gresult::GameResult, nested_state, state::AppState};
use gamelisting::GameListingPlugin;
use mainmenu::MainMenuPlugin;
use mapselection::MapSelectionPlugin;
Expand All @@ -28,7 +24,7 @@ pub struct MenuPluginGroup;
impl PluginGroup for MenuPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(MenuSetupPlugin)
.add(MenuStatePlugin)
.add(MenuPlugin)
.add(MainMenuPlugin)
.add(MapSelectionPlugin)
Expand All @@ -40,40 +36,20 @@ impl PluginGroup for MenuPluginGroup {
}
}

struct MenuSetupPlugin;

impl Plugin for MenuSetupPlugin {
fn build(&self, app: &mut App) {
app.add_state_with_set::<MenuState>()
.configure_sets((AppState::state_set(), MenuState::state_set()).chain())
.add_system(menu_entered_system.in_schedule(OnEnter(AppState::InMenu)))
.add_system(menu_exited_system.in_schedule(OnExit(AppState::InMenu)));
nested_state!(
AppState::InMenu -> MenuState,
doc = "Top-level menu state. Each variant corresponds to a single menu screen.",
enter = menu_entered_system,
variants = {
MainMenu,
SinglePlayerGame,
SignIn,
GameListing,
GameCreation,
MultiPlayerGame,
AfterGame,
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, States)]
pub(crate) enum MenuState {
#[default]
None,
MainMenu,
SinglePlayerGame,
SignIn,
GameListing,
GameCreation,
MultiPlayerGame,
AfterGame,
}

impl StateWithSet for MenuState {
type Set = MenuStateSet;

fn state_set() -> Self::Set {
MenuStateSet
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
pub struct MenuStateSet;
);

fn menu_entered_system(
mut next_state: ResMut<NextState<MenuState>>,
Expand All @@ -85,7 +61,3 @@ fn menu_entered_system(
next_state.set(MenuState::MainMenu);
}
}

fn menu_exited_system(mut next_state: ResMut<NextState<MenuState>>) {
next_state.set(MenuState::None);
}

0 comments on commit 4fdf8e0

Please sign in to comment.