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 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<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.
///
/// 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
/// 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<Source = AudioSource>
where
Source: Asset + Decodable,
{
/// Asset containing the audio data to play.
pub source: Handle<Source>,
pub source: AudioPlayer<Source>,
/// 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<T: Asset + Decodable> Clone for AudioSourceBundle<T> {
impl<T: Decodable + Asset> Default for AudioSourceBundle<T> {
fn default() -> Self {
Self {
source: Default::default(),
source: AudioPlayer(Handle::default()),
settings: Default::default(),
}
}

View file

@ -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<Source: Asset + Decodable>(
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<T: Decodable + Asset>(
}
for (entity, sink) in &query_nonspatial_remove {
if sink.sink.empty() {
commands
.entity(entity)
.remove::<(AudioSourceBundle<T>, AudioSink, PlaybackRemoveMarker)>();
commands.entity(entity).remove::<(
AudioPlayer<T>,
AudioSink,
PlaybackSettings,
PlaybackRemoveMarker,
)>();
}
}
for (entity, sink) in &query_spatial_remove {
if sink.sink.empty() {
commands
.entity(entity)
.remove::<(AudioSourceBundle<T>, SpatialAudioSink, PlaybackRemoveMarker)>();
commands.entity(entity).remove::<(
AudioPlayer<T>,
SpatialAudioSink,
PlaybackSettings,
PlaybackRemoveMarker,
)>();
}
}
}

View file

@ -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<AssetServer>, mut commands: Commands) {
//! commands.spawn(AudioBundle {
//! source: asset_server.load("background_audio.ogg"),
//! settings: PlaybackSettings::LOOP,
//! });
//! commands.spawn((
//! AudioPlayer::<AudioSource>(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.

View file

@ -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<Pitch>` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically."
)]
pub type PitchBundle = AudioSourceBundle<Pitch>;

View file

@ -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.
///

View file

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

View file

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

View file

@ -99,8 +99,5 @@ fn setup(mut assets: ResMut<Assets<SineAudio>>, 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));
}

View file

@ -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());
}
}

View file

@ -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,
));

View file

@ -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::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
PlaybackSettings::LOOP.with_spatial(true),
));
let listener = SpatialListener::new(gap);

View file

@ -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::<AudioSource>(asset_server.load("sounds/Windless Slopes.ogg")),
PlaybackSettings::LOOP.with_spatial(true),
));
let listener = SpatialListener::new(gap);

View file

@ -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));
}
}

View file

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