Skip to content

Commit

Permalink
Migrate audio to required components (#15573)
Browse files Browse the repository at this point in the history
# 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")));
```
  • Loading branch information
Jondolf authored Oct 1, 2024
1 parent eb51b4c commit ed151e7
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 70 deletions.
42 changes: 40 additions & 2 deletions crates/bevy_audio/src/audio.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![expect(deprecated)]

use crate::{AudioSource, Decodable};
use bevy_asset::{Asset, Handle};
use bevy_derive::Deref;
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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(),
}
}
Expand Down
27 changes: 16 additions & 11 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
};

Expand Down Expand Up @@ -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,
)>();
}
}
}
Expand Down
18 changes: 10 additions & 8 deletions crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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,
//! ));
//! }
//! ```

Expand All @@ -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,
};
}

Expand All @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_audio/src/pitch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![expect(deprecated)]

use crate::{AudioSourceBundle, Decodable};
use bevy_asset::Asset;
use bevy_reflect::TypePath;
Expand Down Expand Up @@ -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>;
2 changes: 1 addition & 1 deletion crates/bevy_audio/src/sinks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
7 changes: 3 additions & 4 deletions examples/audio/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
));
}
5 changes: 1 addition & 4 deletions examples/audio/audio_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));
}
Expand Down
5 changes: 1 addition & 4 deletions examples/audio/decodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
8 changes: 4 additions & 4 deletions examples/audio/pitch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down
26 changes: 11 additions & 15 deletions examples/audio/soundtrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));
Expand Down
6 changes: 2 additions & 4 deletions examples/audio/spatial_audio_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 2 additions & 4 deletions examples/audio/spatial_audio_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 1 addition & 5 deletions examples/games/breakout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/mobile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit ed151e7

Please sign in to comment.