implement game menu

This commit is contained in:
yuni 2024-05-13 20:21:56 +02:00
parent 2cf10f2395
commit eb681999f8
9 changed files with 275 additions and 36 deletions

View file

@ -35,7 +35,7 @@ impl Plugin for ActorPlugin {
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
.after(sync::position_to_transform)); .after(sync::position_to_transform));
app.add_systems(Update, ( app.add_systems(Update, (
handle_input.run_if(alive), handle_input.run_if(in_control),
handle_collisions, handle_collisions,
handle_damage, handle_damage,
)); ));

View file

@ -19,7 +19,7 @@ pub struct AudioPlugin;
impl Plugin for AudioPlugin { impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, handle_input.run_if(alive)); app.add_systems(Update, handle_input.run_if(in_control));
app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>())); app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>()));
app.add_systems(Update, play_zoom_sfx); app.add_systems(Update, play_zoom_sfx);
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>())); app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
@ -160,19 +160,19 @@ pub fn respawn_sinks(
pub fn handle_input( pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut evwriter_toggle: EventWriter<ToggleMusicEvent>, mut ew_toggle: EventWriter<ToggleMusicEvent>,
mut evwriter_sfx: EventWriter<PlaySfxEvent>, mut ew_sfx: EventWriter<PlaySfxEvent>,
mut settings: ResMut<var::Settings>, mut settings: ResMut<var::Settings>,
) { ) {
if keyboard_input.just_pressed(settings.key_toggle_music) { if keyboard_input.just_pressed(settings.key_toggle_music) {
settings.mute_music ^= true; settings.mute_music ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click)); ew_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent()); ew_toggle.send(ToggleMusicEvent());
} }
if keyboard_input.just_pressed(settings.key_toggle_sfx) { if keyboard_input.just_pressed(settings.key_toggle_sfx) {
settings.mute_sfx ^= true; settings.mute_sfx ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click)); ew_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent()); ew_toggle.send(ToggleMusicEvent());
} }
} }

View file

