From 82b817a614386403575128c71f3054cf0bc91c97 Mon Sep 17 00:00:00 2001 From: "Elijah C. Voigt" Date: Sun, 25 Feb 2024 11:42:50 -0800 Subject: [PATCH] State management refactor Have not run this, almost certain things are broken. The general idea is we mark entities with components and have a general purpose "OnStateChange" trigger that marks all "active state" entities as visible and all "inactive state" entities as invisible. This should simplify state management -- unless of course there are exceptions which there will be and will require having some sort of "Sticky" component that says "When this state transition occurs, don't touch this one entity" --- src/audio.rs | 2 +- src/credits.rs | 12 +--- src/display3d.rs | 9 ++- src/game.rs | 6 +- src/intro.rs | 26 ++++----- src/loading.rs | 14 ++--- src/main.rs | 44 ++++++++------- src/menu.rs | 141 ++++++++++++++++------------------------------- src/tutorial.rs | 33 ++--------- 9 files changed, 107 insertions(+), 180 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 7268655..b26a51c 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -22,7 +22,7 @@ impl Plugin for AudioPlugin { }); app.init_resource::(); - app.add_systems(OnEnter(GameState::Menu), play_background); + app.add_systems(OnEnter(GameState::Intro), play_background); app.add_systems( Update, ( diff --git a/src/credits.rs b/src/credits.rs index 8f2f643..6cf40d6 100644 --- a/src/credits.rs +++ b/src/credits.rs @@ -5,15 +5,7 @@ pub(crate) struct CreditsPlugin; impl Plugin for CreditsPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, init_credits_ui) - .add_systems( - Update, - (menu::exit_to_menu.run_if(in_state(GameState::Credits)),), - ) - .add_systems( - OnEnter(GameState::Credits), - (update_credits, activate::.after(update_credits)), - ) - .add_systems(OnExit(GameState::Credits), deactivate::); + .add_systems(OnEnter(GameState::Credits), update_credits); } } @@ -23,7 +15,7 @@ struct Credits; fn init_credits_ui(mut commands: Commands) { commands .spawn(( - Credits, + GameState::Credits, NodeBundle { style: Style { width: Val::Percent(100.0), diff --git a/src/display3d.rs b/src/display3d.rs index 5d6ddf1..487190d 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -104,11 +104,14 @@ impl Plugin for Display3dPlugin { .run_if(in_state(GameState::Play)) .run_if(in_state(DisplayState::Display3d)), ) - .add_systems(OnExit(DisplayState::Display3d), deactivate::) + // Manage visible/hidden states + .add_systems( + Update, + manage_state_entities::().run_if(state_changed::()), + ) .add_systems( OnEnter(GameState::Play), ( - activate::.run_if(in_state(DisplayState::Display3d)), set_piece_texture.run_if(resource_exists::()), update_tweaks.run_if(resource_exists::()), opening_animation @@ -119,7 +122,7 @@ impl Plugin for Display3dPlugin { } } -#[derive(Debug, Component)] +#[derive(Debug, Component, PartialEq)] pub(crate) struct Display3d; #[derive(Debug, Component)] diff --git a/src/game.rs b/src/game.rs index c6ce46f..5cf6b12 100644 --- a/src/game.rs +++ b/src/game.rs @@ -13,11 +13,13 @@ impl Plugin for GamePlugin { .insert_resource(Score { ..default() }) .add_systems(Startup, setup_board) .add_systems(OnEnter(GameState::Play), hide_valid_moves) + .add_systems( + Update, + manage_state_entities::().run_if(state_changed::()), + ) .add_systems( Update, ( - menu::exit_to_menu - .run_if(in_state(GameState::Play).or_else(in_state(GameState::Endgame))), update_board .run_if(on_event::()) .after(handle_selection), diff --git a/src/intro.rs b/src/intro.rs index 6e1100b..6448278 100644 --- a/src/intro.rs +++ b/src/intro.rs @@ -14,15 +14,11 @@ impl Plugin for IntroPlugin { .run_if(run_once()), ) .add_systems(OnEnter(GameState::Intro), manage_intro) - .add_systems( - OnExit(GameState::Intro), - (deactivate::, cleanup_intro), - ) + .add_systems(OnExit(GameState::Intro), cleanup_intro) // All of these run during GameState::Intro .add_systems( Update, ( - menu::exit_to_menu, manage_intro.run_if(any_component_removed::()), // Started when the TextScrollAnimation component is added to the parent entity // Updated for as long as there is scrolling text @@ -42,12 +38,12 @@ impl Plugin for IntroPlugin { } } -#[derive(Debug, Component)] -struct Intro; - #[derive(Debug, Resource)] struct IntroPlayed; +#[derive(Debug, Component)] +struct IntroUi; + // Draw the intro text (invisible) on startup // Requires the Tweakfile to be loaded fn init_intro_text( @@ -65,7 +61,7 @@ fn init_intro_text( commands .spawn(( - Intro, + IntroUi, NodeBundle { style: Style { width: Val::Percent(100.0), @@ -85,7 +81,7 @@ fn init_intro_text( texts.iter().for_each(|text| { parent .spawn(( - Intro, + IntroUi, NodeBundle { style: Style { position_type: PositionType::Absolute, @@ -99,7 +95,7 @@ fn init_intro_text( )) .with_children(|parent| { parent.spawn(( - Intro, + IntroUi, TextBundle { text: Text { sections: text @@ -127,7 +123,7 @@ fn init_intro_text( fn manage_intro( // Hack, this way of "finding" the root Node containing our paragraphs is precarious - query: Query, With, Without)>, + query: Query, With, Without)>, mut commands: Commands, ) { info!("Managing intro"); @@ -140,8 +136,8 @@ fn manage_intro( } fn cleanup_intro( - query: Query>, - mut texts: Query<&mut Text, With>, + query: Query>, + mut texts: Query<&mut Text, With>, mut curr: ResMut, tweaks_file: Res, tweaks: Res>, @@ -183,7 +179,7 @@ fn manage_scroll_text_animation( Added, )>, >, - texts: Query, With)>, + texts: Query, With)>, animated_texts: Query<&ui::TextScroll>, parents: Query<&Parent>, children: Query<&Children>, diff --git a/src/loading.rs b/src/loading.rs index a3a6b6e..9b943c6 100644 --- a/src/loading.rs +++ b/src/loading.rs @@ -6,18 +6,12 @@ impl Plugin for LoadingPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, initialize) .add_systems(Update, loading.run_if(in_state(GameState::Loading))) - .add_systems( - OnEnter(GameState::Loading), - (activate::, enter_loading), - ) - .add_systems( - OnExit(GameState::Loading), - (deactivate::, exit_loading), - ); + .add_systems(OnEnter(GameState::Loading), enter_loading) + .add_systems(OnExit(GameState::Loading), exit_loading); } } -#[derive(Debug, Component)] +#[derive(Debug, Component, PartialEq)] struct Loading; // TODO: Why is this image not showing?? @@ -82,6 +76,6 @@ fn loading( debug!("s {} g {} t {} f {}", s, g, t, f); if t { - next_state.set(GameState::Menu) + next_state.set(GameState::Intro) } } diff --git a/src/main.rs b/src/main.rs index 3550c22..84cc1e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,7 +70,6 @@ fn main() { pub enum GameState { #[default] Loading, - Menu, Credits, Intro, Play, @@ -90,31 +89,38 @@ fn debug_state(state: Res>) { info!("State change {:?}", *state); } -fn activate( - mut entities: Query< - &mut Visibility, +pub(crate) fn manage_state_entities() -> impl FnMut( + Query< + (&Marker, &mut Visibility), ( With, Without, Without, ), >, -) { - info!("Activating state"); - entities.iter_mut().for_each(|mut visibility| { - *visibility = Visibility::Visible; - }); -} - -fn deactivate( - mut entities: Query< - &mut Visibility, - (With, Without, Without), + Res>, +) +where + Marker: Component + States + PartialEq + std::fmt::Debug, +{ + move |mut entities: Query< + (&Marker, &mut Visibility), + ( + With, + Without, + Without, + ), >, -) { - entities.iter_mut().for_each(|mut visibility| { - *visibility = Visibility::Hidden; - }); + curr_state: Res>| { + info!("Activating state {:?}", *curr_state); + entities.iter_mut().for_each(|(mark, mut visibility)| { + *visibility = if mark == curr_state.get() { + Visibility::Inherited + } else { + Visibility::Hidden + }; + }); + } } pub(crate) fn any_component_changed(q: Query>) -> bool { diff --git a/src/menu.rs b/src/menu.rs index b56c74a..6c3086c 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,56 +1,48 @@ -use bevy::{ - app::AppExit, - input::{keyboard::KeyboardInput, ButtonState}, -}; - use crate::prelude::*; pub(crate) struct MenuPlugin; impl Plugin for MenuPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, init_menu_ui) + app.add_state::() + // Initialize menus .add_systems( - Update, - ( - handle_menu_button::, // Run in all states - handle_menu_quit.run_if(in_state(GameState::Menu)), - bevy::window::close_on_esc.run_if(in_state(GameState::Menu)), - ), + OnExit(GameState::Loading), + (init_play_menu, init_tutorial_menu, init_endgame_menu), ) + // Manage visible/hidden states .add_systems( - OnEnter(GameState::Menu), - (activate::, activate::), - ) - .add_systems(OnExit(GameState::Menu), deactivate::); + Update, + manage_state_entities::().run_if(state_changed::()), + ); } } -#[derive(Debug, Component)] -struct Menu; - -#[derive(Debug, Component)] -struct Quit; +#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone, Component)] +enum MenuState { + #[default] + None, + Play, + Tutorial, + Endgame, +} fn init_menu_ui(mut commands: Commands) { commands - .spawn(( - Menu, - NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - flex_direction: FlexDirection::Column, - position_type: PositionType::Absolute, - ..default() - }, - background_color: Color::NONE.into(), - visibility: Visibility::Hidden, + .spawn((NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + flex_direction: FlexDirection::Column, + position_type: PositionType::Absolute, ..default() }, - )) + background_color: Color::NONE.into(), + visibility: Visibility::Hidden, + ..default() + },)) .with_children(|parent| { parent.spawn(TextBundle::from_section( "M A R T I A N C H E S S", @@ -115,71 +107,36 @@ fn init_menu_ui(mut commands: Commands) { }); parent - .spawn(( - Quit, - ButtonBundle { - style: Style { - padding: UiRect::all(Val::Px(5.0)), - margin: UiRect::all(Val::Px(5.0)), - ..default() - }, - background_color: Color::ORANGE.with_a(0.5).into(), + .spawn((ButtonBundle { + style: Style { + padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(5.0)), ..default() }, - )) + background_color: Color::ORANGE.with_a(0.5).into(), + ..default() + },)) .with_children(|parent| { - parent.spawn(( - Quit, - TextBundle::from_section( - "Quit", - TextStyle { - color: Color::BLACK, - font_size: 32.0, - ..default() - }, - ), - )); + parent.spawn((TextBundle::from_section( + "Quit", + TextStyle { + color: Color::BLACK, + font_size: 32.0, + ..default() + }, + ),)); }); }); } -pub(crate) fn handle_menu_button( - events: Query<(&Interaction, &T), (With