Skip to content

Commit 1b2aee6

Browse files
committed
Updating GlobalVolume now updates the volume of any playing (Spatial)AudioSinks
Fixes #18952
1 parent 86cc02d commit 1b2aee6

File tree

4 files changed

+73
-9
lines changed

4 files changed

+73
-9
lines changed

crates/bevy_audio/src/audio_output.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ impl<'w, 's> EarPositions<'w, 's> {
8686
}
8787
}
8888

89+
/// Iterates "active" audio and updates their volume based on their [`PlaybackSettings`] and the updated [`GlobalVolume`]
90+
///
91+
/// "Active" audio is any entity with an [`AudioSink`]/[`SpatialAudioSink`]
92+
///
93+
/// This system triggers only on [`GlobalVolume`] updates
94+
pub(crate) fn update_playing_audio_volume(
95+
mut query_playing_audio_sinks: Query<(&mut AudioSink, &PlaybackSettings)>,
96+
mut query_playing_spatial_audio_sinks: Query<(&mut SpatialAudioSink, &PlaybackSettings)>,
97+
global_volume: Res<GlobalVolume>
98+
) {
99+
for (mut sink, settings) in query_playing_audio_sinks.iter_mut() {
100+
sink.set_volume(settings.volume * global_volume.volume);
101+
}
102+
103+
for (mut sink, settings) in query_playing_spatial_audio_sinks.iter_mut() {
104+
sink.set_volume(settings.volume * global_volume.volume);
105+
}
106+
}
107+
89108
/// Plays "queued" audio through the [`AudioOutput`] resource.
90109
///
91110
/// "Queued" audio is any audio entity (with an [`AudioPlayer`] component) that does not have an

