//! This example illustrates the use of [`SubStates`] for more complex state handling patterns. //! //! [`SubStates`] are [`States`] that only exist while the App is in another [`State`]. They can //! be used to create more complex patterns while relying on simple enums, or to de-couple certain //! elements of complex state objects. //! //! In this case, we're transitioning from a `Menu` state to an `InGame` state, at which point we create //! a substate called `IsPaused` to track whether the game is paused or not. use bevy::prelude::*; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] enum AppState { #[default] Menu, InGame, } // In this case, instead of deriving `States`, we derive `SubStates` #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, SubStates)] // And we need to add an attribute to let us know what the source state is // and what value it needs to have. This will ensure that unless we're // in [`AppState::InGame`], the [`IsPaused`] state resource // will not exist. #[source(AppState = AppState::InGame)] enum IsPaused { #[default] Running, Paused, } fn main() { App::new() .add_plugins(DefaultPlugins) .init_state::() .add_sub_state::() // We set the substate up here. // Most of these remain the same .add_systems(Startup, setup) .add_systems(OnEnter(AppState::Menu), setup_menu) .add_systems(Update, menu.run_if(in_state(AppState::Menu))) .add_systems(OnExit(AppState::Menu), cleanup_menu) .add_systems(OnEnter(AppState::InGame), setup_game) .add_systems(OnEnter(IsPaused::Paused), setup_paused_screen) .add_systems( OnExit(IsPaused::Paused), clear_state_bound_entities(IsPaused::Paused), ) .add_systems( Update, ( // Instead of relying on [`AppState::InGame`] here, we're relying on // [`IsPaused::Running`], since we don't want movement or color changes // if we're paused (movement, change_color).run_if(in_state(IsPaused::Running)), // The pause toggle, on the other hand, needs to work whether we're // paused or not, so it uses [`AppState::InGame`] instead. toggle_pause.run_if(in_state(AppState::InGame)), ), ) .add_systems(Update, log_transitions) .run(); } #[derive(Resource)] struct MenuData { button_entity: Entity, } const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); fn setup(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } fn setup_menu(mut commands: Commands) { let button_entity = commands .spawn(NodeBundle { style: Style { // center button width: Val::Percent(100.), height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, ..default() }) .with_children(|parent| { parent .spawn(ButtonBundle { style: Style { width: Val::Px(150.), height: Val::Px(65.), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, ..default() }, image: UiImage::default().with_color(NORMAL_BUTTON), ..default() }) .with_children(|parent| { parent.spawn(TextBundle::from_section( "Play", TextStyle { font_size: 40.0, color: Color::srgb(0.9, 0.9, 0.9), ..default() }, )); }); }) .id(); commands.insert_resource(MenuData { button_entity }); } fn menu( mut next_state: ResMut>, mut interaction_query: Query< (&Interaction, &mut UiImage), (Changed, With