
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
248 lines
8.2 KiB
Rust
248 lines
8.2 KiB
Rust
use crate::{AudioSource, Decodable, Volume};
|
|
use bevy_asset::{Asset, Handle};
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_math::Vec3;
|
|
use bevy_reflect::prelude::*;
|
|
|
|
/// The way Bevy manages the sound playback.
|
|
#[derive(Debug, Clone, Copy, Reflect)]
|
|
#[reflect(Clone)]
|
|
pub enum PlaybackMode {
|
|
/// Play the sound once. Do nothing when it ends.
|
|
///
|
|
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
|
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
|
///
|
|
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
|
/// added again.
|
|
Once,
|
|
/// Repeat the sound forever.
|
|
Loop,
|
|
/// Despawn the entity and its children when the sound finishes playing.
|
|
Despawn,
|
|
/// Remove the audio components from the entity, when the sound finishes playing.
|
|
Remove,
|
|
}
|
|
|
|
/// Initial settings to be used when audio starts playing.
|
|
///
|
|
/// If you would like to control the audio while it is playing, query for the
|
|
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink]
|
|
/// components. Changes to this component will *not* be applied to already-playing audio.
|
|
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
|
#[reflect(Clone, Default, Component, Debug)]
|
|
pub struct PlaybackSettings {
|
|
/// The desired playback behavior.
|
|
pub mode: PlaybackMode,
|
|
/// Volume to play at.
|
|
pub volume: Volume,
|
|
/// Speed to play at.
|
|
pub speed: f32,
|
|
/// Create the sink in paused state.
|
|
/// Useful for "deferred playback", if you want to prepare
|
|
/// the entity, but hear the sound later.
|
|
pub paused: bool,
|
|
/// Whether to create the sink in muted state or not.
|
|
///
|
|
/// This is useful for audio that should be initially muted. You can still
|
|
/// set the initial volume and it is applied when the audio is unmuted.
|
|
pub muted: bool,
|
|
/// Enables spatial audio for this source.
|
|
///
|
|
/// See also: [`SpatialListener`].
|
|
///
|
|
/// 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<SpatialScale>,
|
|
}
|
|
|
|
impl Default for PlaybackSettings {
|
|
fn default() -> Self {
|
|
Self::ONCE
|
|
}
|
|
}
|
|
|
|
impl PlaybackSettings {
|
|
/// Will play the associated audio source once.
|
|
///
|
|
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
|
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
|
///
|
|
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
|
/// added again.
|
|
pub const ONCE: PlaybackSettings = PlaybackSettings {
|
|
mode: PlaybackMode::Once,
|
|
volume: Volume::Linear(1.0),
|
|
speed: 1.0,
|
|
paused: false,
|
|
muted: false,
|
|
spatial: false,
|
|
spatial_scale: None,
|
|
};
|
|
|
|
/// Will play the associated audio source in a loop.
|
|
pub const LOOP: PlaybackSettings = PlaybackSettings {
|
|
mode: PlaybackMode::Loop,
|
|
..PlaybackSettings::ONCE
|
|
};
|
|
|
|
/// Will play the associated audio source once and despawn the entity afterwards.
|
|
pub const DESPAWN: PlaybackSettings = PlaybackSettings {
|
|
mode: PlaybackMode::Despawn,
|
|
..PlaybackSettings::ONCE
|
|
};
|
|
|
|
/// Will play the associated audio source once and remove the audio components afterwards.
|
|
pub const REMOVE: PlaybackSettings = PlaybackSettings {
|
|
mode: PlaybackMode::Remove,
|
|
..PlaybackSettings::ONCE
|
|
};
|
|
|
|
/// Helper to start in a paused state.
|
|
pub const fn paused(mut self) -> Self {
|
|
self.paused = true;
|
|
self
|
|
}
|
|
|
|
/// Helper to start muted.
|
|
pub const fn muted(mut self) -> Self {
|
|
self.muted = true;
|
|
self
|
|
}
|
|
|
|
/// Helper to set the volume from start of playback.
|
|
pub const fn with_volume(mut self, volume: Volume) -> Self {
|
|
self.volume = volume;
|
|
self
|
|
}
|
|
|
|
/// Helper to set the speed from start of playback.
|
|
pub const fn with_speed(mut self, speed: f32) -> Self {
|
|
self.speed = speed;
|
|
self
|
|
}
|
|
|
|
/// Helper to enable or disable spatial audio.
|
|
pub const fn with_spatial(mut self, spatial: bool) -> Self {
|
|
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.
|
|
///
|
|
/// This must be accompanied by `Transform` and `GlobalTransform`.
|
|
/// Only one entity with a `SpatialListener` should be present at any given time.
|
|
#[derive(Component, Clone, Debug, Reflect)]
|
|
#[reflect(Clone, Default, Component, Debug)]
|
|
pub struct SpatialListener {
|
|
/// Left ear position relative to the `GlobalTransform`.
|
|
pub left_ear_offset: Vec3,
|
|
/// Right ear position relative to the `GlobalTransform`.
|
|
pub right_ear_offset: Vec3,
|
|
}
|
|
|
|
impl Default for SpatialListener {
|
|
fn default() -> Self {
|
|
Self::new(4.)
|
|
}
|
|
}
|
|
|
|
impl SpatialListener {
|
|
/// Creates a new `SpatialListener` component.
|
|
///
|
|
/// `gap` is the distance between the left and right "ears" of the listener. Ears are
|
|
/// positioned on the x axis.
|
|
pub fn new(gap: f32) -> Self {
|
|
SpatialListener {
|
|
left_ear_offset: Vec3::X * gap / -2.0,
|
|
right_ear_offset: Vec3::X * gap / 2.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A scale factor applied to the positions of audio sources and listeners for
|
|
/// spatial audio.
|
|
///
|
|
/// Default is `Vec3::ONE`.
|
|
#[derive(Clone, Copy, Debug, Reflect)]
|
|
#[reflect(Clone, Default)]
|
|
pub struct SpatialScale(pub Vec3);
|
|
|
|
impl SpatialScale {
|
|
/// Create a new `SpatialScale` with the same value for all 3 dimensions.
|
|
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 const fn new_2d(scale: f32) -> Self {
|
|
Self(Vec3::new(scale, scale, 0.0))
|
|
}
|
|
}
|
|
|
|
impl Default for SpatialScale {
|
|
fn default() -> Self {
|
|
Self(Vec3::ONE)
|
|
}
|
|
}
|
|
|
|
/// 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, Default, Clone)]
|
|
pub struct DefaultSpatialScale(pub SpatialScale);
|
|
|
|
/// 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, Clone)]
|
|
#[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())
|
|
}
|
|
}
|
|
|
|
impl AudioPlayer<AudioSource> {
|
|
/// Creates a new [`AudioPlayer`] with the given [`Handle<AudioSource>`].
|
|
///
|
|
/// For convenience reasons, this hard-codes the [`AudioSource`] type. If you want to
|
|
/// initialize an [`AudioPlayer`] with a different type, just initialize it directly using normal
|
|
/// tuple struct syntax.
|
|
pub fn new(source: Handle<AudioSource>) -> Self {
|
|
Self(source)
|
|
}
|
|
}
|