diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index d6af1906def4b..58dc0e49a1e6b 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -1,3 +1,5 @@ +#![expect(deprecated)] + use crate::{AudioSource, Decodable}; use bevy_asset::{Asset, Handle}; use bevy_derive::Deref; @@ -228,8 +230,40 @@ impl Default for SpatialScale { pub struct DefaultSpatialScale(pub SpatialScale); /// Bundle for playing a standard bevy audio asset +#[deprecated( + since = "0.15.0", + note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." +)] pub type AudioBundle = AudioSourceBundle; +/// A component for playing a sound. +/// +/// Insert this component onto an entity to trigger an audio source to begin playing. +/// +/// If the handle refers to an unavailable asset (such as if it has not finished loading yet), +/// the audio will not begin playing immediately. The audio will play when the asset is ready. +/// +/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be +/// added to the entity. You can use that component to control the audio settings during playback. +/// +/// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the +/// `PlaybackSettings` component will *not* affect already-playing audio. +#[derive(Component, Reflect)] +#[reflect(Component)] +#[require(PlaybackSettings)] +pub struct AudioPlayer(pub Handle) +where + Source: Asset + Decodable; + +impl Clone for AudioPlayer +where + Source: Asset + Decodable, +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + /// Bundle for playing a sound. /// /// Insert this bundle onto an entity to trigger a sound source to begin playing. @@ -240,12 +274,16 @@ pub type AudioBundle = AudioSourceBundle; /// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be /// added to the entity. You can use that component to control the audio settings during playback. #[derive(Bundle)] +#[deprecated( + since = "0.15.0", + note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." +)] pub struct AudioSourceBundle where Source: Asset + Decodable, { /// Asset containing the audio data to play. - pub source: Handle, + pub source: AudioPlayer, /// Initial settings that the audio starts playing with. /// If you would like to control the audio while it is playing, /// query for the [`AudioSink`][crate::AudioSink] component. @@ -265,7 +303,7 @@ impl Clone for AudioSourceBundle { impl Default for AudioSourceBundle { fn default() -> Self { Self { - source: Default::default(), + source: AudioPlayer(Handle::default()), settings: Default::default(), } } diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 69a31acff1878..c595be70ca609 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, DefaultSpatialScale, GlobalVolume, PlaybackMode, - PlaybackSettings, SpatialAudioSink, SpatialListener, + AudioPlayer, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode, PlaybackSettings, + SpatialAudioSink, SpatialListener, }; use bevy_asset::{Asset, Assets, Handle}; use bevy_ecs::{prelude::*, system::SystemParam}; @@ -89,8 +89,7 @@ impl<'w, 's> EarPositions<'w, 's> { /// Plays "queued" audio through the [`AudioOutput`] resource. /// -/// "Queued" audio is any audio entity (with the components from -/// [`AudioBundle`][crate::AudioBundle] that does not have an +/// "Queued" audio is any audio entity (with an [`AudioPlayer`] component) that does not have an /// [`AudioSink`]/[`SpatialAudioSink`] component. /// /// This system detects such entities, checks if their source asset @@ -141,7 +140,7 @@ pub(crate) fn play_queued_audio_system( let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform { (emitter_transform.translation() * scale).into() } else { - warn!("Spatial AudioBundle with no GlobalTransform component. Using zero."); + warn!("Spatial AudioPlayer with no GlobalTransform component. Using zero."); Vec3::ZERO.into() }; @@ -264,16 +263,22 @@ pub(crate) fn cleanup_finished_audio( } for (entity, sink) in &query_nonspatial_remove { if sink.sink.empty() { - commands - .entity(entity) - .remove::<(AudioSourceBundle, AudioSink, PlaybackRemoveMarker)>(); + commands.entity(entity).remove::<( + AudioPlayer, + AudioSink, + PlaybackSettings, + PlaybackRemoveMarker, + )>(); } } for (entity, sink) in &query_spatial_remove { if sink.sink.empty() { - commands - .entity(entity) - .remove::<(AudioSourceBundle, SpatialAudioSink, PlaybackRemoveMarker)>(); + commands.entity(entity).remove::<( + AudioPlayer, + SpatialAudioSink, + PlaybackSettings, + PlaybackRemoveMarker, + )>(); } } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 6fae1e835d972..5120a341a1e91 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -9,7 +9,7 @@ //! //! ```no_run //! # use bevy_ecs::prelude::*; -//! # use bevy_audio::{AudioBundle, AudioPlugin, PlaybackSettings}; +//! # use bevy_audio::{AudioPlayer, AudioPlugin, AudioSource, PlaybackSettings}; //! # use bevy_asset::{AssetPlugin, AssetServer}; //! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup}; //! fn main() { @@ -20,10 +20,10 @@ //! } //! //! fn play_background_audio(asset_server: Res, mut commands: Commands) { -//! commands.spawn(AudioBundle { -//! source: asset_server.load("background_audio.ogg"), -//! settings: PlaybackSettings::LOOP, -//! }); +//! commands.spawn(( +//! AudioPlayer::(asset_server.load("background_audio.ogg")), +//! PlaybackSettings::LOOP, +//! )); //! } //! ``` @@ -38,11 +38,13 @@ mod sinks; /// The audio prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. +#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ - AudioBundle, AudioSink, AudioSinkPlayback, AudioSource, AudioSourceBundle, Decodable, - GlobalVolume, Pitch, PitchBundle, PlaybackSettings, SpatialAudioSink, SpatialListener, + AudioBundle, AudioPlayer, AudioSink, AudioSinkPlayback, AudioSource, AudioSourceBundle, + Decodable, GlobalVolume, Pitch, PitchBundle, PlaybackSettings, SpatialAudioSink, + SpatialListener, }; } @@ -66,7 +68,7 @@ struct AudioPlaySet; /// Adds support for audio playback to a Bevy Application /// -/// Insert an [`AudioBundle`] onto your entities to play audio. +/// Insert an [`AudioPlayer`] onto your entities to play audio. #[derive(Default)] pub struct AudioPlugin { /// The global volume for all audio entities. diff --git a/crates/bevy_audio/src/pitch.rs b/crates/bevy_audio/src/pitch.rs index 1f4c406a5d967..02863d6c62781 100644 --- a/crates/bevy_audio/src/pitch.rs +++ b/crates/bevy_audio/src/pitch.rs @@ -1,3 +1,5 @@ +#![expect(deprecated)] + use crate::{AudioSourceBundle, Decodable}; use bevy_asset::Asset; use bevy_reflect::TypePath; @@ -35,4 +37,8 @@ impl Decodable for Pitch { } /// Bundle for playing a bevy note sound +#[deprecated( + since = "0.15.0", + note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." +)] pub type PitchBundle = AudioSourceBundle; diff --git a/crates/bevy_audio/src/sinks.rs b/crates/bevy_audio/src/sinks.rs index 68fa1e99c4543..4c2cee3b935a7 100644 --- a/crates/bevy_audio/src/sinks.rs +++ b/crates/bevy_audio/src/sinks.rs @@ -76,7 +76,7 @@ pub trait AudioSinkPlayback { /// Used to control audio during playback. /// /// Bevy inserts this component onto your entities when it begins playing an audio source. -/// Use [`AudioBundle`][crate::AudioBundle] to trigger that to happen. +/// Use [`AudioPlayer`][crate::AudioPlayer] to trigger that to happen. /// /// You can use this component to modify the playback settings while the audio is playing. /// diff --git a/examples/audio/audio.rs b/examples/audio/audio.rs index 5309587275873..5f4225fc73c7b 100644 --- a/examples/audio/audio.rs +++ b/examples/audio/audio.rs @@ -11,8 +11,7 @@ fn main() { } fn setup(asset_server: Res, mut commands: Commands) { - commands.spawn(AudioBundle { - source: asset_server.load("sounds/Windless Slopes.ogg"), - ..default() - }); + commands.spawn(AudioPlayer::( + asset_server.load("sounds/Windless Slopes.ogg"), + )); } diff --git a/examples/audio/audio_control.rs b/examples/audio/audio_control.rs index 6adbded9e3240..320502998d56a 100644 --- a/examples/audio/audio_control.rs +++ b/examples/audio/audio_control.rs @@ -12,10 +12,7 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(( - AudioBundle { - source: asset_server.load("sounds/Windless Slopes.ogg"), - ..default() - }, + AudioPlayer::(asset_server.load("sounds/Windless Slopes.ogg")), MyMusic, )); } diff --git a/examples/audio/decodable.rs b/examples/audio/decodable.rs index 60f0c5443ff3d..b26952ad06b32 100644 --- a/examples/audio/decodable.rs +++ b/examples/audio/decodable.rs @@ -99,8 +99,5 @@ fn setup(mut assets: ResMut>, mut commands: Commands) { let audio_handle = assets.add(SineAudio { frequency: 440., // this is the frequency of A4 }); - commands.spawn(AudioSourceBundle { - source: audio_handle, - ..default() - }); + commands.spawn(AudioPlayer(audio_handle)); } diff --git a/examples/audio/pitch.rs b/examples/audio/pitch.rs index fc81d8a575598..06aeccc3f4ea2 100644 --- a/examples/audio/pitch.rs +++ b/examples/audio/pitch.rs @@ -30,10 +30,10 @@ fn play_pitch( ) { for _ in events.read() { info!("playing pitch with frequency: {}", frequency.0); - commands.spawn(PitchBundle { - source: pitch_assets.add(Pitch::new(frequency.0, Duration::new(1, 0))), - settings: PlaybackSettings::DESPAWN, - }); + commands.spawn(( + AudioPlayer(pitch_assets.add(Pitch::new(frequency.0, Duration::new(1, 0)))), + PlaybackSettings::DESPAWN, + )); info!("number of pitch assets: {}", pitch_assets.len()); } } diff --git a/examples/audio/soundtrack.rs b/examples/audio/soundtrack.rs index 58473def761d7..a8b931d777f64 100644 --- a/examples/audio/soundtrack.rs +++ b/examples/audio/soundtrack.rs @@ -72,33 +72,29 @@ fn change_track( commands.entity(track).insert(FadeOut); } - // Spawn a new `AudioBundle` with the appropriate soundtrack based on + // Spawn a new `AudioPlayer` with the appropriate soundtrack based on // the game state. // // Volume is set to start at zero and is then increased by the fade_in system. match game_state.as_ref() { GameState::Peaceful => { commands.spawn(( - AudioBundle { - source: soundtrack_player.track_list.first().unwrap().clone(), - settings: PlaybackSettings { - mode: bevy::audio::PlaybackMode::Loop, - volume: bevy::audio::Volume::ZERO, - ..default() - }, + AudioPlayer(soundtrack_player.track_list.first().unwrap().clone()), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + volume: bevy::audio::Volume::ZERO, + ..default() }, FadeIn, )); } GameState::Battle => { commands.spawn(( - AudioBundle { - source: soundtrack_player.track_list.get(1).unwrap().clone(), - settings: PlaybackSettings { - mode: bevy::audio::PlaybackMode::Loop, - volume: bevy::audio::Volume::ZERO, - ..default() - }, + AudioPlayer(soundtrack_player.track_list.get(1).unwrap().clone()), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + volume: bevy::audio::Volume::ZERO, + ..default() }, FadeIn, )); diff --git a/examples/audio/spatial_audio_2d.rs b/examples/audio/spatial_audio_2d.rs index c1f8e9bb648a6..d6ac629486533 100644 --- a/examples/audio/spatial_audio_2d.rs +++ b/examples/audio/spatial_audio_2d.rs @@ -37,10 +37,8 @@ fn setup( MeshMaterial2d(materials.add(Color::from(BLUE))), Transform::from_translation(Vec3::new(0.0, 50.0, 0.0)), Emitter::default(), - AudioBundle { - source: asset_server.load("sounds/Windless Slopes.ogg"), - settings: PlaybackSettings::LOOP.with_spatial(true), - }, + AudioPlayer::(asset_server.load("sounds/Windless Slopes.ogg")), + PlaybackSettings::LOOP.with_spatial(true), )); let listener = SpatialListener::new(gap); diff --git a/examples/audio/spatial_audio_3d.rs b/examples/audio/spatial_audio_3d.rs index 0e4134d53c579..4f44142c0051a 100644 --- a/examples/audio/spatial_audio_3d.rs +++ b/examples/audio/spatial_audio_3d.rs @@ -28,10 +28,8 @@ fn setup( MeshMaterial3d(materials.add(Color::from(BLUE))), Transform::from_xyz(0.0, 0.0, 0.0), Emitter::default(), - AudioBundle { - source: asset_server.load("sounds/Windless Slopes.ogg"), - settings: PlaybackSettings::LOOP.with_spatial(true), - }, + AudioPlayer::(asset_server.load("sounds/Windless Slopes.ogg")), + PlaybackSettings::LOOP.with_spatial(true), )); let listener = SpatialListener::new(gap); diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index 124f95879516d..166887fc99f83 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -416,11 +416,7 @@ fn play_collision_sound( if !collision_events.is_empty() { // This prevents events staying active on the next frame. collision_events.clear(); - commands.spawn(AudioBundle { - source: sound.clone(), - // auto-despawn the entity when playback finishes - settings: PlaybackSettings::DESPAWN, - }); + commands.spawn((AudioPlayer(sound.clone()), PlaybackSettings::DESPAWN)); } } diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index ef3bd8ab9f21a..7311d7789d782 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -157,10 +157,10 @@ fn button_handler( } fn setup_music(asset_server: Res, mut commands: Commands) { - commands.spawn(AudioBundle { - source: asset_server.load("sounds/Windless Slopes.ogg"), - settings: PlaybackSettings::LOOP, - }); + commands.spawn(( + AudioPlayer::(asset_server.load("sounds/Windless Slopes.ogg")), + PlaybackSettings::LOOP, + )); } // Pause audio when app goes into background and resume when it returns.