Migrate audio to required components (#15573)

# Objective

What's that? Another PR for the grand migration to required components?
This time, audio!

## Solution

Deprecate `AudioSourceBundle`, `AudioBundle`, and `PitchBundle`, as per
the [chosen
proposal](https://hackmd.io/@bevy/required_components/%2Fzxgp-zMMRUCdT7LY1ZDQwQ).

However, we cannot call the component `AudioSource`, because that's what
the stored asset is called. I deliberated on a few names, like
`AudioHandle`, or even just `Audio`, but landed on `AudioPlayer`, since
it's probably the most accurate and "nice" name for this. Open to
alternatives though.

---

## Migration Guide

Replace all insertions of `AudioSoucreBundle`, `AudioBundle`, and
`PitchBundle` with the `AudioPlayer` component. The other components
required by it will now be inserted automatically.

In cases where the generics cannot be inferred, you may need to specify
them explicitly. For example:

```rust
commands.spawn(AudioPlayer::<AudioSource>(asset_server.load("sounds/sick_beats.ogg")));
```
This commit is contained in:
Joona Aalto 2024-10-02 01:43:29 +03:00 committed by GitHub
parent eb51b4c28e
commit ed151e756c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 102 additions and 70 deletions

View file

@ -1,3 +1,5 @@
#![expect(deprecated)]
use crate::{AudioSource, Decodable}; use crate::{AudioSource, Decodable};
use bevy_asset::{Asset, Handle}; use bevy_asset::{Asset, Handle};
use bevy_derive::Deref; use bevy_derive::Deref;
@ -228,8 +230,40 @@ impl Default for SpatialScale {
pub struct DefaultSpatialScale(pub SpatialScale); pub struct DefaultSpatialScale(pub SpatialScale);
/// Bundle for playing a standard bevy audio asset /// 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<AudioSource>; pub type AudioBundle = AudioSourceBundle<AudioSource>;
/// 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<Source = AudioSource>(pub Handle<Source>)
where
Source: Asset + Decodable;
impl<Source> Clone for AudioPlayer<Source>
where
Source: Asset + Decodable,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
/// Bundle for playing a sound. /// Bundle for playing a sound.
/// ///
/// Insert this bundle onto an entity to trigger a sound source to begin playing. /// Insert this bundle onto an entity to trigger a sound source to begin playing.
@ -240,12 +274,16 @@ pub type AudioBundle = AudioSourceBundle<AudioSource>;
/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be /// 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. /// added to the entity. You can use that component to control the audio settings during playback.
#[derive(Bundle)] #[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<Source = AudioSource> pub struct AudioSourceBundle<Source = AudioSource>
where where
Source: Asset + Decodable, Source: Asset + Decodable,
{ {
/// Asset containing the audio data to play. /// Asset containing the audio data to play.
pub source: Handle<Source>, pub source: AudioPlayer<Source>,
/// Initial settings that the audio starts playing with. /// Initial settings that the audio starts playing with.
/// If you would like to control the audio while it is playing, /// If you would like to control the audio while it is playing,
/// query for the [`AudioSink`][crate::AudioSink] component. /// query for the [`AudioSink`][crate::AudioSink] component.
@ -265,7 +303,7 @@ impl<T: Asset + Decodable> Clone for AudioSourceBundle<T> {
impl<T: Decodable + Asset> Default for AudioSourceBundle<T> { impl<T: Decodable + Asset> Default for AudioSourceBundle<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
source: Default::default(), source: AudioPlayer(Handle::default()),
settings: Default::default(), settings: Default::default(),
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AudioSourceBundle, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode, AudioPlayer, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode, PlaybackSettings,
PlaybackSettings, SpatialAudioSink, SpatialListener, SpatialAudioSink, SpatialListener,
}; };
use bevy_asset::{Asset, Assets, Handle}; use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::{prelude::*, system::SystemParam}; use bevy_ecs::{prelude::*, system::SystemParam};
@ -89,8 +89,7 @@ impl<'w, 's> EarPositions<'w, 's> {
/// Plays "queued" audio through the [`AudioOutput`] resource. /// Plays "queued" audio through the [`AudioOutput`] resource.
/// ///
/// "Queued" audio is any audio entity (with the components from /// "Queued" audio is any audio entity (with an [`AudioPlayer`] component) that does not have an
/// [`AudioBundle`][crate::AudioBundle] that does not have an
/// [`AudioSink`]/[`SpatialAudioSink`] component. /// [`AudioSink`]/[`SpatialAudioSink`] component.
/// ///
/// This system detects such entities, checks if their source asset /// This system detects such entities, checks if their source asset
@ -141,7 +140,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform { let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
(emitter_transform.translation() * scale).into() (emitter_transform.translation() * scale).into()
} else { } else {
warn!("Spatial AudioBundle with no GlobalTransform component. Using zero."); warn!("Spatial AudioPlayer with no GlobalTransform component. Using zero.");
Vec3::ZERO.into() Vec3::ZERO.into()
}; };
@ -264,16 +263,22 @@ pub(crate) fn cleanup_finished_audio<T: Decodable + Asset>(
} }
for (entity, sink) in &query_nonspatial_remove { for (entity, sink) in &query_nonspatial_remove {
if sink.sink.empty() { if sink.sink.empty() {
commands commands.entity(entity).remove::<(
.entity(entity) AudioPlayer<T>,
.remove::<(AudioSourceBundle<T>, AudioSink, PlaybackRemoveMarker)>(); AudioSink,
PlaybackSettings,
PlaybackRemoveMarker,
)>();
} }
} }
for (entity, sink) in &query_spatial_remove { for (entity, sink) in &query_spatial_remove {
if sink.sink.empty() { if sink.sink.empty() {
commands commands.entity(entity).remove::<(
.entity(entity) AudioPlayer<T>,
.remove::<(AudioSourceBundle<T>, SpatialAudioSink, PlaybackRemoveMarker)>(); SpatialAudioSink,
PlaybackSettings,
PlaybackRemoveMarker,
)>();
} }
} }
} }

View file

@ -9,7 +9,7 @@
//! //!
//! ```no_run //! ```no_run
//! # use bevy_ecs::prelude::*; //! # use bevy_ecs::prelude::*;
//! # use bevy_audio::{AudioBundle, AudioPlugin, PlaybackSettings}; //! # use bevy_audio::{AudioPlayer, AudioPlugin, AudioSource, PlaybackSettings};
//! # use bevy_asset::{AssetPlugin, AssetServer}; //! # use bevy_asset::{AssetPlugin, AssetServer};
//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup}; //! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup};
//! fn main() { //! fn main() {
@ -20,10 +20,10 @@
//! } //! }
//! //!
//! fn play_background_audio(asset_server: Res<AssetServer>, mut commands: Commands) { //! fn play_background_audio(asset_server: Res<AssetServer>, mut commands: Commands) {
//! commands.spawn(AudioBundle { //! commands.spawn((
//! source: asset_server.load("background_audio.ogg"), //! AudioPlayer::<AudioSource>(asset_server.load("background_audio.ogg")),
//! settings: PlaybackSettings::LOOP, //! PlaybackSettings::LOOP,
//! }); //! ));
//! } //! }
//! ``` //! ```
@ -38,11 +38,13 @@ mod sinks;
/// The audio prelude. /// The audio prelude.
/// ///
/// This includes the most common types in this crate, re-exported for your convenience. /// This includes the most common types in this crate, re-exported for your convenience.
#[expect(deprecated)]
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
AudioBundle, AudioSink, AudioSinkPlayback, AudioSource, AudioSourceBundle, Decodable, AudioBundle, AudioPlayer, AudioSink, AudioSinkPlayback, AudioSource, AudioSourceBundle,
GlobalVolume, Pitch, PitchBundle, PlaybackSettings, SpatialAudioSink, SpatialListener, Decodable, GlobalVolume, Pitch, PitchBundle, PlaybackSettings, SpatialAudioSink,
SpatialListener,
}; };
} }
@ -66,7 +68,7 @@ struct AudioPlaySet;
/// Adds support for audio playback to a Bevy Application /// 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)] #[derive(Default)]
pub struct AudioPlugin { pub struct AudioPlugin {
/// The global volume for all audio entities. /// The global volume for all audio entities.

View file

@ -1,3 +1,5 @@
#![expect(deprecated)]
use crate::{AudioSourceBundle, Decodable}; use crate::{AudioSourceBundle, Decodable};
use bevy_asset::Asset; use bevy_asset::Asset;
use bevy_reflect::TypePath; use bevy_reflect::TypePath;
@ -35,4 +37,8 @@ impl Decodable for Pitch {
} }
/// Bundle for playing a bevy note sound /// Bundle for playing a bevy note sound
#[deprecated(
since = "0.15.0",
note = "Use the `AudioPlayer<Pitch>` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically."
)]
pub type PitchBundle = AudioSourceBundle<Pitch>; pub type PitchBundle = AudioSourceBundle<Pitch>;

