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..e66181f 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)] @@ -64,3 +66,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 a61962e..4f44a7c 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -15,13 +15,13 @@ 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}, }; 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}, }; @@ -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 @@ -184,20 +192,25 @@ 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, damage::ui_text_fade_out, ) .after(AttackSet), + ) + .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)), ); @@ -209,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))); } } diff --git a/src/systems/states/mod.rs b/src/systems/states/mod.rs index 442ebea..22624a4 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,22 @@ 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 const IN_ACTIVE_STATE: &[GameState; 2] = &[GameState::Active, GameState::GameOver]; } pub fn is_in_menu_state(state: Res>) -> bool { @@ -41,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 { diff --git a/src/ui/menus/game_over.rs b/src/ui/menus/game_over.rs new file mode 100644 index 0000000..9d97701 --- /dev/null +++ b/src/ui/menus/game_over.rs @@ -0,0 +1,107 @@ +use bevy::{app::AppExit, 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, + Quit, +} + +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), + ("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() + }, + ..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, + mut exit: EventWriter, +) { + events.nav_iter().activated_in_query_foreach_mut( + &mut buttons, + |mut button| match &mut *button { + MenuButton::ExitGame => { + next_state.set(GameState::Reset); + } + MenuButton::Quit => { + exit.send(AppExit); + } + }, + ); + + 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;