@ -31,7 +31,7 @@ pub struct CameraPlugin;
impl Plugin for CameraPlugin { impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera); app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input.run_if(alive)); app.add_systems(Update, handle_input.run_if(in_control));
app.add_systems(Update, update_map_only_object_visibility.run_if(alive)); app.add_systems(Update, update_map_only_object_visibility.run_if(alive));
app.add_systems(Update, manage_player_actor.after(handle_input)); app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player app.add_systems(PostUpdate, sync_camera_to_player
@ -391,7 +391,7 @@ pub fn apply_input_to_player(
Option<&actor::PlayerDrivesThis>, Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>, ), (With<actor::PlayerCamera>, Without<Camera>)>,
) { ) {
if settings.map_active { if settings.map_active || !settings.in_control() {
return; return;
} }
let dt = time.delta_seconds(); let dt = time.delta_seconds();

View file

@ -59,3 +59,7 @@ pub fn style_centered() -> Style {
pub fn alive(settings: Res<Settings>) -> bool { pub fn alive(settings: Res<Settings>) -> bool {
return settings.alive; return settings.alive;
} }
pub fn in_control(settings: Res<Settings>) -> bool {
return settings.in_control();
}

View file

@ -14,24 +14,100 @@ use crate::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::pbr::ExtendedMaterial; use bevy::pbr::ExtendedMaterial;
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
use bevy::window::{Window, WindowMode, PrimaryWindow};
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
pub struct GamePlugin; pub struct GamePlugin;
impl Plugin for GamePlugin { impl Plugin for GamePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, handle_cheats); app.add_systems(Update, handle_cheats.run_if(in_control));
app.add_systems(Update, debug); app.add_systems(Update, debug);
app.add_systems(Update, handle_game_event);
app.add_systems(PreUpdate, handle_player_death); app.add_systems(PreUpdate, handle_player_death);
app.add_systems(PostUpdate, update_id2pos); app.add_systems(PostUpdate, update_id2pos);
app.insert_resource(Id2Pos(HashMap::new())); app.insert_resource(Id2Pos(HashMap::new()));
app.add_event::<PlayerDiesEvent>(); app.add_event::<PlayerDiesEvent>();
app.add_event::<GameEvent>();
} }
} }
#[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType); #[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType);
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>); #[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Event)]
pub enum GameEvent {
SetAR(Turn),
SetMusic(Turn),
SetSound(Turn),
SetMap(Turn),
SetFullscreen(Turn),
}
pub enum Turn {
On,
Off,
Toggle,
}
impl Turn {
pub fn to_bool(&self, current_state: bool) -> bool {
match self {
Turn::On => true,
Turn::Off => false,
Turn::Toggle => !current_state,
}
}
}
fn handle_game_event(
mut settings: ResMut<Settings>,
mut er_game: EventReader<GameEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
mut mapcam: ResMut<camera::MapCam>,
opt: Res<var::CommandLineOptions>,
) {
for event in er_game.read() {
match event {
GameEvent::SetAR(turn) => {
settings.hud_active = turn.to_bool(settings.hud_active);
ew_togglemusic.send(audio::ToggleMusicEvent());
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetMusic(turn) => {
// TODO invert "mute_music" to "music_active"
settings.mute_music = turn.to_bool(settings.mute_music);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetSound(turn) => {
// TODO invert "mute_sfx" to "sfx_active"
settings.mute_sfx = turn.to_bool(settings.mute_sfx);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetMap(turn) => {
settings.map_active = turn.to_bool(settings.map_active);
if settings.map_active {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Woosh));
}
*mapcam = camera::MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetFullscreen(turn) => {
for mut window in &mut q_window {
let current_state = window.mode != WindowMode::Windowed;
window.mode = match turn.to_bool(current_state) {
true => opt.window_mode_fullscreen,
false => WindowMode::Windowed
};
}
}
}
}
}
fn handle_player_death( fn handle_player_death(
mut cmd: Commands, mut cmd: Commands,
mut er_playerdies: EventReader<PlayerDiesEvent>, mut er_playerdies: EventReader<PlayerDiesEvent>,

View file

@ -41,7 +41,7 @@ impl Plugin for HudPlugin {
update_dashboard, update_dashboard,
update_speedometer, update_speedometer,
update_gauges, update_gauges,
handle_input.run_if(alive), handle_input.run_if(in_control),
handle_target_event, handle_target_event,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
@ -938,12 +938,11 @@ fn update_hud(
fn handle_input( fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_input: Res<ButtonInput<MouseButton>>, mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<Settings>, settings: Res<Settings>,
mut log: ResMut<Log>, mut log: ResMut<Log>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut ew_target: EventWriter<TargetEvent>, mut ew_target: EventWriter<TargetEvent>,
mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>, mut ew_game: EventWriter<GameEvent>,
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>, q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
) { ) {
@ -954,10 +953,8 @@ fn handle_input(
} }
} }
if keyboard_input.just_pressed(settings.key_togglehud) { if keyboard_input.just_pressed(settings.key_togglehud) {
settings.hud_active ^= true; ew_game.send(GameEvent::SetAR(Turn::Toggle));
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
ew_togglemusic.send(audio::ToggleMusicEvent());
ew_updateoverlays.send(UpdateOverlayVisibility);
} }
if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) { if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) {
if let Ok(camtrans) = q_camera.get_single() { if let Ok(camtrans) = q_camera.get_single() {

View file

@ -33,6 +33,7 @@ pub mod prelude {
pub use crate::common::*; pub use crate::common::*;
pub use crate::var::Settings; pub use crate::var::Settings;
pub use crate::load::load_asset; pub use crate::load::load_asset;
pub use game::{GameEvent, Turn};
} }
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode}; use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
@ -54,7 +55,7 @@ Note: borderless fullscreen is the default, but it crashes on some systems.";
fn main() { fn main() {
let prefs = var::load_prefs(); let prefs = var::load_prefs();
let mut opt = CommandLineOptions { let mut opt = var::CommandLineOptions {
window_mode_fullscreen: prefs.get_fullscreen_mode(), window_mode_fullscreen: prefs.get_fullscreen_mode(),
window_mode_initial: prefs.get_window_mode(), window_mode_initial: prefs.get_window_mode(),
use_gl: prefs.render_mode_is_gl(), use_gl: prefs.render_mode_is_gl(),
@ -146,16 +147,9 @@ impl Plugin for OutFlyPlugin {
} }
} }
#[derive(Resource, Default)]
pub struct CommandLineOptions {
window_mode_fullscreen: WindowMode,
window_mode_initial: WindowMode,
use_gl: bool,
}
fn setup( fn setup(
mut windows: Query<&mut Window, With<PrimaryWindow>>, mut windows: Query<&mut Window, With<PrimaryWindow>>,
opt: Res<CommandLineOptions>, opt: Res<var::CommandLineOptions>,
) { ) {
for mut window in &mut windows { for mut window in &mut windows {
window.cursor.grab_mode = CursorGrabMode::Locked; window.cursor.grab_mode = CursorGrabMode::Locked;
@ -168,14 +162,10 @@ fn setup(
fn handle_input( fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
opt: Res<CommandLineOptions>, opt: Res<var::CommandLineOptions>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
mut windows: Query<&mut Window, With<PrimaryWindow>>, mut windows: Query<&mut Window, With<PrimaryWindow>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) { ) {
if keyboard_input.pressed(settings.key_exit) {
app_exit_events.send(bevy::app::AppExit);
}
if keyboard_input.just_pressed(settings.key_fullscreen) { if keyboard_input.just_pressed(settings.key_fullscreen) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
for mut window in &mut windows { for mut window in &mut windows {

View file

@ -22,22 +22,44 @@ impl Plugin for MenuPlugin {
app.add_systems(Startup, setup.after(hud::setup)); app.add_systems(Startup, setup.after(hud::setup));
app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::<DeathScreenEvent>())); app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::<DeathScreenEvent>()));
app.add_systems(Update, handle_deathscreen_input); app.add_systems(Update, handle_deathscreen_input);
app.add_systems(PostUpdate, update_menu);
app.add_systems(Update, handle_input.run_if(alive));
app.insert_resource(MenuState::default());
app.add_event::<DeathScreenEvent>(); app.add_event::<DeathScreenEvent>();
} }
} }
#[derive(Component)] pub struct MenuElement;
#[derive(Component)] pub struct MenuTopLevel;
#[derive(Component)] pub struct DeathScreenElement; #[derive(Component)] pub struct DeathScreenElement;
#[derive(Component)] pub struct BlackScreen;
#[derive(Component)] pub struct DeathText; #[derive(Component)] pub struct DeathText;
#[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide } #[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide }
pub const MENUDEF: &[(&str, MenuAction)] = &[
("Open Map", MenuAction::ToggleMap),
("Toggle Augmented Reality", MenuAction::ToggleAR),
("Toggle Sound", MenuAction::ToggleSound),
("Toggle Music", MenuAction::ToggleMusic),
("Toggle Fullscreen", MenuAction::ToggleFullscreen),
("Quit", MenuAction::Quit),
];
#[derive(Component)]
pub enum MenuAction {
ToggleMap,
ToggleAR,
ToggleSound,
ToggleMusic,
ToggleFullscreen,
Quit,
}
pub fn setup( pub fn setup(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
settings: Res<Settings>, settings: Res<Settings>,
) { ) {
commands.spawn(( commands.spawn((
BlackScreen,
DeathScreenElement, DeathScreenElement,
NodeBundle { NodeBundle {
style: style_fullscreen(), style: style_fullscreen(),
@ -98,6 +120,54 @@ pub fn setup(
}, },
)); ));
}); });
let style_menu = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_hud,
color: settings.hud_color,
..default()
};
let sections: Vec<TextSection> = Vec::from_iter(MENUDEF.iter().map(|(label, _)|
TextSection::new(label.to_string() + "\n", style_menu.clone())
));
commands.spawn((
MenuElement,
NodeBundle {
style: style_fullscreen(),
background_color: Color::rgba(0.0, 0.0, 0.0, 0.8).into(),
visibility: Visibility::Hidden,
..default()
},
));
commands.spawn((
MenuElement,
NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceAround,
..default()
},
visibility: Visibility::Hidden,
..default()
},
)).with_children(|builder| {
builder.spawn((
MenuTopLevel,
TextBundle {
text: Text {
sections,
justify: JustifyText::Center,
..default()
},
..default()
},
));
});
} }
pub fn show_deathscreen( pub fn show_deathscreen(
@ -151,3 +221,92 @@ pub fn handle_deathscreen_input(
ew_deathscreen.send(DeathScreenEvent::Hide); ew_deathscreen.send(DeathScreenEvent::Hide);
} }
} }
#[derive(Resource, Debug, Default)]
pub struct MenuState {
cursor: usize,
}
pub fn update_menu(
mut q_text: Query<&mut Text, With<MenuTopLevel>>,
menustate: Res<MenuState>,
settings: Res<Settings>,
) {
if !settings.menu_active {
return;
}
if let Ok(mut text) = q_text.get_single_mut() {
for i in 0..text.sections.len() {
if menustate.cursor == i {
text.sections[i].style.color = settings.hud_color_subtitles;
} else {
text.sections[i].style.color = settings.hud_color;
}
}
}
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<Settings>,
mut q_vis: Query<&mut Visibility, With<MenuElement>>,
mut menustate: ResMut<MenuState>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
mut ew_game: EventWriter<game::GameEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if keyboard_input.just_pressed(settings.key_menu)
|| keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active
{
settings.menu_active ^= true;
for mut vis in &mut q_vis {
*vis = bool2vis(settings.menu_active);
}
}
if !settings.menu_active {
return;
}
if keyboard_input.just_pressed(settings.key_forward)
|| keyboard_input.just_pressed(settings.key_mouseup)
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
{
menustate.cursor = menustate.cursor.saturating_sub(1);
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
}
if keyboard_input.just_pressed(settings.key_back)
|| keyboard_input.just_pressed(settings.key_mousedown)
|| keyboard_input.just_pressed(KeyCode::ArrowDown)
{
menustate.cursor = (menustate.cursor + 1).min(MENUDEF.len() - 1);
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
}
if keyboard_input.just_pressed(settings.key_interact)
|| keyboard_input.just_pressed(KeyCode::Enter)
{
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
match MENUDEF[menustate.cursor].1 {
MenuAction::ToggleMap => {
ew_game.send(GameEvent::SetMap(Turn::Toggle));
settings.menu_active = false;
for mut vis in &mut q_vis {
*vis = Visibility::Hidden;
}
},
MenuAction::ToggleAR => {
ew_game.send(GameEvent::SetAR(Turn::Toggle));
},
MenuAction::ToggleMusic => {
ew_game.send(GameEvent::SetMusic(Turn::Toggle));
},
MenuAction::ToggleSound => {
ew_game.send(GameEvent::SetSound(Turn::Toggle));
},
MenuAction::ToggleFullscreen => {
ew_game.send(GameEvent::SetFullscreen(Turn::Toggle));
},
MenuAction::Quit => {
app_exit_events.send(bevy::app::AppExit);
},
};
}
}

