diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 1869fb47555db..9cfcad0bb1271 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -86,6 +86,37 @@ impl<'w, 's> EarPositions<'w, 's> { } } +/// Iterates "active" audio and updates their volume based on their [`PlaybackSettings`] and the updated [`GlobalVolume`] +/// +/// "Active" audio is any entity with an [`AudioSink`]/[`SpatialAudioSink`] +/// +/// This system triggers only on [`GlobalVolume`] updates +pub(crate) fn update_playing_audio_volume( + mut query_playing_audio_sinks: Query< + (&mut AudioSink, &PlaybackSettings), + ( + Without, + Without, + ), + >, + mut query_playing_spatial_audio_sinks: Query< + (&mut SpatialAudioSink, &PlaybackSettings), + ( + Without, + Without, + ), + >, + global_volume: Res, +) { + for (mut sink, settings) in query_playing_audio_sinks.iter_mut() { + sink.set_volume(settings.volume * global_volume.volume); + } + + for (mut sink, settings) in query_playing_spatial_audio_sinks.iter_mut() { + sink.set_volume(settings.volume * global_volume.volume); + } +} + /// Plays "queued" audio through the [`AudioOutput`] resource. /// /// "Queued" audio is any audio entity (with an [`AudioPlayer`] component) that does not have an diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index becbf5d1da31a..aacf60669c97a 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -96,7 +96,14 @@ impl Plugin for AudioPlugin { ) .add_systems( PostUpdate, - (update_emitter_positions, update_listener_positions).in_set(AudioPlaybackSystems), + ( + update_emitter_positions, + update_listener_positions, + update_playing_audio_volume + .run_if(resource_changed::) + .ambiguous_with_all(), + ) + .in_set(AudioPlaybackSystems), ) .init_resource::(); diff --git a/crates/bevy_audio/src/volume.rs b/crates/bevy_audio/src/volume.rs index b1378ae485747..b00f000d4b031 100644 --- a/crates/bevy_audio/src/volume.rs +++ b/crates/bevy_audio/src/volume.rs @@ -3,8 +3,6 @@ use bevy_math::ops; use bevy_reflect::prelude::*; /// Use this [`Resource`] to control the global volume of all audio. -/// -/// Note: Changing [`GlobalVolume`] does not affect already playing audio. #[derive(Resource, Debug, Default, Clone, Copy, Reflect)] #[reflect(Resource, Debug, Default, Clone)] pub struct GlobalVolume { diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 4794d0e44fc28..871be65d8f898 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -5,6 +5,7 @@ use bevy::prelude::*; const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9); +const VOLUME_MAX: u32 = 9; // Enum that will be used as a global state for the game #[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)] @@ -27,6 +28,9 @@ enum DisplayQuality { #[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] struct Volume(u32); +#[derive(Resource, Deref)] +struct MenuMusic(Handle); + fn main() { App::new() .add_plugins(DefaultPlugins) @@ -36,13 +40,20 @@ fn main() { // Declare the game state, whose starting value is determined by the `Default` trait .init_state::() .add_systems(Startup, setup) + .add_systems( + Update, + translate_discreet_volume_to_global_volume.run_if(resource_changed::), + ) // Adds the plugins for each state .add_plugins((splash::splash_plugin, menu::menu_plugin, game::game_plugin)) .run(); } -fn setup(mut commands: Commands) { +fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); + // Sound + let music = asset_server.load("sounds/Windless Slopes.ogg"); + commands.insert_resource(MenuMusic(music)); } mod splash { @@ -113,14 +124,19 @@ mod game { prelude::*, }; + use crate::{play_music, stop_music}; + use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR}; // This plugin will contain the game. In this case, it's just be a screen that will // display the current settings for 5 seconds before returning to the menu pub fn game_plugin(app: &mut App) { - app.add_systems(OnEnter(GameState::Game), game_setup) + app.add_systems(OnEnter(GameState::Game), (game_setup, play_music)) .add_systems(Update, game.run_if(in_state(GameState::Game))) - .add_systems(OnExit(GameState::Game), despawn_screen::); + .add_systems( + OnExit(GameState::Game), + (despawn_screen::, stop_music), + ); } // Tag component used to tag entities added on the game screen @@ -229,6 +245,8 @@ mod menu { prelude::*, }; + use crate::{play_music, stop_music, VOLUME_MAX}; + use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR}; // This plugin manages the menu, with 5 different screens: @@ -265,14 +283,17 @@ mod menu { despawn_screen::, ) // Systems to handle the sound settings screen - .add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup) + .add_systems( + OnEnter(MenuState::SettingsSound), + (sound_settings_menu_setup, play_music), + ) .add_systems( Update, setting_button::.run_if(in_state(MenuState::SettingsSound)), ) .add_systems( OnExit(MenuState::SettingsSound), - despawn_screen::, + (despawn_screen::, stop_music), ) // Common systems to all screens that handles buttons behavior .add_systems( @@ -663,7 +684,7 @@ mod menu { Children::spawn(( Spawn((Text::new("Volume"), button_text_style.clone())), SpawnWith(move |parent: &mut ChildSpawner| { - for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] { + for volume_setting in 0..VOLUME_MAX { let mut entity = parent.spawn(( Button, Node { @@ -735,3 +756,25 @@ fn despawn_screen(to_despawn: Query>, mut commands commands.entity(entity).despawn(); } } + +fn stop_music(playing_sounds_query: Query>) { + for (audio_sink, spatial_audio_sink) in &playing_sounds_query { + if let Some(audio_sink) = audio_sink { + audio_sink.stop(); + } + if let Some(spatial_audio_sink) = spatial_audio_sink { + spatial_audio_sink.stop(); + } + } +} + +fn play_music(mut commands: Commands, music: Res) { + commands.spawn((AudioPlayer(music.clone()), PlaybackSettings::DESPAWN)); +} + +fn translate_discreet_volume_to_global_volume( + volume: Res, + mut global_volume: ResMut, +) { + global_volume.volume = bevy::audio::Volume::Linear(volume.0 as f32 / VOLUME_MAX as f32); +}