crates/bevy_audio/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ impl AddAudioSource for App {
118118
{
119119
self.init_asset::<T>().add_systems(
120120
PostUpdate,
121-
(play_queued_audio_system::<T>, cleanup_finished_audio::<T>)
121+
(
122+
update_playing_audio_volume
123+
.run_if(resource_changed::<GlobalVolume>),
124+
play_queued_audio_system::<T>,
125+
cleanup_finished_audio::<T>
126+
)
122127
.in_set(AudioPlaybackSystems),
123128
);
124129
self

crates/bevy_audio/src/volume.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ use bevy_math::ops;
33
use bevy_reflect::prelude::*;
44

55
/// Use this [`Resource`] to control the global volume of all audio.
6-
///
7-
/// Note: Changing [`GlobalVolume`] does not affect already playing audio.
86
#[derive(Resource, Debug, Default, Clone, Copy, Reflect)]
97
#[reflect(Resource, Debug, Default, Clone)]
108
pub struct GlobalVolume {

examples/games/game_menu.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use bevy::prelude::*;
66

77
const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
8+
const VOLUME_MAX: u32 = 9;
89

910
// Enum that will be used as a global state for the game
1011
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
@@ -27,6 +28,9 @@ enum DisplayQuality {
2728
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
2829
struct Volume(u32);
2930

31+
#[derive(Resource, Deref)]
32+
struct MenuMusic(Handle<AudioSource>);
33+
3034
fn main() {
3135
App::new()
3236
.add_plugins(DefaultPlugins)
@@ -36,13 +40,20 @@ fn main() {
3640
// Declare the game state, whose starting value is determined by the `Default` trait
3741
.init_state::<GameState>()
3842
.add_systems(Startup, setup)
43+
.add_systems(Update, translate_discreet_volume_to_global_volume.run_if(resource_changed::<Volume>))
3944
// Adds the plugins for each state
4045
.add_plugins((splash::splash_plugin, menu::menu_plugin, game::game_plugin))
4146
.run();
4247
}
4348

44-
fn setup(mut commands: Commands) {
49+
fn setup(
50+
mut commands: Commands,
51+
asset_server: Res<AssetServer>,
52+
) {
4553
commands.spawn(Camera2d);
54+
// Sound
55+
let music = asset_server.load("sounds/Windless Slopes.ogg");
56+
commands.insert_resource(MenuMusic(music));
4657
}
4758

4859
mod splash {
@@ -113,14 +124,16 @@ mod game {
113124
prelude::*,
114125
};
115126

127+
use crate::{play_music, stop_music, MenuMusic};
128+
116129
use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR};
117130

118131
// This plugin will contain the game. In this case, it's just be a screen that will
119132
// display the current settings for 5 seconds before returning to the menu
120133
pub fn game_plugin(app: &mut App) {
121-
app.add_systems(OnEnter(GameState::Game), game_setup)
134+
app.add_systems(OnEnter(GameState::Game), (game_setup, play_music))
122135
.add_systems(Update, game.run_if(in_state(GameState::Game)))
123-
.add_systems(OnExit(GameState::Game), despawn_screen::<OnGameScreen>);
136+
.add_systems(OnExit(GameState::Game), (despawn_screen::<OnGameScreen>, stop_music));
124137
}
125138

126139
// Tag component used to tag entities added on the game screen
@@ -229,6 +242,8 @@ mod menu {
229242
prelude::*,
230243
};
231244

245+
use crate::{play_music, stop_music, VOLUME_MAX};
246+
232247
use super::{despawn_screen, DisplayQuality, GameState, Volume, TEXT_COLOR};
233248

234249
// This plugin manages the menu, with 5 different screens:
@@ -265,14 +280,14 @@ mod menu {
265280
despawn_screen::<OnDisplaySettingsMenuScreen>,
266281
)
267282
// Systems to handle the sound settings screen
268-
.add_systems(OnEnter(MenuState::SettingsSound), sound_settings_menu_setup)
283+
.add_systems(OnEnter(MenuState::SettingsSound), (sound_settings_menu_setup, play_music))
269284
.add_systems(
270285
Update,
271286
setting_button::<Volume>.run_if(in_state(MenuState::SettingsSound)),
272287
)
273288
.add_systems(
274289
OnExit(MenuState::SettingsSound),
275-
despawn_screen::<OnSoundSettingsMenuScreen>,
290+
(despawn_screen::<OnSoundSettingsMenuScreen>, stop_music),
276291
)
277292
// Common systems to all screens that handles buttons behavior
278293
.add_systems(
@@ -663,7 +678,7 @@ mod menu {
663678
Children::spawn((
664679
Spawn((Text::new("Volume"), button_text_style.clone())),
665680
SpawnWith(move |parent: &mut ChildSpawner| {
666-
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
681+
for volume_setting in 0..VOLUME_MAX {
667682
let mut entity = parent.spawn((
668683
Button,
669684
Node {
@@ -735,3 +750,30 @@ fn despawn_screen<T: Component>(to_despawn: Query<Entity, With<T>>, mut commands
735750
commands.entity(entity).despawn();
736751
}
737752
}
753+
754+
fn stop_music(
755+
playing_sounds_query: Query<AnyOf<(&AudioSink, &SpatialAudioSink)>>
756+
){
757+
for (audio_sink, spatial_audio_sink) in &playing_sounds_query {
758+
if let Some(audio_sink) = audio_sink {
759+
audio_sink.stop();
760+
}
761+
if let Some(spatial_audio_sink) = spatial_audio_sink {
762+
spatial_audio_sink.stop();
763+
}
764+
}
765+
}
766+
767+
fn play_music(
768+
mut commands: Commands,
769+
music: Res<MenuMusic>,
770+
){
771+
commands.spawn((AudioPlayer(music.clone()), PlaybackSettings::DESPAWN));
772+
}
773+
774+
fn translate_discreet_volume_to_global_volume(
775+
volume: Res<Volume>,
776+
mut global_volume: ResMut<GlobalVolume>,
777+
) {
778+
global_volume.volume = bevy::audio::Volume::Linear(volume.0 as f32 / VOLUME_MAX as f32);
779+
}

0 commit comments

Comments
 (0)