mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 14:40:19 +00:00
0746b8eb4c
# Objective Fixes #12225 Prior to the `bevy_color` port, `GREEN` used to mean "full green." But it is now a much darker color matching the css1 spec. ## Solution Change usages of `basic::GREEN` or `css::GREEN` to `LIME` to restore the examples to their former colors. This also removes the duplicate definition of `GREEN` from `css`. (it was already re-exported from `basic`) ## Note A lot of these examples could use nicer colors. I'm not trying to do that here. "Dark Grey" will be tackled separately and has its own tracking issue.
820 lines
34 KiB
Rust
820 lines
34 KiB
Rust
//! This example will display a simple menu using Bevy UI where you can start a new game,
|
|
//! change some settings or quit. There is no actual game, it will just display the current
|
|
//! settings for 5 seconds before going back to the menu.
|
|
|
|
use bevy::prelude::*;
|
|
|
|
const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
|
|
|
|
// Enum that will be used as a global state for the game
|
|
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
|
|
enum GameState {
|
|
#[default]
|
|
Splash,
|
|
Menu,
|
|
Game,
|
|
}
|
|
|
|
// One of the two settings that can be set through the menu. It will be a resource in the app
|
|
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
|
|
enum DisplayQuality {
|
|
Low,
|
|
Medium,
|
|
High,
|
|
}
|
|
|
|
// One of the two settings that can be set through the menu. It will be a resource in the app
|
|
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
|
|
struct Volume(u32);
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
// Insert as resource the initial value for the settings resources
|
|
.insert_resource(DisplayQuality::Medium)
|
|
.insert_resource(Volume(7))
|
|
// Declare the game state, whose starting value is determined by the `Default` trait
|
|
.init_state::<GameState>()
|
|
.add_systems(Startup, setup)
|
|
// Adds the plugins for each state
|
|
.add_plugins((splash::splash_plugin, menu::menu_plugin, game::game_plugin))
|
|
.run();
|
|
}
|
|
|
|
fn setup(mut commands: Commands) {
|
|
commands.spawn(Camera2dBundle::default());
|
|
}
|
|
|
|
mod splash {
|
|
use bevy::prelude::*;
|
|
|
|
use super::{despawn_screen, GameState};
|
|
|
|
// This plugin will display a splash screen with Bevy logo for 1 second before switching to the menu
|
|
pub fn splash_plugin(app: &mut App) {
|
|
// As this plugin is managing the splash screen, it will focus on the state `GameState::Splash`
|
|
app
|
|
// When entering the state, spawn everything needed for this screen
|
|
.add_systems(OnEnter(GameState::Splash), splash_setup)
|
|
// While in this state, run the `countdown` system
|
|
.add_systems(Update, countdown.run_if(in_state(GameState::Splash)))
|
|
// When exiting the state, despawn everything that was spawned for this screen
|
|
.add_systems(OnExit(GameState::Splash), despawn_screen::<OnSplashScreen>);
|
|
}
|
|
|
|
// Tag component used to tag entities added on the splash screen
|
|
#[derive(Component)]
|
|
struct OnSplashScreen;
|
|
|
|
// Newtype to use a `Timer` for this screen as a resource
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
struct SplashTimer(Timer);
|
|
|
|
fn splash_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
let icon = asset_server.load("branding/icon.png");
|
|
// Display the logo
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnSplashScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn(ImageBundle {
|
|
style: Style {
|
|
// This will set the logo to be 200px wide, and auto adjust its height
|
|
width: Val::Px(200.0),
|
|
..default()
|
|
},
|
|
image: UiImage::new(icon),
|
|
..default()
|
|
});
|
|
});
|
|
// Insert the timer as a resource
|
|
commands.insert_resource(SplashTimer(Timer::from_seconds(1.0, TimerMode::Once)));
|
|
}
|
|
|
|
// Tick the timer, and change state when finished
|
|
fn countdown(
|
|
mut game_state: ResMut<NextState<GameState>>,
|
|
time: Res<Time>,
|
|
mut timer: ResMut<SplashTimer>,
|
|
) {
|
|
if timer.tick(time.delta()).finished() {
|
|
game_state.set(GameState::Menu);
|
|
}
|
|
}
|
|
}
|
|
|
|
mod game {
|
|
use bevy::{
|
|
color::palettes::basic::{BLUE, LIME},
|
|
prelude::*,
|
|
};
|
|
|
|
use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR};
|
|
|
|
// This plugin will contain the game. In this case, it's just be a screen that will
|
|
// display the current settings for 5 seconds before returning to the menu
|
|
pub fn game_plugin(app: &mut App) {
|
|
app.add_systems(OnEnter(GameState::Game), game_setup)
|
|
.add_systems(Update, game.run_if(in_state(GameState::Game)))
|
|
.add_systems(OnExit(GameState::Game), despawn_screen::<OnGameScreen>);
|
|
}
|
|
|
|
// Tag component used to tag entities added on the game screen
|
|
#[derive(Component)]
|
|
struct OnGameScreen;
|
|
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
struct GameTimer(Timer);
|
|
|
|
fn game_setup(
|
|
mut commands: Commands,
|
|
display_quality: Res<DisplayQuality>,
|
|
volume: Res<Volume>,
|
|
) {
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
// center children
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnGameScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
// First create a `NodeBundle` for centering what we want to display
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
// This will display its children in a column, from top to bottom
|
|
flex_direction: FlexDirection::Column,
|
|
// `align_items` will align children on the cross axis. Here the main axis is
|
|
// vertical (column), so the cross axis is horizontal. This will center the
|
|
// children
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: Color::BLACK.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
// Display two lines of text, the second one with the current settings
|
|
parent.spawn(
|
|
TextBundle::from_section(
|
|
"Will be back to the menu shortly...",
|
|
TextStyle {
|
|
font_size: 80.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
},
|
|
)
|
|
.with_style(Style {
|
|
margin: UiRect::all(Val::Px(50.0)),
|
|
..default()
|
|
}),
|
|
);
|
|
parent.spawn(
|
|
TextBundle::from_sections([
|
|
TextSection::new(
|
|
format!("quality: {:?}", *display_quality),
|
|
TextStyle {
|
|
font_size: 60.0,
|
|
color: BLUE.into(),
|
|
..default()
|
|
},
|
|
),
|
|
TextSection::new(
|
|
" - ",
|
|
TextStyle {
|
|
font_size: 60.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
},
|
|
),
|
|
TextSection::new(
|
|
format!("volume: {:?}", *volume),
|
|
TextStyle {
|
|
font_size: 60.0,
|
|
color: LIME.into(),
|
|
..default()
|
|
},
|
|
),
|
|
])
|
|
.with_style(Style {
|
|
margin: UiRect::all(Val::Px(50.0)),
|
|
..default()
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
// Spawn a 5 seconds timer to trigger going back to the menu
|
|
commands.insert_resource(GameTimer(Timer::from_seconds(5.0, TimerMode::Once)));
|
|
}
|
|
|
|
// Tick the timer, and change state when finished
|
|
fn game(
|
|
time: Res<Time>,
|
|
mut game_state: ResMut<NextState<GameState>>,
|
|
mut timer: ResMut<GameTimer>,
|
|
) {
|
|
if timer.tick(time.delta()).finished() {
|
|
game_state.set(GameState::Menu);
|
|
}
|
|
}
|
|
}
|
|
|
|
mod menu {
|
|
use bevy::{app::AppExit, color::palettes::css::CRIMSON, prelude::*};
|
|
|
|
use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR};
|
|
|
|
// This plugin manages the menu, with 5 different screens:
|
|
// - a main menu with "New Game", "Settings", "Quit"
|
|
// - a settings menu with two submenus and a back button
|
|
// - two settings screen with a setting that can be set and a back button
|
|
pub fn menu_plugin(app: &mut App) {
|
|
app
|
|
// At start, the menu is not enabled. This will be changed in `menu_setup` when
|
|
// entering the `GameState::Menu` state.
|
|
// Current screen in the menu is handled by an independent state from `GameState`
|
|
.init_state::<MenuState>()
|
|
.add_systems(OnEnter(GameState::Menu), menu_setup)
|
|
// Systems to handle the main menu screen
|
|
.add_systems(OnEnter(MenuState::Main), main_menu_setup)
|
|
.add_systems(OnExit(MenuState::Main), despawn_screen::<OnMainMenuScreen>)
|
|
// Systems to handle the settings menu screen
|
|
.add_systems(OnEnter(MenuState::Settings), settings_menu_setup)
|
|
.add_systems(
|
|
OnExit(MenuState::Settings),
|
|
despawn_screen::<OnSettingsMenuScreen>,
|
|
)
|
|
// Systems to handle the display settings screen
|
|
.add_systems(
|
|
OnEnter(MenuState::SettingsDisplay),
|
|
display_settings_menu_setup,
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(setting_button::<DisplayQuality>.run_if(in_state(MenuState::SettingsDisplay)),),
|
|
)
|
|
.add_systems(
|
|
OnExit(MenuState::SettingsDisplay),
|
|
despawn_screen::<OnDisplaySettingsMenuScreen>,
|
|
)
|
|
// Systems to handle the sound settings screen
|
|
.add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup)
|
|
.add_systems(
|
|
Update,
|
|
setting_button::<Volume>.run_if(in_state(MenuState::SettingsSound)),
|
|
)
|
|
.add_systems(
|
|
OnExit(MenuState::SettingsSound),
|
|
despawn_screen::<OnSoundSettingsMenuScreen>,
|
|
)
|
|
// Common systems to all screens that handles buttons behavior
|
|
.add_systems(
|
|
Update,
|
|
(menu_action, button_system).run_if(in_state(GameState::Menu)),
|
|
);
|
|
}
|
|
|
|
// State used for the current menu screen
|
|
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
|
|
enum MenuState {
|
|
Main,
|
|
Settings,
|
|
SettingsDisplay,
|
|
SettingsSound,
|
|
#[default]
|
|
Disabled,
|
|
}
|
|
|
|
// Tag component used to tag entities added on the main menu screen
|
|
#[derive(Component)]
|
|
struct OnMainMenuScreen;
|
|
|
|
// Tag component used to tag entities added on the settings menu screen
|
|
#[derive(Component)]
|
|
struct OnSettingsMenuScreen;
|
|
|
|
// Tag component used to tag entities added on the display settings menu screen
|
|
#[derive(Component)]
|
|
struct OnDisplaySettingsMenuScreen;
|
|
|
|
// Tag component used to tag entities added on the sound settings menu screen
|
|
#[derive(Component)]
|
|
struct OnSoundSettingsMenuScreen;
|
|
|
|
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 HOVERED_PRESSED_BUTTON: Color = Color::srgb(0.25, 0.65, 0.25);
|
|
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
|
|
|
|
// Tag component used to mark which setting is currently selected
|
|
#[derive(Component)]
|
|
struct SelectedOption;
|
|
|
|
// All actions that can be triggered from a button click
|
|
#[derive(Component)]
|
|
enum MenuButtonAction {
|
|
Play,
|
|
Settings,
|
|
SettingsDisplay,
|
|
SettingsSound,
|
|
BackToMainMenu,
|
|
BackToSettings,
|
|
Quit,
|
|
}
|
|
|
|
// This system handles changing all buttons color based on mouse interaction
|
|
fn button_system(
|
|
mut interaction_query: Query<
|
|
(&Interaction, &mut UiImage, Option<&SelectedOption>),
|
|
(Changed<Interaction>, With<Button>),
|
|
>,
|
|
) {
|
|
for (interaction, mut image, selected) in &mut interaction_query {
|
|
image.color = match (*interaction, selected) {
|
|
(Interaction::Pressed, _) | (Interaction::None, Some(_)) => PRESSED_BUTTON,
|
|
(Interaction::Hovered, Some(_)) => HOVERED_PRESSED_BUTTON,
|
|
(Interaction::Hovered, None) => HOVERED_BUTTON,
|
|
(Interaction::None, None) => NORMAL_BUTTON,
|
|
}
|
|
}
|
|
}
|
|
|
|
// This system updates the settings when a new value for a setting is selected, and marks
|
|
// the button as the one currently selected
|
|
fn setting_button<T: Resource + Component + PartialEq + Copy>(
|
|
interaction_query: Query<(&Interaction, &T, Entity), (Changed<Interaction>, With<Button>)>,
|
|
mut selected_query: Query<(Entity, &mut UiImage), With<SelectedOption>>,
|
|
mut commands: Commands,
|
|
mut setting: ResMut<T>,
|
|
) {
|
|
for (interaction, button_setting, entity) in &interaction_query {
|
|
if *interaction == Interaction::Pressed && *setting != *button_setting {
|
|
let (previous_button, mut previous_image) = selected_query.single_mut();
|
|
previous_image.color = NORMAL_BUTTON;
|
|
commands.entity(previous_button).remove::<SelectedOption>();
|
|
commands.entity(entity).insert(SelectedOption);
|
|
*setting = *button_setting;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn menu_setup(mut menu_state: ResMut<NextState<MenuState>>) {
|
|
menu_state.set(MenuState::Main);
|
|
}
|
|
|
|
fn main_menu_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
// Common style for all buttons on the screen
|
|
let button_style = Style {
|
|
width: Val::Px(250.0),
|
|
height: Val::Px(65.0),
|
|
margin: UiRect::all(Val::Px(20.0)),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
};
|
|
let button_icon_style = Style {
|
|
width: Val::Px(30.0),
|
|
// This takes the icons out of the flexbox flow, to be positioned exactly
|
|
position_type: PositionType::Absolute,
|
|
// The icon will be close to the left border of the button
|
|
left: Val::Px(10.0),
|
|
..default()
|
|
};
|
|
let button_text_style = TextStyle {
|
|
font_size: 40.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
};
|
|
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnMainMenuScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
// Display the game name
|
|
parent.spawn(
|
|
TextBundle::from_section(
|
|
"Bevy Game Menu UI",
|
|
TextStyle {
|
|
font_size: 80.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
},
|
|
)
|
|
.with_style(Style {
|
|
margin: UiRect::all(Val::Px(50.0)),
|
|
..default()
|
|
}),
|
|
);
|
|
|
|
// Display three buttons for each action available from the main menu:
|
|
// - new game
|
|
// - settings
|
|
// - quit
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style.clone(),
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
MenuButtonAction::Play,
|
|
))
|
|
.with_children(|parent| {
|
|
let icon = asset_server.load("textures/Game Icons/right.png");
|
|
parent.spawn(ImageBundle {
|
|
style: button_icon_style.clone(),
|
|
image: UiImage::new(icon),
|
|
..default()
|
|
});
|
|
parent.spawn(TextBundle::from_section(
|
|
"New Game",
|
|
button_text_style.clone(),
|
|
));
|
|
});
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style.clone(),
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
MenuButtonAction::Settings,
|
|
))
|
|
.with_children(|parent| {
|
|
let icon = asset_server.load("textures/Game Icons/wrench.png");
|
|
parent.spawn(ImageBundle {
|
|
style: button_icon_style.clone(),
|
|
image: UiImage::new(icon),
|
|
..default()
|
|
});
|
|
parent.spawn(TextBundle::from_section(
|
|
"Settings",
|
|
button_text_style.clone(),
|
|
));
|
|
});
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style,
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
MenuButtonAction::Quit,
|
|
))
|
|
.with_children(|parent| {
|
|
let icon = asset_server.load("textures/Game Icons/exitRight.png");
|
|
parent.spawn(ImageBundle {
|
|
style: button_icon_style,
|
|
image: UiImage::new(icon),
|
|
..default()
|
|
});
|
|
parent.spawn(TextBundle::from_section("Quit", button_text_style));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
fn settings_menu_setup(mut commands: Commands) {
|
|
let button_style = Style {
|
|
width: Val::Px(200.0),
|
|
height: Val::Px(65.0),
|
|
margin: UiRect::all(Val::Px(20.0)),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
};
|
|
|
|
let button_text_style = TextStyle {
|
|
font_size: 40.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
};
|
|
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnSettingsMenuScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
for (action, text) in [
|
|
(MenuButtonAction::SettingsDisplay, "Display"),
|
|
(MenuButtonAction::SettingsSound, "Sound"),
|
|
(MenuButtonAction::BackToMainMenu, "Back"),
|
|
] {
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style.clone(),
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
action,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn(TextBundle::from_section(
|
|
text,
|
|
button_text_style.clone(),
|
|
));
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
fn display_settings_menu_setup(mut commands: Commands, display_quality: Res<DisplayQuality>) {
|
|
let button_style = Style {
|
|
width: Val::Px(200.0),
|
|
height: Val::Px(65.0),
|
|
margin: UiRect::all(Val::Px(20.0)),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
};
|
|
let button_text_style = TextStyle {
|
|
font_size: 40.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
};
|
|
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnDisplaySettingsMenuScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
// Create a new `NodeBundle`, this time not setting its `flex_direction`. It will
|
|
// use the default value, `FlexDirection::Row`, from left to right.
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
// Display a label for the current setting
|
|
parent.spawn(TextBundle::from_section(
|
|
"Display Quality",
|
|
button_text_style.clone(),
|
|
));
|
|
// Display a button for each possible value
|
|
for quality_setting in [
|
|
DisplayQuality::Low,
|
|
DisplayQuality::Medium,
|
|
DisplayQuality::High,
|
|
] {
|
|
let mut entity = parent.spawn((
|
|
ButtonBundle {
|
|
style: Style {
|
|
width: Val::Px(150.0),
|
|
height: Val::Px(65.0),
|
|
..button_style.clone()
|
|
},
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
quality_setting,
|
|
));
|
|
entity.with_children(|parent| {
|
|
parent.spawn(TextBundle::from_section(
|
|
format!("{quality_setting:?}"),
|
|
button_text_style.clone(),
|
|
));
|
|
});
|
|
if *display_quality == quality_setting {
|
|
entity.insert(SelectedOption);
|
|
}
|
|
}
|
|
});
|
|
// Display the back button to return to the settings screen
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style,
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
MenuButtonAction::BackToSettings,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn(TextBundle::from_section("Back", button_text_style));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
fn sound_settings_menu_setup(mut commands: Commands, volume: Res<Volume>) {
|
|
let button_style = Style {
|
|
width: Val::Px(200.0),
|
|
height: Val::Px(65.0),
|
|
margin: UiRect::all(Val::Px(20.0)),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
};
|
|
let button_text_style = TextStyle {
|
|
font_size: 40.0,
|
|
color: TEXT_COLOR,
|
|
..default()
|
|
};
|
|
|
|
commands
|
|
.spawn((
|
|
NodeBundle {
|
|
style: Style {
|
|
width: Val::Percent(100.0),
|
|
height: Val::Percent(100.0),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
OnSoundSettingsMenuScreen,
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
background_color: CRIMSON.into(),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
parent.spawn(TextBundle::from_section(
|
|
"Volume",
|
|
button_text_style.clone(),
|
|
));
|
|
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
|
|
let mut entity = parent.spawn((
|
|
ButtonBundle {
|
|
style: Style {
|
|
width: Val::Px(30.0),
|
|
height: Val::Px(65.0),
|
|
..button_style.clone()
|
|
},
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
Volume(volume_setting),
|
|
));
|
|
if *volume == Volume(volume_setting) {
|
|
entity.insert(SelectedOption);
|
|
}
|
|
}
|
|
});
|
|
parent
|
|
.spawn((
|
|
ButtonBundle {
|
|
style: button_style,
|
|
image: UiImage::default().with_color(NORMAL_BUTTON),
|
|
..default()
|
|
},
|
|
MenuButtonAction::BackToSettings,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn(TextBundle::from_section("Back", button_text_style));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
fn menu_action(
|
|
interaction_query: Query<
|
|
(&Interaction, &MenuButtonAction),
|
|
(Changed<Interaction>, With<Button>),
|
|
>,
|
|
mut app_exit_events: EventWriter<AppExit>,
|
|
mut menu_state: ResMut<NextState<MenuState>>,
|
|
mut game_state: ResMut<NextState<GameState>>,
|
|
) {
|
|
for (interaction, menu_button_action) in &interaction_query {
|
|
if *interaction == Interaction::Pressed {
|
|
match menu_button_action {
|
|
MenuButtonAction::Quit => {
|
|
app_exit_events.send(AppExit);
|
|
}
|
|
MenuButtonAction::Play => {
|
|
game_state.set(GameState::Game);
|
|
menu_state.set(MenuState::Disabled);
|
|
}
|
|
MenuButtonAction::Settings => menu_state.set(MenuState::Settings),
|
|
MenuButtonAction::SettingsDisplay => {
|
|
menu_state.set(MenuState::SettingsDisplay);
|
|
}
|
|
MenuButtonAction::SettingsSound => {
|
|
menu_state.set(MenuState::SettingsSound);
|
|
}
|
|
MenuButtonAction::BackToMainMenu => menu_state.set(MenuState::Main),
|
|
MenuButtonAction::BackToSettings => {
|
|
menu_state.set(MenuState::Settings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generic system that takes a component as a parameter, and will despawn all entities with that component
|
|
fn despawn_screen<T: Component>(to_despawn: Query<Entity, With<T>>, mut commands: Commands) {
|
|
for entity in &to_despawn {
|
|
commands.entity(entity).despawn_recursive();
|
|
}
|
|
}
|