From c51242d6c7b63c79be2f34c14b2754f0595226c8 Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 00:52:21 +0000 Subject: [PATCH 1/6] add Game Over screen on player death --- CHANGELOG.md | 7 ++- assets/locales/de/de-DE/game.ftl | 3 + assets/locales/en/en-US/game.ftl | 3 + assets/locales/ru/ru-RU/game.ftl | 3 + src/ships/ship.rs | 9 ++- src/systems/mod.rs | 10 +++- src/systems/states/mod.rs | 20 +++++-- src/ui/menus/game_over.rs | 99 ++++++++++++++++++++++++++++++++ src/ui/menus/mod.rs | 1 + 9 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 src/ui/menus/game_over.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 22321c6..e3bea65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Actual values for semi-major axes of planetary orbits (scaled down after calculations) - Pause menu with Continue and Exit Game buttons -- Added basic English (US) translations for pause menu -- Added basic Russian (RU) translations for pause menu -- Added basic German (DE) translations for pause menu +- Game Over screen on player death +- Added basic English (US) translations for pause menu and game over screen +- Added basic Russian (RU) translations for pause menu and game over screen +- Added basic German (DE) translations for pause menu and game over screen ### Changed diff --git a/assets/locales/de/de-DE/game.ftl b/assets/locales/de/de-DE/game.ftl index 57d9d53..26ac288 100644 --- a/assets/locales/de/de-DE/game.ftl +++ b/assets/locales/de/de-DE/game.ftl @@ -28,6 +28,9 @@ near = In der Nähe von { $celestial -> # Pause pause = Pause +# Game Over +game-over = Spiel Vorbei + # Datetime # TODO: Pass date and time as a workable datetime value for translation # time = { DATETIME($datetime) } diff --git a/assets/locales/en/en-US/game.ftl b/assets/locales/en/en-US/game.ftl index b40a7b3..2b1f07e 100644 --- a/assets/locales/en/en-US/game.ftl +++ b/assets/locales/en/en-US/game.ftl @@ -27,6 +27,9 @@ near = Near { $celestial -> # Pause pause = Pause +# Game Over +game-over = Game Over + # Datetime # TODO: Pass date and time as a workable datetime value for translation # time = { DATETIME($datetime) } diff --git a/assets/locales/ru/ru-RU/game.ftl b/assets/locales/ru/ru-RU/game.ftl index c05e046..d437eb9 100644 --- a/assets/locales/ru/ru-RU/game.ftl +++ b/assets/locales/ru/ru-RU/game.ftl @@ -27,6 +27,9 @@ near = Рядом с { $celestial -> # Pause pause = Пауза +# Game Over +game-over = Игра закончена + # Datetime # TODO: Pass date and time as a workable datetime value for translation # time = { DATETIME($datetime) } diff --git a/src/ships/ship.rs b/src/ships/ship.rs index f5f5481..0a1fc5e 100644 --- a/src/ships/ship.rs +++ b/src/ships/ship.rs @@ -1,7 +1,9 @@ use bevy::prelude::*; use bevy_rapier2d::prelude::*; -use crate::systems::events::BulletShipContactEvent; +use crate::systems::{events::BulletShipContactEvent, states::GameState}; + +use super::player::Player; /// Ship component #[derive(Component)] @@ -52,6 +54,8 @@ pub(crate) fn ship_damage( mut commands: Commands, mut bullet_ship_contact_events: EventReader, mut ship_health: Query<&mut Health, With>, + player: Query>, + mut next_state: ResMut>, ) { for event in bullet_ship_contact_events.read() { commands.entity(event.bullet).despawn(); @@ -61,6 +65,9 @@ pub(crate) fn ship_damage( if health.0 <= 0. { commands.entity(event.ship).despawn(); } + if player.is_empty() { + next_state.set(GameState::GameOver); + } } } } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index a61962e..f703f4d 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -15,7 +15,7 @@ use crate::{ temp, ui::{ camera, damage, hud, - menus::{credits, pause, start_menu}, + menus::{credits, game_over, pause, start_menu}, }, world::astronomy::{self, planetary_system, starfield}, }; @@ -97,6 +97,9 @@ impl Plugin for SystemsPlugin { (pause::pause_screen, pause::toggle_physics_off), ); + // - Game Over + app.add_systems(OnEnter(GameState::GameOver), game_over::game_over_screen); + // OnTransition app.add_systems( OnTransition { @@ -147,6 +150,11 @@ impl Plugin for SystemsPlugin { pause::pause_input_system.run_if(in_state(GameState::Paused)), ); + app.add_systems( + Update, + game_over::game_over_input_system.run_if(in_state(GameState::GameOver)), + ); + app.add_systems( Update, start_menu::menu_focus_system diff --git a/src/systems/states/mod.rs b/src/systems/states/mod.rs index 442ebea..dac0da5 100644 --- a/src/systems/states/mod.rs +++ b/src/systems/states/mod.rs @@ -14,10 +14,11 @@ pub enum GameState { GameCreate, Active, Paused, + GameOver, Reset, } impl GameState { - pub const IN_ANY_STATE: &[GameState; 8] = &[ + pub const IN_ANY_STATE: &[GameState; 9] = &[ GameState::Loading, GameState::LoadingTranslations, GameState::StartMenu, @@ -25,12 +26,21 @@ impl GameState { GameState::GameCreate, GameState::Active, GameState::Paused, + GameState::GameOver, GameState::Reset, ]; - pub const IN_MENU_STATE: &[GameState; 3] = - &[GameState::StartMenu, GameState::Credits, GameState::Paused]; - pub const IN_GAME_STATE: &[GameState; 3] = - &[GameState::GameCreate, GameState::Active, GameState::Paused]; + pub const IN_MENU_STATE: &[GameState; 4] = &[ + GameState::StartMenu, + GameState::Credits, + GameState::Paused, + GameState::GameOver, + ]; + pub const IN_GAME_STATE: &[GameState; 4] = &[ + GameState::GameCreate, + GameState::Active, + GameState::Paused, + GameState::GameOver, + ]; } pub fn is_in_menu_state(state: Res>) -> bool { diff --git a/src/ui/menus/game_over.rs b/src/ui/menus/game_over.rs new file mode 100644 index 0000000..7751843 --- /dev/null +++ b/src/ui/menus/game_over.rs @@ -0,0 +1,99 @@ +use bevy::prelude::*; +use bevy_ui_navigation::prelude::*; +use fluent_content::Content; +use leafwing_input_manager::prelude::ActionState; + +use crate::{ + core::{effects::blink::DrawBlinkTimer, resources::assets::UiAssets}, + i18n::I18n, + inputs::menu::MenuAction, + systems::states::{ForState, GameState}, +}; + +#[derive(Component)] +pub enum MenuButton { + ExitGame, +} + +pub(crate) fn game_over_screen(mut commands: Commands, ui: Res, i18n: Res) { + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + background_color: BackgroundColor(Color::rgba(0., 0., 0., 0.65)), + ..default() + }, + ForState { + states: vec![GameState::GameOver], + }, + )) + .with_children(|parent| { + parent.spawn(( + TextBundle { + style: Style { ..default() }, + text: Text::from_section( + i18n.content("game-over").unwrap().to_ascii_uppercase(), + TextStyle { + font: ui.font.clone(), + font_size: 50.0, + color: Color::rgb_u8(0x88, 0x00, 0x00), + }, + ), + ..default() + }, + DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)), + )); + + for (string, marker) in [("exit-game", MenuButton::ExitGame)] { + 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() + }, + ..default() + }, // DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)), + Focusable::default(), + marker, + )); + } + }); +} + +pub(crate) fn game_over_input_system( + mut next_state: ResMut>, + inputs: Res>, + mut requests: EventWriter, + mut buttons: Query<&mut MenuButton>, + mut events: EventReader, +) { + events.nav_iter().activated_in_query_foreach_mut( + &mut buttons, + |mut button| match &mut *button { + MenuButton::ExitGame => { + next_state.set(GameState::Reset); + } + }, + ); + + if inputs.just_pressed(MenuAction::Select) { + requests.send(NavRequest::Action); + } +} diff --git a/src/ui/menus/mod.rs b/src/ui/menus/mod.rs index b93831b..96554ef 100644 --- a/src/ui/menus/mod.rs +++ b/src/ui/menus/mod.rs @@ -1,3 +1,4 @@ pub mod credits; +pub mod game_over; pub mod pause; pub mod start_menu; From 6130fea8bae5368817fea3bc4c31b14b01cd24e5 Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 02:04:23 +0000 Subject: [PATCH 2/6] maintain active GameState in GameOver screen --- src/systems/mod.rs | 4 ++-- src/systems/states/mod.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/systems/mod.rs b/src/systems/mod.rs index f703f4d..2fc7628 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -21,7 +21,7 @@ use crate::{ }; use self::{ - states::{is_in_game_state, is_in_menu_state, GameState}, + states::{is_in_active_state, is_in_game_state, is_in_menu_state, GameState}, system_sets::{AttackSet, MovementSet}, }; @@ -207,7 +207,7 @@ impl Plugin for SystemsPlugin { ) .after(AttackSet), ) - .run_if(in_state(GameState::Active)), + .run_if(is_in_active_state), ); // PostUpdate diff --git a/src/systems/states/mod.rs b/src/systems/states/mod.rs index dac0da5..22624a4 100644 --- a/src/systems/states/mod.rs +++ b/src/systems/states/mod.rs @@ -41,6 +41,7 @@ impl GameState { GameState::Paused, GameState::GameOver, ]; + pub const IN_ACTIVE_STATE: &[GameState; 2] = &[GameState::Active, GameState::GameOver]; } pub fn is_in_menu_state(state: Res>) -> bool { @@ -51,6 +52,10 @@ pub fn is_in_game_state(state: Res>) -> bool { GameState::IN_GAME_STATE.contains(state.get()) } +pub fn is_in_active_state(state: Res>) -> bool { + GameState::IN_ACTIVE_STATE.contains(state.get()) +} + /// Component to tag an entity as only needed in some of the states #[derive(Component, Debug)] pub struct ForState { From ada2ebb63b670c341843b26cfb74daa47d37447e Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 02:09:59 +0000 Subject: [PATCH 3/6] go to GameOver immediately on player despawn --- src/ships/ship.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ships/ship.rs b/src/ships/ship.rs index 0a1fc5e..bc4c131 100644 --- a/src/ships/ship.rs +++ b/src/ships/ship.rs @@ -65,9 +65,10 @@ pub(crate) fn ship_damage( if health.0 <= 0. { commands.entity(event.ship).despawn(); } - if player.is_empty() { - next_state.set(GameState::GameOver); - } } } + + if player.is_empty() { + next_state.set(GameState::GameOver); + } } From 56e068139f9cd502f10df4e1666766f4a802de87 Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 02:54:27 +0000 Subject: [PATCH 4/6] reorder systems --- src/systems/mod.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 2fc7628..60b09ef 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -192,14 +192,9 @@ impl Plugin for SystemsPlugin { // The SystemSets aren't aware of one another, they need to be configured. // Reassess and order systems appropriately. enemy::enemy_targeting_system.before(ship::ship_damage), - (player::player_flight_system, enemy::enemy_flight_system).in_set(MovementSet), - (bullet::spawn_bullet, camera::follow_player).after(MovementSet), - ( - enemy::enemy_weapons_system, - player::player_weapons_system, - events::contact_system, - ) - .in_set(AttackSet), + enemy::enemy_flight_system.in_set(MovementSet), + bullet::spawn_bullet.after(MovementSet), + (enemy::enemy_weapons_system, events::contact_system).in_set(AttackSet), ( ship::ship_damage, damage::ui_spawn_damage, @@ -210,6 +205,16 @@ impl Plugin for SystemsPlugin { .run_if(is_in_active_state), ); + app.add_systems( + Update, + ( + player::player_flight_system.in_set(MovementSet), + camera::follow_player.after(MovementSet), + player::player_weapons_system.in_set(AttackSet), + ) + .run_if(in_state(GameState::Active)), + ); + // PostUpdate app.add_systems( PostUpdate, From eadf04a68daab055b6c3676b1c3b12771033d9b5 Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 03:00:04 +0000 Subject: [PATCH 5/6] add quit button to Game Over screen --- src/ui/menus/game_over.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ui/menus/game_over.rs b/src/ui/menus/game_over.rs index 7751843..9d97701 100644 --- a/src/ui/menus/game_over.rs +++ b/src/ui/menus/game_over.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{app::AppExit, prelude::*}; use bevy_ui_navigation::prelude::*; use fluent_content::Content; use leafwing_input_manager::prelude::ActionState; @@ -13,6 +13,7 @@ use crate::{ #[derive(Component)] pub enum MenuButton { ExitGame, + Quit, } pub(crate) fn game_over_screen(mut commands: Commands, ui: Res, i18n: Res) { @@ -51,7 +52,10 @@ pub(crate) fn game_over_screen(mut commands: Commands, ui: Res, i18n: DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)), )); - for (string, marker) in [("exit-game", MenuButton::ExitGame)] { + for (string, marker) in [ + ("exit-game", MenuButton::ExitGame), + ("quit", MenuButton::Quit), + ] { parent.spawn(( TextBundle { text: Text::from_section( @@ -83,6 +87,7 @@ pub(crate) fn game_over_input_system( mut requests: EventWriter, mut buttons: Query<&mut MenuButton>, mut events: EventReader, + mut exit: EventWriter, ) { events.nav_iter().activated_in_query_foreach_mut( &mut buttons, @@ -90,6 +95,9 @@ pub(crate) fn game_over_input_system( MenuButton::ExitGame => { next_state.set(GameState::Reset); } + MenuButton::Quit => { + exit.send(AppExit); + } }, ); From 04985cbb1881da46d930e89e815f63753c7d5b49 Mon Sep 17 00:00:00 2001 From: Thom Bruce Date: Wed, 15 Nov 2023 03:08:46 +0000 Subject: [PATCH 6/6] move game_over handling to own system run only in Active GameState --- src/ships/ship.rs | 7 +++++-- src/systems/mod.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ships/ship.rs b/src/ships/ship.rs index bc4c131..e66181f 100644 --- a/src/ships/ship.rs +++ b/src/ships/ship.rs @@ -54,8 +54,6 @@ pub(crate) fn ship_damage( mut commands: Commands, mut bullet_ship_contact_events: EventReader, mut ship_health: Query<&mut Health, With>, - player: Query>, - mut next_state: ResMut>, ) { for event in bullet_ship_contact_events.read() { commands.entity(event.bullet).despawn(); @@ -67,7 +65,12 @@ pub(crate) fn ship_damage( } } } +} +pub(crate) fn game_over( + player: Query>, + mut next_state: ResMut>, +) { if player.is_empty() { next_state.set(GameState::GameOver); } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 60b09ef..4f44a7c 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -222,6 +222,6 @@ impl Plugin for SystemsPlugin { ); // Last - // app.add_systems(Last, _); + app.add_systems(Last, ship::game_over.run_if(in_state(GameState::Active))); } }