View file

@ -71,6 +71,7 @@ pub struct Settings {
pub hud_active: bool, pub hud_active: bool,
pub map_active: bool, pub map_active: bool,
pub deathscreen_active: bool, pub deathscreen_active: bool,
pub menu_active: bool,
pub death_cause: String, pub death_cause: String,
pub is_zooming: bool, pub is_zooming: bool,
pub third_person: bool, pub third_person: bool,
@ -87,7 +88,7 @@ pub struct Settings {
//pub key_map_zoom_out_wheel: MouseButton, //pub key_map_zoom_out_wheel: MouseButton,
//pub key_map_zoom_in_wheel: MouseButton, //pub key_map_zoom_in_wheel: MouseButton,
pub key_togglehud: KeyCode, pub key_togglehud: KeyCode,
pub key_exit: KeyCode, pub key_menu: KeyCode,
pub key_restart: KeyCode, pub key_restart: KeyCode,
pub key_fullscreen: KeyCode, pub key_fullscreen: KeyCode,
pub key_help: KeyCode, pub key_help: KeyCode,
@ -187,6 +188,7 @@ impl Default for Settings {
hud_active: true, hud_active: true,
map_active: false, map_active: false,
deathscreen_active: false, deathscreen_active: false,
menu_active: false,
death_cause: "Unknown".to_string(), death_cause: "Unknown".to_string(),
is_zooming: false, is_zooming: false,
third_person: true, third_person: true,
@ -203,7 +205,7 @@ impl Default for Settings {
//key_map_zoom_out_wheel: KeyCode::Shift, //key_map_zoom_out_wheel: KeyCode::Shift,
//key_map_zoom_in_wheel: KeyCode::Shift, //key_map_zoom_in_wheel: KeyCode::Shift,
key_togglehud: KeyCode::Tab, key_togglehud: KeyCode::Tab,
key_exit: KeyCode::Escape, key_menu: KeyCode::Escape,
key_restart: KeyCode::F7, key_restart: KeyCode::F7,
key_fullscreen: KeyCode::F11, key_fullscreen: KeyCode::F11,
key_help: KeyCode::F1, key_help: KeyCode::F1,
@ -287,6 +289,10 @@ impl Settings {
self.key_reply10, self.key_reply10,
]; ];
} }
pub fn in_control(&self) -> bool {
return self.alive && !self.menu_active;
}
} }
#[derive(Resource, Deserialize, Debug, Default)] #[derive(Resource, Deserialize, Debug, Default)]
@ -574,3 +580,10 @@ impl GameVars {
} }
} }
} }
#[derive(Resource, Default)]
pub struct CommandLineOptions {
pub window_mode_fullscreen: WindowMode,
pub window_mode_initial: WindowMode,
pub use_gl: bool,
}