Skip to content

Commit

Permalink
enable menu navigation via Bevy UI Navigation crate
Browse files Browse the repository at this point in the history
  • Loading branch information
thombruce committed Nov 6, 2023
1 parent bf6966f commit fec8e52
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 25 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- New start menu with "New Game", "Settings", "Credits" and "Quit" buttons

### Changed

- Changed basic English (US) translations for start menu
- Changed basic Russian (RU) translations for start menu
- Changed basic German (DE) translations for start menu

## [0.0.22] - 2023-11-03

### Added
Expand Down
92 changes: 92 additions & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
bevy = "0.11.3"
bevy-inspector-egui = "0.20.0"
bevy-ui-navigation = "0.32.0"
bevy_asset_loader = { version = "0.17.0", features = ["2d"] }
bevy_common_assets = { version = "0.7.0", features = ["ron"] }
bevy_fluent = "0.7.0"
Expand Down
13 changes: 12 additions & 1 deletion src/inputs/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,26 @@ use leafwing_input_manager::prelude::*;

#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
pub enum MenuAction {
Select,
Start,
Settings,
Credits,
Quit,
}

pub fn menu_input_map() -> InputMap<MenuAction> {
let mut input_map = InputMap::<MenuAction>::new([
(KeyCode::Return, MenuAction::Start),
// Action Keys
(KeyCode::Return, MenuAction::Select),
// Hotkeys
(KeyCode::N, MenuAction::Start),
(KeyCode::S, MenuAction::Settings),
(KeyCode::C, MenuAction::Credits),
(KeyCode::Q, MenuAction::Quit),
]);
// Action Buttons
input_map.insert(GamepadButtonType::South, MenuAction::Select);
// Hotkey Buttons
input_map.insert(GamepadButtonType::Start, MenuAction::Start);
input_map.insert(GamepadButtonType::North, MenuAction::Credits);

Expand Down
7 changes: 7 additions & 0 deletions src/systems/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::prelude::*;
use bevy_ui_navigation::prelude::NavRequestSystem;

pub mod events;
pub mod states;
Expand Down Expand Up @@ -133,6 +134,12 @@ impl Plugin for SystemsPlugin {
Update,
start_menu::menu_input_system.run_if(is_in_menu_state),
);
app.add_systems(
Update,
start_menu::menu_focus_system
.after(NavRequestSystem)
.run_if(is_in_menu_state),
);

app.add_systems(
Update,
Expand Down
105 changes: 81 additions & 24 deletions src/ui/menus/start_menu.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use bevy::{
app::AppExit,
audio::{PlaybackMode, Volume},
prelude::*,
};
use bevy_ui_navigation::{prelude::*, systems::InputMapping};
use fluent_content::Content;
use leafwing_input_manager::prelude::{ActionState, InputManagerPlugin};

Expand All @@ -15,11 +17,30 @@ use crate::{
pub struct MenuPlugin;
impl Plugin for MenuPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(InputManagerPlugin::<MenuAction>::default());
app.add_plugins((
InputManagerPlugin::<MenuAction>::default(),
DefaultNavigationPlugins,
));
}
}

pub(crate) fn init_start_menu(mut commands: Commands, audios: Res<AudioAssets>) {
#[derive(Component)]
pub enum MenuButton {
NewGame,
Settings,
Credits,
Quit,
}

pub(crate) fn init_start_menu(
mut commands: Commands,
audios: Res<AudioAssets>,
mut input_mapping: ResMut<InputMapping>,
) {
// TODO: We might prefer to do this part ourselves... maybe.
input_mapping.keyboard_navigation = true;
// input_mapping.focus_follows_mouse = true;

commands.insert_resource(menu_input_map());
commands.insert_resource(ActionState::<MenuAction>::default());

Expand Down Expand Up @@ -76,35 +97,33 @@ pub(crate) fn spawn_start_menu(mut commands: Commands, ui: Res<UiAssets>, i18n:
Name::new("Title"),
));

for string in ["new-game", "settings", "credits", "quit"] {
parent
.spawn(ButtonBundle {
for (string, marker) in [
("new-game", MenuButton::NewGame),
("settings", MenuButton::Settings),
("credits", MenuButton::Credits),
("quit", MenuButton::Quit),
] {
parent.spawn((
TextBundle {
text: Text::from_section(
i18n.content(string).unwrap().to_ascii_uppercase(),
TextStyle {
font: ui.font.clone(),
font_size: 25.0,
color: Color::rgb_u8(0x00, 0x88, 0x88),
},
),
style: Style {
margin: UiRect::top(Val::Px(25.)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
background_color: BackgroundColor(Color::Rgba {
red: 0.,
green: 0.,
blue: 0.,
alpha: 0.,
}),
..default()
})
.with_children(|parent| {
parent.spawn(
TextBundle::from_section(
i18n.content(string).unwrap().to_ascii_uppercase(),
TextStyle {
font: ui.font.clone(),
font_size: 25.0,
color: Color::rgb_u8(0x00, 0x88, 0x88),
},
), // DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)),
);
});
}, // DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)),
Focusable::default(),
marker,
));
}
});
}
Expand All @@ -113,7 +132,32 @@ pub(crate) fn menu_input_system(
state: Res<State<GameState>>,
mut next_state: ResMut<NextState<GameState>>,
inputs: Res<ActionState<MenuAction>>,
mut requests: EventWriter<NavRequest>,
mut buttons: Query<&mut MenuButton>,
mut events: EventReader<NavEvent>,
mut exit: EventWriter<AppExit>,
) {
events.nav_iter().activated_in_query_foreach_mut(
&mut buttons,
|mut button| match &mut *button {
MenuButton::NewGame => {
next_state.set(GameState::GameCreate);
}
MenuButton::Settings => {
// start the game
}
MenuButton::Credits => {
next_state.set(GameState::Credits);
}
MenuButton::Quit => {
exit.send(AppExit);
}
},
);

if inputs.just_pressed(MenuAction::Select) {
requests.send(NavRequest::Action);
}
if inputs.just_pressed(MenuAction::Start) {
next_state.set(GameState::GameCreate);
}
Expand All @@ -129,3 +173,16 @@ pub(crate) fn menu_input_system(
}
}
}

// TODO: This is very general. It can be moved to a less specific location.
pub(crate) fn menu_focus_system(
mut interaction_query: Query<(&Focusable, &mut Text), Changed<Focusable>>,
) {
for (focusable, mut text) in interaction_query.iter_mut() {
if let FocusState::Focused = focusable.state() {
text.sections[0].style.color = Color::rgb_u8(0x00, 0x88, 0x88);
} else {
text.sections[0].style.color = Color::rgb_u8(0x00, 0x44, 0x44);
}
}
}

0 comments on commit fec8e52

Please sign in to comment.