View file

@ -76,7 +76,7 @@ pub trait AudioSinkPlayback {
/// Used to control audio during playback. /// Used to control audio during playback.
/// ///
/// Bevy inserts this component onto your entities when it begins playing an audio source. /// 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. /// You can use this component to modify the playback settings while the audio is playing.
/// ///

View file

@ -11,8 +11,7 @@ fn main() {
} }
fn setup(asset_server: Res<AssetServer>, mut commands: Commands) { fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.spawn(AudioBundle { commands.spawn(AudioPlayer::<AudioSource>(
source: asset_server.load("sounds/Windless Slopes.ogg"), asset_server.load("sounds/Windless Slopes.ogg"),
..default() ));
});
} }

View file

@ -12,10 +12,7 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(( commands.spawn((
AudioBundle { AudioPlayer::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
source: asset_server.load("sounds/Windless Slopes.ogg"),
..default()
},
MyMusic, MyMusic,
)); ));
} }

View file

@ -99,8 +99,5 @@ fn setup(mut assets: ResMut<Assets<SineAudio>>, mut commands: Commands) {
let audio_handle = assets.add(SineAudio { let audio_handle = assets.add(SineAudio {
frequency: 440., // this is the frequency of A4 frequency: 440., // this is the frequency of A4
}); });
commands.spawn(AudioSourceBundle { commands.spawn(AudioPlayer(audio_handle));
source: audio_handle,
..default()
});
} }

