bevy_kira_components/
spatial.rs1use bevy::diagnostic::{Diagnostic, DiagnosticPath, RegisterDiagnostic};
3use bevy::prelude::*;
4
5use kira::spatial::emitter::{EmitterDistances, EmitterHandle, EmitterSettings};
6use kira::spatial::listener::ListenerHandle;
7use kira::spatial::scene::{SpatialSceneHandle, SpatialSceneSettings};
8use kira::tween::{Easing, Tween};
9
10use crate::{AudioPlaybackSet, AudioSourceSetup, AudioWorld, InternalAudioMarker};
11
12#[doc(hidden)]
13#[allow(missing_docs)]
14pub mod prelude {
15 pub use super::{AudioListener, SpatialEmitter, SpatialWorld};
16}
17
18pub(crate) struct SpatialAudioPlugin;
22
23impl Plugin for SpatialAudioPlugin {
24 fn build(&self, app: &mut App) {
25 app.init_resource::<SpatialWorld>()
26 .add_plugins(SpatialDiagnosticsPlugin)
27 .add_systems(
28 PreUpdate,
29 (add_listeners, add_emitters)
30 .in_set(AudioPlaybackSet::Setup)
31 .before(AudioSourceSetup),
32 )
33 .add_systems(
34 PostUpdate,
35 (update_listeners, update_emitters).in_set(AudioPlaybackSet::Update),
36 );
37 }
38}
39
40#[derive(Component)]
43pub struct AudioListener;
44
45#[derive(Component)]
47pub(crate) struct SpatialListenerHandle(ListenerHandle);
48
49#[derive(Component)]
55pub struct SpatialEmitter {
56 pub attenuation: Option<Easing>,
59 pub enable_spatialization: bool,
61 pub distances: EmitterDistances,
64}
65
66impl Default for SpatialEmitter {
67 fn default() -> Self {
68 Self {
69 attenuation: None,
70 enable_spatialization: true,
71 distances: EmitterDistances::default(),
72 }
73 }
74}
75
76#[derive(Component)]
78pub(crate) struct SpatialEmitterHandle(pub(crate) EmitterHandle);
79
80#[derive(Resource)]
82pub struct SpatialWorld {
83 pub(crate) spatial_handle: SpatialSceneHandle,
84}
85
86impl FromWorld for SpatialWorld {
87 fn from_world(world: &mut World) -> Self {
88 let settings = world
89 .remove_non_send_resource::<SpatialSceneSettings>()
90 .unwrap_or_default();
91 let mut audio_world = world.resource_mut::<AudioWorld>();
92 let spatial_handle = audio_world
93 .audio_manager
94 .add_spatial_scene(settings)
95 .expect("Cannot create audio spatial world");
96 Self { spatial_handle }
97 }
98}
99
100fn add_listeners(
101 mut commands: Commands,
102 mut spatial_world: ResMut<SpatialWorld>,
103 q: Query<(Entity, &GlobalTransform), Added<AudioListener>>,
104) {
105 for (entity, global_transform) in &q {
106 let (_, quat, position) = global_transform.to_scale_rotation_translation();
107 let listener = spatial_world
108 .spatial_handle
109 .add_listener(position, quat, default())
110 .unwrap();
111 debug!("Add listener to {entity:?}");
112 commands
113 .entity(entity)
114 .insert(SpatialListenerHandle(listener));
115 }
116}
117
118fn add_emitters(
119 mut commands: Commands,
120 mut spatial_world: ResMut<SpatialWorld>,
121 q: Query<(Entity, &GlobalTransform, &SpatialEmitter), Added<InternalAudioMarker>>,
122) {
123 for (entity, global_transform, spatial_emitter) in &q {
124 let result = spatial_world.spatial_handle.add_emitter(
125 global_transform.translation(),
126 EmitterSettings::default()
127 .attenuation_function(spatial_emitter.attenuation)
128 .enable_spatialization(spatial_emitter.enable_spatialization)
129 .distances(spatial_emitter.distances)
130 .persist_until_sounds_finish(true),
131 );
132 debug!("Add emitter to {entity:?}");
133 match result {
134 Ok(emitter) => {
135 commands
136 .entity(entity)
137 .insert(SpatialEmitterHandle(emitter));
138 }
139 Err(err) => {
140 error!("Cannot create spatial audio emitter for entity {entity:?}: {err}");
141 }
142 }
143 }
144}
145
146fn update_listeners(mut q: Query<(&mut SpatialListenerHandle, &GlobalTransform)>) {
147 for (mut listener, global_transform) in &mut q {
148 let (_, quat, position) = global_transform.to_scale_rotation_translation();
149 listener.0.set_position(position, Tween::default()).unwrap();
150 listener.0.set_orientation(quat, Tween::default()).unwrap();
151 }
152}
153
154fn update_emitters(mut q: Query<(Entity, &mut SpatialEmitterHandle, &GlobalTransform)>) {
155 for (entity, mut emitter, global_transform) in &mut q {
156 let position = global_transform.translation();
157 match emitter.0.set_position(position, Tween::default()) {
158 Ok(_) => {}
159 Err(err) => {
160 error!("Cannot set spatial audio position for entity {entity:?}: {err}");
161 }
162 }
163 }
164}
165
166pub const SPATIAL_EMITTERS: DiagnosticPath = DiagnosticPath::const_new("kira::spatial::emitters");
168pub const SPATIAL_LISTENERS: DiagnosticPath = DiagnosticPath::const_new("kira::spatial::listeners");
170
171struct SpatialDiagnosticsPlugin;
172
173impl Plugin for SpatialDiagnosticsPlugin {
174 fn build(&self, app: &mut App) {
175 app.register_diagnostic(Diagnostic::new(SPATIAL_EMITTERS).with_suffix(" emitters"))
176 .register_diagnostic(Diagnostic::new(SPATIAL_LISTENERS).with_suffix(" listeners"));
177 }
178}