diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 709d595d4e758..bbddfbb689d90 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -67,6 +67,9 @@ pub struct PlaybackSettings { /// Note: Bevy does not currently support HRTF or any other high-quality 3D sound rendering /// features. Spatial audio is implemented via simple left-right stereo panning. pub spatial: bool, + /// Optional scale factor applied to the positions of this audio source and the listener, + /// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale). + pub spatial_scale: Option, } impl Default for PlaybackSettings { @@ -84,6 +87,7 @@ impl PlaybackSettings { speed: 1.0, paused: false, spatial: false, + spatial_scale: None, }; /// Will play the associated audio source in a loop. @@ -127,6 +131,12 @@ impl PlaybackSettings { self.spatial = spatial; self } + + /// Helper to use a custom spatial scale. + pub const fn with_spatial_scale(mut self, spatial_scale: SpatialScale) -> Self { + self.spatial_scale = Some(spatial_scale); + self + } } /// Settings for the listener for spatial audio sources. @@ -180,25 +190,22 @@ impl GlobalVolume { } } -/// The scale factor applied to the positions of audio sources and listeners for +/// A scale factor applied to the positions of audio sources and listeners for /// spatial audio. /// -/// You may need to adjust this scale to fit your world's units. -/// /// Default is `Vec3::ONE`. -#[derive(Resource, Clone, Copy, Reflect)] -#[reflect(Resource)] +#[derive(Clone, Copy, Debug, Reflect)] pub struct SpatialScale(pub Vec3); impl SpatialScale { /// Create a new `SpatialScale` with the same value for all 3 dimensions. - pub fn new(scale: f32) -> Self { + pub const fn new(scale: f32) -> Self { Self(Vec3::splat(scale)) } /// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0` /// for `z`. - pub fn new_2d(scale: f32) -> Self { + pub const fn new_2d(scale: f32) -> Self { Self(Vec3::new(scale, scale, 0.0)) } } @@ -209,6 +216,16 @@ impl Default for SpatialScale { } } +/// The default scale factor applied to the positions of audio sources and listeners for +/// spatial audio. Can be overridden for individual sounds in [`PlaybackSettings`]. +/// +/// You may need to adjust this scale to fit your world's units. +/// +/// Default is `Vec3::ONE`. +#[derive(Resource, Default, Clone, Copy, Reflect)] +#[reflect(Resource)] +pub struct DefaultSpatialScale(pub SpatialScale); + /// Bundle for playing a standard bevy audio asset pub type AudioBundle = AudioSourceBundle; diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 2acf0c6ebba18..75a7f6451b488 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -1,6 +1,6 @@ use crate::{ - AudioSourceBundle, Decodable, GlobalVolume, PlaybackMode, PlaybackSettings, SpatialAudioSink, - SpatialListener, SpatialScale, + AudioSourceBundle, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode, + PlaybackSettings, SpatialAudioSink, SpatialListener, }; use bevy_asset::{Asset, Assets, Handle}; use bevy_ecs::{prelude::*, system::SystemParam}; @@ -56,10 +56,9 @@ pub struct PlaybackRemoveMarker; #[derive(SystemParam)] pub(crate) struct EarPositions<'w, 's> { pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>, - pub(crate) scale: Res<'w, SpatialScale>, } impl<'w, 's> EarPositions<'w, 's> { - /// Gets a set of transformed and scaled ear positions. + /// Gets a set of transformed ear positions. /// /// If there are no listeners, use the default values. If a user has added multiple /// listeners for whatever reason, we will return the first value. @@ -70,16 +69,13 @@ impl<'w, 's> EarPositions<'w, 's> { .next() .map(|(_, transform, settings)| { ( - transform.transform_point(settings.left_ear_offset) * self.scale.0, - transform.transform_point(settings.right_ear_offset) * self.scale.0, + transform.transform_point(settings.left_ear_offset), + transform.transform_point(settings.right_ear_offset), ) }) .unwrap_or_else(|| { let settings = SpatialListener::default(); - ( - settings.left_ear_offset * self.scale.0, - settings.right_ear_offset * self.scale.0, - ) + (settings.left_ear_offset, settings.right_ear_offset) }); (left_ear, right_ear) @@ -112,6 +108,7 @@ pub(crate) fn play_queued_audio_system( (Without, Without), >, ear_positions: EarPositions, + default_spatial_scale: Res, mut commands: Commands, ) where f32: rodio::cpal::FromSample, @@ -138,8 +135,10 @@ pub(crate) fn play_queued_audio_system( ); } + let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0; + let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform { - (emitter_transform.translation() * ear_positions.scale.0).into() + (emitter_transform.translation() * scale).into() } else { warn!("Spatial AudioBundle with no GlobalTransform component. Using zero."); Vec3::ZERO.into() @@ -148,8 +147,8 @@ pub(crate) fn play_queued_audio_system( let sink = match SpatialSink::try_new( stream_handle, emitter_translation, - left_ear.into(), - right_ear.into(), + (left_ear * scale).into(), + (right_ear * scale).into(), ) { Ok(sink) => sink, Err(err) => { @@ -285,34 +284,46 @@ pub(crate) fn audio_output_available(audio_output: Res) -> bool { /// Updates spatial audio sinks when emitter positions change. pub(crate) fn update_emitter_positions( - mut emitters: Query<(&GlobalTransform, &SpatialAudioSink), Changed>, - spatial_scale: Res, + mut emitters: Query< + (&GlobalTransform, &SpatialAudioSink, &PlaybackSettings), + Or<(Changed, Changed)>, + >, + default_spatial_scale: Res, ) { - for (transform, sink) in emitters.iter_mut() { - let translation = transform.translation() * spatial_scale.0; + for (transform, sink, settings) in emitters.iter_mut() { + let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0; + + let translation = transform.translation() * scale; sink.set_emitter_position(translation); } } /// Updates spatial audio sink ear positions when spatial listeners change. pub(crate) fn update_listener_positions( - mut emitters: Query<&SpatialAudioSink>, + mut emitters: Query<(&SpatialAudioSink, &PlaybackSettings)>, changed_listener: Query< (), ( - Or<(Changed, Changed)>, + Or<( + Changed, + Changed, + Changed, + )>, With, ), >, ear_positions: EarPositions, + default_spatial_scale: Res, ) { - if !ear_positions.scale.is_changed() && changed_listener.is_empty() { + if !default_spatial_scale.is_changed() && changed_listener.is_empty() { return; } let (left_ear, right_ear) = ear_positions.get(); - for sink in emitters.iter_mut() { - sink.set_ears_position(left_ear, right_ear); + for (sink, settings) in emitters.iter_mut() { + let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0; + + sink.set_ears_position(left_ear * scale, right_ear * scale); } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 1d63ac7d461f8..ecba57bf0df95 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -67,7 +67,7 @@ pub struct AudioPlugin { pub global_volume: GlobalVolume, /// The scale factor applied to the positions of audio sources and listeners for /// spatial audio. - pub spatial_scale: SpatialScale, + pub default_spatial_scale: SpatialScale, } impl Plugin for AudioPlugin { @@ -75,11 +75,11 @@ impl Plugin for AudioPlugin { app.register_type::() .register_type::() .register_type::() - .register_type::() + .register_type::() .register_type::() .register_type::() .insert_resource(self.global_volume) - .insert_resource(self.spatial_scale) + .insert_resource(DefaultSpatialScale(self.default_spatial_scale)) .configure_sets( PostUpdate, AudioPlaySet diff --git a/examples/audio/spatial_audio_2d.rs b/examples/audio/spatial_audio_2d.rs index f911bb59e830c..79018311eb567 100644 --- a/examples/audio/spatial_audio_2d.rs +++ b/examples/audio/spatial_audio_2d.rs @@ -13,7 +13,7 @@ const AUDIO_SCALE: f32 = 1. / 100.0; fn main() { App::new() .add_plugins(DefaultPlugins.set(AudioPlugin { - spatial_scale: SpatialScale::new_2d(AUDIO_SCALE), + default_spatial_scale: SpatialScale::new_2d(AUDIO_SCALE), ..default() })) .add_systems(Startup, setup)