View file

@ -30,10 +30,10 @@ fn play_pitch(
) { ) {
for _ in events.read() { for _ in events.read() {
info!("playing pitch with frequency: {}", frequency.0); info!("playing pitch with frequency: {}", frequency.0);
commands.spawn(PitchBundle { commands.spawn((
source: pitch_assets.add(Pitch::new(frequency.0, Duration::new(1, 0))), AudioPlayer(pitch_assets.add(Pitch::new(frequency.0, Duration::new(1, 0)))),
settings: PlaybackSettings::DESPAWN, PlaybackSettings::DESPAWN,
}); ));
info!("number of pitch assets: {}", pitch_assets.len()); info!("number of pitch assets: {}", pitch_assets.len());
} }
} }

View file

@ -72,34 +72,30 @@ fn change_track(
commands.entity(track).insert(FadeOut); 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. // the game state.
// //
// Volume is set to start at zero and is then increased by the fade_in system. // Volume is set to start at zero and is then increased by the fade_in system.
match game_state.as_ref() { match game_state.as_ref() {
GameState::Peaceful => { GameState::Peaceful => {
commands.spawn(( commands.spawn((
AudioBundle { AudioPlayer(soundtrack_player.track_list.first().unwrap().clone()),
source: soundtrack_player.track_list.first().unwrap().clone(), PlaybackSettings {
settings: PlaybackSettings {
mode: bevy::audio::PlaybackMode::Loop, mode: bevy::audio::PlaybackMode::Loop,
volume: bevy::audio::Volume::ZERO, volume: bevy::audio::Volume::ZERO,
..default() ..default()
}, },
},
FadeIn, FadeIn,
)); ));
} }
GameState::Battle => { GameState::Battle => {
commands.spawn(( commands.spawn((
AudioBundle { AudioPlayer(soundtrack_player.track_list.get(1).unwrap().clone()),
source: soundtrack_player.track_list.get(1).unwrap().clone(), PlaybackSettings {
settings: PlaybackSettings {
mode: bevy::audio::PlaybackMode::Loop, mode: bevy::audio::PlaybackMode::Loop,
volume: bevy::audio::Volume::ZERO, volume: bevy::audio::Volume::ZERO,
..default() ..default()
}, },
},
FadeIn, FadeIn,
)); ));
} }

View file

@ -37,10 +37,8 @@ fn setup(
MeshMaterial2d(materials.add(Color::from(BLUE))), MeshMaterial2d(materials.add(Color::from(BLUE))),
Transform::from_translation(Vec3::new(0.0, 50.0, 0.0)), Transform::from_translation(Vec3::new(0.0, 50.0, 0.0)),
Emitter::default(), Emitter::default(),
AudioBundle { AudioPlayer::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
source: asset_server.load("sounds/Windless Slopes.ogg"), PlaybackSettings::LOOP.with_spatial(true),
settings: PlaybackSettings::LOOP.with_spatial(true),
},
)); ));
let listener = SpatialListener::new(gap); let listener = SpatialListener::new(gap);

View file

@ -28,10 +28,8 @@ fn setup(
MeshMaterial3d(materials.add(Color::from(BLUE))), MeshMaterial3d(materials.add(Color::from(BLUE))),
Transform::from_xyz(0.0, 0.0, 0.0), Transform::from_xyz(0.0, 0.0, 0.0),
Emitter::default(), Emitter::default(),
AudioBundle { AudioPlayer::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
source: asset_server.load("sounds/Windless Slopes.ogg"), PlaybackSettings::LOOP.with_spatial(true),
settings: PlaybackSettings::LOOP.with_spatial(true),
},
)); ));
let listener = SpatialListener::new(gap); let listener = SpatialListener::new(gap);

View file

@ -416,11 +416,7 @@ fn play_collision_sound(
if !collision_events.is_empty() { if !collision_events.is_empty() {
// This prevents events staying active on the next frame. // This prevents events staying active on the next frame.
collision_events.clear(); collision_events.clear();
commands.spawn(AudioBundle { commands.spawn((AudioPlayer(sound.clone()), PlaybackSettings::DESPAWN));
source: sound.clone(),
// auto-despawn the entity when playback finishes
settings: PlaybackSettings::DESPAWN,
});
} }
} }

View file

@ -157,10 +157,10 @@ fn button_handler(
} }
fn setup_music(asset_server: Res<AssetServer>, mut commands: Commands) { fn setup_music(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.spawn(AudioBundle { commands.spawn((
source: asset_server.load("sounds/Windless Slopes.ogg"), AudioPlayer::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
settings: PlaybackSettings::LOOP, PlaybackSettings::LOOP,
}); ));
} }
// Pause audio when app goes into background and resume when it returns. // Pause audio when app goes into background and resume when it returns.