// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ // // This module manages sound effects and music. use bevy::prelude::*; use bevy::audio::{PlaybackMode, Volume}; use crate::prelude::*; use std::collections::HashMap; pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(Update, respawn_sinks.run_if(on_event::())); app.add_systems(Update, play_zoom_sfx); app.add_systems(Update, pause_all.run_if(on_event::())); app.add_systems(PostUpdate, play_sfx); app.add_systems(PostUpdate, update_music); app.add_event::(); app.add_event::(); app.add_event::(); app.add_event::(); app.insert_resource(ZoomTimer( Timer::from_seconds(0.09, TimerMode::Repeating))); } } #[derive(Resource)] pub struct ZoomTimer(Timer); const PATHS: &[(SfxType, Sfx, &str)] = &[ (SfxType::BGM, Sfx::BGM, "music/Aleksey Chistilin - Cinematic Cello.ogg"), (SfxType::LoopSfx, Sfx::ElectricMotor, "sounds/electricmotor.ogg"), (SfxType::LoopSfx, Sfx::Ion, "sounds/ion.ogg"), (SfxType::LoopSfx, Sfx::Rocket, "sounds/rocket.ogg"), (SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.ogg"), (SfxType::OneOff, Sfx::Achieve, "sounds/achieve.ogg"), (SfxType::OneOff, Sfx::Click, "sounds/click-button-140881-crop.ogg"), (SfxType::OneOff, Sfx::Connect, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Crash, "sounds/crash.ogg"), (SfxType::OneOff, Sfx::EnterVehicle, "sounds/bikestart.ogg"), (SfxType::OneOff, Sfx::IncomingChatMessage, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"), (SfxType::OneOff, Sfx::Switch, "sounds/typosonic-typing-192811-crop.ogg"), (SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"), (SfxType::OneOff, Sfx::Woosh, "sounds/woosh.ogg"), (SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"), ]; #[derive(Component, PartialEq, Hash, Eq, Copy, Clone)] pub enum Sfx { Achieve, BGM, Click, Connect, Crash, ElectricMotor, EnterVehicle, IncomingChatMessage, Ion, Ping, Rocket, Switch, Thruster, WakeUp, Woosh, Zoom, } pub fn str2sfx(sfx_label: &str) -> Sfx { return match sfx_label { "achieve" => Sfx::Achieve, "switch" => Sfx::Switch, "click" => Sfx::Click, "woosh" => Sfx::Woosh, "zoom" => Sfx::Zoom, "chat" => Sfx::IncomingChatMessage, "ping" => Sfx::Ping, "connect" => Sfx::Connect, "entervehicle" => Sfx::EnterVehicle, "crash" => Sfx::Ping, _ => Sfx::Click, }; } pub enum SfxType { BGM, LoopSfx, OneOff, } #[derive(Event)] pub struct PlaySfxEvent(pub Sfx); #[derive(Event)] pub struct PauseAllSfxEvent; #[derive(Event)] pub struct RespawnSinksEvent; #[derive(Event)] pub struct ToggleMusicEvent(); #[derive(Resource)] pub struct Sounds(HashMap>); pub fn setup( mut commands: Commands, asset_server: Res, mut ew_respawnsinks: EventWriter, ) { let mut map = HashMap::new(); for (_, sfx, path) in PATHS { let source = asset_server.load(*path); map.insert(*sfx, source.clone()); } commands.insert_resource(Sounds(map)); ew_respawnsinks.send(RespawnSinksEvent); } pub fn respawn_sinks( mut commands: Commands, asset_server: Res, settings: Res, q_audiosinks: Query, With)>, ) { for sink in &q_audiosinks { commands.entity(sink).despawn(); } for (sfxtype, sfx, path) in PATHS { let source = asset_server.load(*path); match sfxtype { SfxType::BGM => { commands.spawn(( *sfx, AudioBundle { source, settings: PlaybackSettings { mode: PlaybackMode::Loop, paused: settings.mute_music, ..default() }, }, )); }, SfxType::LoopSfx => { commands.spawn(( *sfx, AudioBundle { source, settings: PlaybackSettings { mode: PlaybackMode::Loop, volume: Volume::new(0.0), paused: true, ..default() }, }, )); }, SfxType::OneOff => () } } } pub fn play_sfx( mut commands: Commands, settings: Res, mut events_sfx: EventReader, sounds: Res, ) { if settings.mute_sfx && !events_sfx.is_empty() { events_sfx.clear(); } for sfx in events_sfx.read() { if let Some(source) = sounds.0.get(&sfx.0) { commands.spawn(AudioBundle { source: source.clone(), settings: PlaybackSettings::DESPAWN, }); } } } pub fn update_music( mut events: EventReader, q_audiosinks: Query<(&AudioSink, &Sfx)>, settings: Res, ) { if !events.is_empty() { events.clear(); for (bgm_sink, sfx) in &q_audiosinks { if *sfx != Sfx::BGM { continue; } if settings.mute_music { bgm_sink.pause(); } else { bgm_sink.play(); } } } } pub fn play_zoom_sfx( time: Res