1use crate::prelude::Engine;
60use bevy::{
61 audio::{AudioSink, PlaybackMode, Volume},
62 prelude::*,
63};
64use std::{array::IntoIter, fmt::Debug};
65
66#[derive(Default)]
67#[doc(hidden)]
68pub struct AudioManagerPlugin;
70
71impl Plugin for AudioManagerPlugin {
72 fn build(&self, app: &mut bevy::prelude::App) {
73 app.add_systems(Update, queue_managed_audio_system);
74 }
75}
76
77#[derive(Default)]
82pub struct AudioManager {
83 sfx_queue: Vec<(String, f32)>,
84 music_queue: Vec<Option<(String, f32)>>,
85 playing: Option<Entity>,
86 music_playing: bool,
87}
88
89impl Debug for AudioManager {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 f.debug_struct("AudioManager")
92 .field("sfx_queue", &self.sfx_queue)
93 .field("music_queue", &self.music_queue)
94 .field("music_playing", &self.music_playing)
95 .finish()
96 }
97}
98
99impl AudioManager {
100 pub fn play_sfx<S: Into<String>>(&mut self, sfx: S, volume: f32) {
105 self.sfx_queue.push((sfx.into(), volume.clamp(0.0, 1.0)));
106 }
107 pub fn play_music<S: Into<String>>(&mut self, music: S, volume: f32) {
112 self.music_playing = true;
113 self.music_queue
114 .push(Some((music.into(), volume.clamp(0.0, 1.0))));
115 }
116 pub fn stop_music(&mut self) {
118 if self.music_playing {
119 self.music_playing = false;
120 self.music_queue.push(None);
121 }
122 }
123 pub fn music_playing(&self) -> bool {
125 self.music_playing
126 }
127}
128
129#[derive(Copy, Clone, Debug)]
130pub enum SfxPreset {
137 Click,
138 Confirmation1,
139 Confirmation2,
140 Congratulations,
141 Forcefield1,
142 Forcefield2,
143 Impact1,
144 Impact2,
145 Impact3,
146 Jingle1,
147 Jingle2,
148 Jingle3,
149 Minimize1,
150 Minimize2,
151 Switch1,
152 Switch2,
153 Tones1,
154 Tones2,
155}
156
157impl SfxPreset {
158 pub fn variant_iter() -> IntoIter<SfxPreset, 18> {
159 static SFX_PRESETS: [SfxPreset; 18] = [
160 SfxPreset::Click,
161 SfxPreset::Confirmation1,
162 SfxPreset::Confirmation2,
163 SfxPreset::Congratulations,
164 SfxPreset::Forcefield1,
165 SfxPreset::Forcefield2,
166 SfxPreset::Impact1,
167 SfxPreset::Impact2,
168 SfxPreset::Impact3,
169 SfxPreset::Jingle1,
170 SfxPreset::Jingle2,
171 SfxPreset::Jingle3,
172 SfxPreset::Minimize1,
173 SfxPreset::Minimize2,
174 SfxPreset::Switch1,
175 SfxPreset::Switch2,
176 SfxPreset::Tones1,
177 SfxPreset::Tones2,
178 ];
179 SFX_PRESETS.into_iter()
180 }
181}
182
183impl From<SfxPreset> for String {
184 fn from(sfx_preset: SfxPreset) -> Self {
185 match sfx_preset {
186 SfxPreset::Click => "sfx/click.ogg".into(),
187 SfxPreset::Confirmation1 => "sfx/confirmation1.ogg".into(),
188 SfxPreset::Confirmation2 => "sfx/confirmation2.ogg".into(),
189 SfxPreset::Congratulations => "sfx/congratulations.ogg".into(),
190 SfxPreset::Forcefield1 => "sfx/forcefield1.ogg".into(),
191 SfxPreset::Forcefield2 => "sfx/forcefield2.ogg".into(),
192 SfxPreset::Impact1 => "sfx/impact1.ogg".into(),
193 SfxPreset::Impact2 => "sfx/impact2.ogg".into(),
194 SfxPreset::Impact3 => "sfx/impact3.ogg".into(),
195 SfxPreset::Jingle1 => "sfx/jingle1.ogg".into(),
196 SfxPreset::Jingle2 => "sfx/jingle2.ogg".into(),
197 SfxPreset::Jingle3 => "sfx/jingle3.ogg".into(),
198 SfxPreset::Minimize1 => "sfx/minimize1.ogg".into(),
199 SfxPreset::Minimize2 => "sfx/minimize2.ogg".into(),
200 SfxPreset::Switch1 => "sfx/switch1.ogg".into(),
201 SfxPreset::Switch2 => "sfx/switch2.ogg".into(),
202 SfxPreset::Tones1 => "sfx/tones1.ogg".into(),
203 SfxPreset::Tones2 => "sfx/tones2.ogg".into(),
204 }
205 }
206}
207
208#[derive(Copy, Clone, Debug)]
215pub enum MusicPreset {
216 Classy8Bit,
217 MysteriousMagic,
218 WhimsicalPopsicle,
219}
220
221impl MusicPreset {
222 pub fn variant_iter() -> IntoIter<MusicPreset, 3> {
223 static MUSIC_PRESETS: [MusicPreset; 3] = [
224 MusicPreset::Classy8Bit,
225 MusicPreset::MysteriousMagic,
226 MusicPreset::WhimsicalPopsicle,
227 ];
228 MUSIC_PRESETS.into_iter()
229 }
230}
231
232impl From<MusicPreset> for String {
233 fn from(music_preset: MusicPreset) -> String {
234 match music_preset {
235 MusicPreset::Classy8Bit => "music/Classy 8-Bit.ogg".into(),
236 MusicPreset::MysteriousMagic => "music/Mysterious Magic.ogg".into(),
237 MusicPreset::WhimsicalPopsicle => "music/Whimsical Popsicle.ogg".into(),
238 }
239 }
240}
241
242#[derive(Component)]
243pub struct Music;
244
245#[doc(hidden)]
247pub fn queue_managed_audio_system(
248 mut commands: Commands,
249 music_query: Query<(Entity, &AudioSink), With<Music>>,
250 asset_server: Res<AssetServer>,
251 mut game_state: ResMut<Engine>,
252) {
253 for (sfx, volume) in game_state.audio_manager.sfx_queue.drain(..) {
254 commands.spawn((
255 AudioPlayer::<AudioSource>(asset_server.load(format!("audio/{}", sfx))),
256 PlaybackSettings {
257 mode: PlaybackMode::Despawn,
258 volume: Volume::Linear(volume),
259 ..Default::default()
260 },
261 ));
262 }
263 let last_music_item = game_state.audio_manager.music_queue.pop();
264 game_state.audio_manager.music_queue.clear();
265 if let Some(item) = last_music_item {
266 if let Ok((entity, music)) = music_query.single() {
268 music.stop();
269 commands.entity(entity).despawn();
270 }
271 if let Some((music, volume)) = item {
273 let entity = commands
274 .spawn((
275 AudioPlayer::<AudioSource>(asset_server.load(format!("audio/{}", music))),
276 PlaybackSettings {
277 mode: PlaybackMode::Loop,
278 volume: Volume::Linear(volume),
279 ..Default::default()
280 },
281 Music,
282 ))
283 .id();
284 game_state.audio_manager.playing = Some(entity);
285 }
286 }
287}