Skip to content

Updating GlobalVolume now updates the volume of any playing (Spatial)AudioSinks #19168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlaybackDespawnMarker>,
Without<PlaybackRemoveMarker>,
),
>,
mut query_playing_spatial_audio_sinks: Query<
(&mut SpatialAudioSink, &PlaybackSettings),
(
Without<PlaybackDespawnMarker>,
Without<PlaybackRemoveMarker>,
),
>,
global_volume: Res<GlobalVolume>,
) {
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
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<GlobalVolume>)
.ambiguous_with_all(),
)
.in_set(AudioPlaybackSystems),
)
.init_resource::<AudioOutput>();

Expand Down
2 changes: 0 additions & 2 deletions crates/bevy_audio/src/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
55 changes: 49 additions & 6 deletions examples/games/game_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -27,6 +28,9 @@ enum DisplayQuality {
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
struct Volume(u32);

#[derive(Resource, Deref)]
struct MenuMusic(Handle<AudioSource>);

fn main() {
App::new()
.add_plugins(DefaultPlugins)
Expand All @@ -36,13 +40,20 @@ fn main() {
// Declare the game state, whose starting value is determined by the `Default` trait
.init_state::<GameState>()
.add_systems(Startup, setup)
.add_systems(
Update,
translate_discreet_volume_to_global_volume.run_if(resource_changed::<Volume>),
)
// 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<AssetServer>) {
commands.spawn(Camera2d);
// Sound
let music = asset_server.load("sounds/Windless Slopes.ogg");
commands.insert_resource(MenuMusic(music));
}

mod splash {
Expand Down Expand Up @@ -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::<OnGameScreen>);
.add_systems(
OnExit(GameState::Game),
(despawn_screen::<OnGameScreen>, stop_music),
);
}

// Tag component used to tag entities added on the game screen
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -265,14 +283,17 @@ mod menu {
despawn_screen::<OnDisplaySettingsMenuScreen>,
)
// 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::<Volume>.run_if(in_state(MenuState::SettingsSound)),
)
.add_systems(
OnExit(MenuState::SettingsSound),
despawn_screen::<OnSoundSettingsMenuScreen>,
(despawn_screen::<OnSoundSettingsMenuScreen>, stop_music),
)
// Common systems to all screens that handles buttons behavior
.add_systems(
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -735,3 +756,25 @@ fn despawn_screen<T: Component>(to_despawn: Query<Entity, With<T>>, mut commands
commands.entity(entity).despawn();
}
}

fn stop_music(playing_sounds_query: Query<AnyOf<(&AudioSink, &SpatialAudioSink)>>) {
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<MenuMusic>) {
commands.spawn((AudioPlayer(music.clone()), PlaybackSettings::DESPAWN));
}

fn translate_discreet_volume_to_global_volume(
volume: Res<Volume>,
mut global_volume: ResMut<GlobalVolume>,
) {
global_volume.volume = bevy::audio::Volume::Linear(volume.0 as f32 / VOLUME_MAX as f32);
}