diff --git a/assets/game_audio_assets.assets.ron b/assets/game_audio_assets.assets.ron index 9aaf1651..2b184ef7 100644 --- a/assets/game_audio_assets.assets.ron +++ b/assets/game_audio_assets.assets.ron @@ -62,4 +62,31 @@ "sounds.objective_completed": File ( path: "sounds/objective_completed.wav", ), + "sounds.button_select_1": File ( + path: "sounds/button_select_1.wav", + ), + "sounds.button_select_2": File ( + path: "sounds/button_select_2.wav", + ), + "sounds.button_select_3": File ( + path: "sounds/button_select_3.wav", + ), + "sounds.button_select_4": File ( + path: "sounds/button_select_4.wav", + ), + "sounds.button_select_5": File ( + path: "sounds/button_select_5.wav", + ), + "sounds.button_release_1": File ( + path: "sounds/button_release_1.wav", + ), + "sounds.button_release_2": File ( + path: "sounds/button_release_2.wav", + ), + "sounds.button_release_3": File ( + path: "sounds/button_release_3.wav", + ), + "sounds.button_confirm": File ( + path: "sounds/button_confirm.wav", + ), }) \ No newline at end of file diff --git a/assets/ui_assets.assets.ron b/assets/ui_assets.assets.ron new file mode 100644 index 00000000..228aba65 --- /dev/null +++ b/assets/ui_assets.assets.ron @@ -0,0 +1,16 @@ +({ + "thetawave_logo.layout": TextureAtlasLayout ( + tile_size_x: 1920., + tile_size_y: 1080., + columns: 4, + rows: 12, + ), + "thetawave_logo.image": File( path: "texture/thetawave_logo_spritesheet.png"), + "thetawave_menu_button.layout": TextureAtlasLayout ( + tile_size_x: 160., + tile_size_y: 34., + columns: 1, + rows: 2, + ), + "thetawave_menu_button.image": File( path: "texture/menu_button_spritesheet.png"), +}) \ No newline at end of file diff --git a/crates/thetawave_interface/src/audio.rs b/crates/thetawave_interface/src/audio.rs index 515d1936..6722665e 100644 --- a/crates/thetawave_interface/src/audio.rs +++ b/crates/thetawave_interface/src/audio.rs @@ -31,6 +31,9 @@ pub enum SoundEffectType { BulletBounce, MegablastAbility, ObjectiveCompleted, + ButtonSelect, + ButtonRelease, + ButtonConfirm, } /// Subtype of sound effect for collisions diff --git a/crates/thetawave_interface/src/input.rs b/crates/thetawave_interface/src/input.rs index cdb96924..fb157c91 100644 --- a/crates/thetawave_interface/src/input.rs +++ b/crates/thetawave_interface/src/input.rs @@ -21,6 +21,8 @@ pub enum MenuAction { ExitPauseMenu, PauseGame, ToggleTutorial, + NavigateUp, + NavigateDown, } /// Player actions during the main game/while fighting mobs. Many of these can be simultaneously diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 69a860e0..6d51f5bb 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -24,6 +24,10 @@ impl Plugin for AnimationPlugin { animate_sprite_system .run_if(in_state(states::AppStates::Game)) .run_if(in_state(states::GameStates::Playing)), + ) + .add_systems( + Update, + animate_sprite_system.run_if(in_state(states::AppStates::MainMenu)), ); app.add_event::(); diff --git a/src/assets/audio.rs b/src/assets/audio.rs index e69cb82b..dac0b160 100644 --- a/src/assets/audio.rs +++ b/src/assets/audio.rs @@ -1,6 +1,7 @@ use bevy::prelude::*; use bevy_asset_loader::prelude::*; use bevy_kira_audio::AudioSource; +use rand::Rng; use thetawave_interface::audio::{BGMusicType, CollisionSoundType, SoundEffectType}; #[derive(AssetCollection, Resource)] @@ -47,6 +48,24 @@ pub struct GameAudioAssets { pub megablast_ability: Handle, #[asset(key = "sounds.objective_completed")] pub objective_completed: Handle, + #[asset(key = "sounds.button_select_1")] + pub button_select_1: Handle, + #[asset(key = "sounds.button_select_2")] + pub button_select_2: Handle, + #[asset(key = "sounds.button_select_3")] + pub button_select_3: Handle, + #[asset(key = "sounds.button_select_4")] + pub button_select_4: Handle, + #[asset(key = "sounds.button_select_5")] + pub button_select_5: Handle, + #[asset(key = "sounds.button_release_1")] + pub button_release_1: Handle, + #[asset(key = "sounds.button_release_2")] + pub button_release_2: Handle, + #[asset(key = "sounds.button_release_3")] + pub button_release_3: Handle, + #[asset(key = "sounds.button_confirm")] + pub button_confirm: Handle, } impl GameAudioAssets { @@ -80,6 +99,25 @@ impl GameAudioAssets { SoundEffectType::BulletBounce => self.bullet_bounce.clone(), SoundEffectType::MegablastAbility => self.megablast_ability.clone(), SoundEffectType::ObjectiveCompleted => self.objective_completed.clone(), + SoundEffectType::ButtonRelease => { + let idx: u8 = rand::thread_rng().gen_range(1..=3); + match idx { + 1 => self.button_release_1.clone(), + 2 => self.button_release_2.clone(), + _ => self.button_release_3.clone(), + } + } + SoundEffectType::ButtonSelect => { + let idx: u8 = rand::thread_rng().gen_range(1..=5); + match idx { + 1 => self.button_select_1.clone(), + 2 => self.button_select_2.clone(), + 3 => self.button_select_3.clone(), + 4 => self.button_select_4.clone(), + _ => self.button_select_5.clone(), + } + } + SoundEffectType::ButtonConfirm => self.button_confirm.clone(), } } } diff --git a/src/assets/mod.rs b/src/assets/mod.rs index 066c5811..e1b03841 100644 --- a/src/assets/mod.rs +++ b/src/assets/mod.rs @@ -5,5 +5,8 @@ mod item; mod mob; mod player; mod projectile; +mod ui; -pub use self::{audio::*, consumable::*, effect::*, item::*, mob::*, player::*, projectile::*}; +pub use self::{ + audio::*, consumable::*, effect::*, item::*, mob::*, player::*, projectile::*, ui::*, +}; diff --git a/src/assets/ui.rs b/src/assets/ui.rs new file mode 100644 index 00000000..4699d445 --- /dev/null +++ b/src/assets/ui.rs @@ -0,0 +1,17 @@ +use bevy::{ + prelude::{Handle, Resource, TextureAtlasLayout}, + render::texture::Image, +}; +use bevy_asset_loader::prelude::AssetCollection; + +#[derive(AssetCollection, Resource)] +pub struct UiAssets { + #[asset(key = "thetawave_logo.layout")] + pub thetawave_logo_layout: Handle, + #[asset(key = "thetawave_logo.image")] + pub thetawave_logo_image: Handle, + #[asset(key = "thetawave_menu_button.layout")] + pub thetawave_menu_button_layout: Handle, + #[asset(key = "thetawave_menu_button.image")] + pub thetawave_menu_button_image: Handle, +} diff --git a/src/audio/mod.rs b/src/audio/mod.rs index dbacac29..bd63161a 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -44,9 +44,9 @@ pub fn set_audio_volume_system( menu_audio_channel: Res>, effects_audio_channel: Res>, ) { - background_audio_channel.set_volume(0.70); + background_audio_channel.set_volume(0.20); menu_audio_channel.set_volume(0.05); - effects_audio_channel.set_volume(0.60); + effects_audio_channel.set_volume(0.80); } fn play_sound_effect_system( diff --git a/src/background/mod.rs b/src/background/mod.rs index 5c71a092..73ab95f5 100644 --- a/src/background/mod.rs +++ b/src/background/mod.rs @@ -38,7 +38,7 @@ use std::ops::Range; use thetawave_interface::{ game::options::GameOptions, run::{RunDefeatType, RunEndEvent, RunOutcomeType}, - states::{self, GameCleanup}, + states::{self, GameCleanup, MainMenuCleanup}, }; use thiserror::Error; @@ -59,6 +59,16 @@ impl Plugin for BackgroundPlugin { create_background_system.in_set(GameEnterSet::BuildLevel), ); + app.add_systems( + OnEnter(states::AppStates::MainMenu), + create_background_system, + ); + + app.add_systems( + Update, + rotate_planet_system.run_if(in_state(states::AppStates::MainMenu)), + ); + app.add_systems( Update, (rotate_planet_system, on_defeat_star_explode_system) @@ -197,7 +207,8 @@ pub fn create_background_system( let mut rng = rand::thread_rng(); // Choose random positions for the bodies - let background_transform = Transform::from_translation(backgrounds_res.background_transation); + let background_transform = Transform::from_translation(backgrounds_res.background_transation) + .with_scale(Vec3::new(1.5, 1.5, 1.0)); let star_transform = Transform::from_xyz( rng.gen_range(backgrounds_res.star_position_x_range.clone()), 0.0, @@ -213,6 +224,7 @@ pub fn create_background_system( rotation_speed: rng.gen_range(backgrounds_res.rotation_speed_range.clone()), }) .insert(GameCleanup) + .insert(MainMenuCleanup) .insert(Visibility::default()) .insert(InheritedVisibility::default()) .insert(Name::new("Planet")); @@ -266,6 +278,7 @@ pub fn create_background_system( let mut background_commands = commands.spawn_empty(); background_commands .insert(GameCleanup) + .insert(MainMenuCleanup) .insert(Visibility::default()) .insert(InheritedVisibility::default()) .insert(Name::new("Space Background")) @@ -338,6 +351,7 @@ pub fn create_background_system( ..default() },)) .insert(GameCleanup) + .insert(MainMenuCleanup) .insert(Visibility::default()) .insert(InheritedVisibility::default()) .insert(Name::new("Star")) diff --git a/src/main.rs b/src/main.rs index 453dba43..fe068619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -215,7 +215,9 @@ impl PluginGroup for ThetawaveGamePlugins { #[cfg(test)] mod test { + use crate::animation::AnimationPlugin; use crate::audio::ThetawaveAudioPlugin; + use crate::background::BackgroundPlugin; use crate::{build_app, options, ui, ThetawaveGamePlugins}; use bevy::app::{App, PluginGroup}; use bevy::asset::AssetPlugin; @@ -268,7 +270,11 @@ mod test { // Ideally audio is mostly handled via `thetawave_interface::audio` and events, so that // we really only skip testing 1 match statement and external audio deps. .disable::() - .disable::(); + .disable::() + // The background plugin & animation plugins require the render pipeline, which I dont + // not want in CI. + .disable::() + .disable::(); let mut app = build_app(base_plugins, game_plugins); app.add_event::() diff --git a/src/options/display.rs b/src/options/display.rs index e7526fe1..e93a1df7 100644 --- a/src/options/display.rs +++ b/src/options/display.rs @@ -23,7 +23,7 @@ impl From for Window { Window { title: "Thetawave".to_string(), resolution: (display_config.width, display_config.height).into(), - resizable: false, + resizable: true, mode: if display_config.fullscreen { WindowMode::SizedFullscreen } else { diff --git a/src/options/input.ron b/src/options/input.ron index bda37cec..a8326e09 100644 --- a/src/options/input.ron +++ b/src/options/input.ron @@ -10,15 +10,22 @@ (Reset, KeyR), (ExitPauseMenu, Escape), (PauseGame, Escape), + (NavigateUp, KeyW), + (NavigateDown, KeyS), + (NavigateUp, ArrowUp), + (NavigateDown, ArrowDown), ], menu_gamepad: [ (ChangeCharacterGamepad, DPadLeft), (ChangeCharacterGamepad, DPadRight), (ToggleTutorial, DPadUp), (ToggleTutorial, DPadDown), + (NavigateUp, DPadUp), + (NavigateDown, DPadDown), (JoinGamepad, South), (Back, East), (Confirm, Start), + (Confirm, South), (Reset, East), (ExitPauseMenu, Start), (PauseGame, Start), diff --git a/src/states/game.rs b/src/states/game.rs index af6f1561..d3cbfc53 100644 --- a/src/states/game.rs +++ b/src/states/game.rs @@ -33,27 +33,6 @@ pub fn start_game_system( } } -// Start the game by entering the Game state -pub fn start_instructions_system( - menu_input_query: Query<&ActionState, With>, - mut next_app_state: ResMut>, - mut sound_effect_pub: EventWriter, -) { - // read menu input action - if let Ok(action_state) = menu_input_query.get_single() { - // if input read enter the game state - if action_state.just_released(&MenuAction::Confirm) { - // set the state to game - next_app_state.set(AppStates::Instructions); - - // play sound effect - sound_effect_pub.send(PlaySoundEffectEvent { - sound_effect_type: SoundEffectType::MenuInputSuccess, - }); - } - } -} - pub fn start_character_selection_system( menu_input_query: Query<&ActionState, With>, mut next_app_state: ResMut>, diff --git a/src/states/mod.rs b/src/states/mod.rs index 985eda0c..267c72bc 100644 --- a/src/states/mod.rs +++ b/src/states/mod.rs @@ -28,6 +28,7 @@ use crate::assets::ItemAssets; use crate::assets::MobAssets; use crate::assets::PlayerAssets; use crate::assets::ProjectileAssets; +use crate::assets::UiAssets; use crate::GameEnterSet; use crate::GameUpdateSet; @@ -60,13 +61,15 @@ impl Plugin for StatesPlugin { .with_dynamic_assets_file::( "game_audio_assets.assets.ron", ) + .with_dynamic_assets_file::("ui_assets.assets.ron") .load_collection::() .load_collection::() .load_collection::() .load_collection::() .load_collection::() .load_collection::() - .load_collection::(), + .load_collection::() + .load_collection::(), ); app.edit_schedule(OnEnter(AppStates::Game), |schedule| { @@ -109,11 +112,6 @@ impl Plugin for StatesPlugin { .run_if(in_state(GameStates::Playing)), ); - app.add_systems( - Update, - start_instructions_system.run_if(in_state(AppStates::MainMenu)), - ); - app.add_systems( Update, start_character_selection_system.run_if(in_state(AppStates::Instructions)), diff --git a/src/ui/main_menu.rs b/src/ui/main_menu.rs deleted file mode 100644 index dbdb2f81..00000000 --- a/src/ui/main_menu.rs +++ /dev/null @@ -1,189 +0,0 @@ -use crate::options::PlayingOnArcadeResource; -use bevy::{ - asset::AssetServer, - ecs::{ - component::Component, - event::EventWriter, - system::{Commands, Query, Res}, - }, - hierarchy::BuildChildren, - render::color::Color, - text::{JustifyText, Text, TextStyle}, - time::{Time, Timer, TimerMode}, - transform::components::Transform, - ui::{ - node_bundles::{ImageBundle, NodeBundle, TextBundle}, - BackgroundColor, FlexDirection, JustifyContent, Style, UiRect, Val, - }, - utils::default, -}; -use std::time::Duration; -use thetawave_interface::audio::{BGMusicType, ChangeBackgroundMusicEvent}; -use thetawave_interface::game::historical_metrics::{ - MobKillsByPlayerForCompletedGames, UserStatsByPlayerForCompletedGamesCache, DEFAULT_USER_ID, -}; -use thetawave_interface::states::MainMenuCleanup; - -#[derive(Component)] -pub struct MainMenuUI; - -#[derive(Component)] -pub struct BouncingPromptComponent { - pub flash_timer: Timer, - pub is_active: bool, -} - -pub fn setup_main_menu_system( - mut commands: Commands, - asset_server: Res, - mut change_bg_music_event_writer: EventWriter, - historical_games_shot_counts: Res, - historical_games_enemy_mob_kill_counts: Res, - playing_on_arcade: Res, -) { - let maybe_user_stats = (**historical_games_shot_counts).get(&DEFAULT_USER_ID); - - let (accuracy_rate, total_shots_fired): (f32, usize) = match maybe_user_stats { - None => (100.0, 0), - Some(current_game_shot_counts) => { - let accuracy = (current_game_shot_counts.total_shots_hit as f32 - / current_game_shot_counts.total_shots_fired as f32) - * 100.0; - (accuracy, current_game_shot_counts.total_shots_fired) - } - }; - - change_bg_music_event_writer.send(ChangeBackgroundMusicEvent { - bg_music_type: Some(BGMusicType::Main), - loop_from: Some(0.0), - fade_in: Some(Duration::from_secs(2)), - fade_out: Some(Duration::from_secs(2)), - }); - - commands - .spawn(NodeBundle { - style: Style { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - ..Default::default() - }, - background_color: Color::rgba(0.0, 0.0, 0.0, 0.0).into(), - ..Default::default() - }) - .insert(MainMenuCleanup) - .insert(MainMenuUI) - .with_children(|parent| { - parent - .spawn(ImageBundle { - image: asset_server - .load("texture/main_menu_background_54.png") - .into(), - style: Style { - width: Val::Percent(100.0), - flex_direction: FlexDirection::Column, - height: Val::Percent(100.0), - justify_content: JustifyContent::FlexEnd, - ..Default::default() - }, - ..default() - }) - .with_children(|parent| { - let font = asset_server.load("fonts/wibletown-regular.otf"); - - parent - .spawn(NodeBundle { - style: Style { - width: Val::Auto, - height: Val::Auto, - margin: UiRect { - bottom: Val::Auto, - top: Val::Percent(40.0), - right: Val::Auto, - left: Val::Auto, - }, - padding: UiRect::all(Val::Px(10.0)), - - justify_content: JustifyContent::Center, - ..Default::default() - }, - background_color: BackgroundColor::from(Color::BLACK.with_a(0.9)), - ..default() - }) - .with_children(|parent| { - parent.spawn(TextBundle { - style: Style { - width: Val::Auto, - height: Val::Auto, - margin: UiRect::all(Val::Auto), - ..Default::default() - }, - - text: Text::from_section( - format!( - "Projectiles fired: {}\nAccuracy: {:.2}%\n\nEnemies destroyed:\n{}", - total_shots_fired, - accuracy_rate, - super::pprint_mob_kills_from_data(&historical_games_enemy_mob_kill_counts), - ), - TextStyle { - font, - font_size: 32.0, - color: Color::WHITE, - }, - ) - .with_justify(JustifyText::Center), - - ..default() - }); - }); - - parent - .spawn(ImageBundle { - image: asset_server - .load(if **playing_on_arcade { - "texture/start_game_prompt_arcade.png" - } else { - "texture/start_game_prompt_keyboard.png" - }) - .into(), - style: Style { - width: Val::Px(400.0), - height: Val::Px(100.0), - margin: UiRect { - bottom: Val::Percent(10.0), - top: Val::Auto, - right: Val::Auto, - left: Val::Auto, - }, - justify_content: JustifyContent::Center, - ..Default::default() - }, - ..Default::default() - }) - .insert(BouncingPromptComponent { - flash_timer: Timer::from_seconds(2.0, TimerMode::Repeating), - is_active: true, - }); - }); - }); -} - -pub fn bouncing_prompt_system( - mut flashing_prompt_query: Query<(&mut Transform, &mut BouncingPromptComponent)>, - time: Res