// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ // // This module manages sound effects and music. use bevy::prelude::*; use bevy::audio::{PlaybackMode, Volume}; use crate::var; const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg"; const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg"; const ASSET_INCOMING_MESSAGE: &str = "sounds/connect.ogg"; const ASSET_PING: &str = "sounds/connect.ogg"; const ASSET_CONNECT: &str = "sounds/connect.ogg"; const ASSET_BGM: &str = "music/Aleksey Chistilin - Cinematic Cello.ogg"; const ASSET_THRUSTER: &str = "sounds/thruster.ogg"; const ASSET_ROCKET: &str = "sounds/rocket.ogg"; const ASSET_ION: &str = "sounds/ion.ogg"; const ASSET_WAKEUP: &str = "sounds/wakeup.ogg"; const ASSET_BIKESTART: &str = "sounds/bikestart.ogg"; const ASSET_CRASH: &str = "sounds/crash.ogg"; const ASSET_ELECTRICMOTOR: &str = "sounds/electricmotor.ogg"; pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(Update, toggle_bgm); app.add_systems(PostUpdate, play_sfx); app.add_systems(PostUpdate, update_music); app.add_event::(); app.add_event::(); } } pub enum Sfx { IncomingChatMessage, Click, Switch, Ping, Connect, EnterVehicle, Crash, WakeUp, None, } #[derive(Event)] pub struct PlaySfxEvent(pub Sfx); #[derive(Event)] pub struct ToggleMusicEvent(); #[derive(Component)] pub struct ComponentBGM; #[derive(Component)] pub struct ComponentThrusterSound; #[derive(Component)] pub struct ComponentRocketSound; #[derive(Component)] pub struct ComponentIonSound; #[derive(Component)] pub struct ComponentElectricMotorSound; #[derive(Component)] struct SoundBGM(Handle); #[derive(Resource)] pub struct SoundClick(Handle); #[derive(Resource)] pub struct SoundSwitch(Handle); #[derive(Resource)] pub struct SoundIncomingMessage(Handle); #[derive(Resource)] pub struct SoundPing(Handle); #[derive(Resource)] pub struct SoundConnect(Handle); #[derive(Resource)] pub struct SoundBikeStart(Handle); #[derive(Resource)] pub struct SoundCrash(Handle); #[derive(Resource)] pub struct SoundWakeUp(Handle); pub fn setup( mut commands: Commands, settings: Res, asset_server: Res, ) { commands.spawn(( ComponentBGM, AudioBundle { source: SoundBGM(asset_server.load(ASSET_BGM)).0.clone(), settings: PlaybackSettings { mode: PlaybackMode::Loop, paused: settings.hud_active || settings.mute_music, ..default() }, }, )); commands.spawn(( ComponentThrusterSound, AudioBundle { source: asset_server.load(ASSET_THRUSTER), settings: PlaybackSettings { mode: PlaybackMode::Loop, volume: Volume::new(0.0), paused: true, ..default() }, }, )); commands.spawn(( ComponentRocketSound, AudioBundle { source: asset_server.load(ASSET_ROCKET), settings: PlaybackSettings { mode: PlaybackMode::Loop, volume: Volume::new(0.0), paused: true, ..default() }, }, )); commands.spawn(( ComponentIonSound, AudioBundle { source: asset_server.load(ASSET_ION), settings: PlaybackSettings { mode: PlaybackMode::Loop, volume: Volume::new(0.0), paused: true, ..default() }, }, )); commands.spawn(( ComponentElectricMotorSound, AudioBundle { source: asset_server.load(ASSET_ELECTRICMOTOR), settings: PlaybackSettings { mode: PlaybackMode::Loop, volume: Volume::new(0.5), paused: true, ..default() }, }, )); commands.insert_resource(SoundClick(asset_server.load(ASSET_CLICK))); commands.insert_resource(SoundSwitch(asset_server.load(ASSET_SWITCH))); commands.insert_resource(SoundIncomingMessage(asset_server.load(ASSET_INCOMING_MESSAGE))); commands.insert_resource(SoundPing(asset_server.load(ASSET_PING))); commands.insert_resource(SoundConnect(asset_server.load(ASSET_CONNECT))); commands.insert_resource(SoundBikeStart(asset_server.load(ASSET_BIKESTART))); commands.insert_resource(SoundCrash(asset_server.load(ASSET_CRASH))); commands.insert_resource(SoundWakeUp(asset_server.load(ASSET_WAKEUP))); } pub fn toggle_bgm( keyboard_input: Res>, mut evwriter_toggle: EventWriter, mut evwriter_sfx: EventWriter, mut settings: ResMut, ) { if keyboard_input.just_pressed(settings.key_toggle_music) { settings.mute_music ^= true; evwriter_sfx.send(PlaySfxEvent(Sfx::Click)); evwriter_toggle.send(ToggleMusicEvent()); } if keyboard_input.just_pressed(settings.key_toggle_sfx) { settings.mute_sfx ^= true; evwriter_sfx.send(PlaySfxEvent(Sfx::Click)); evwriter_toggle.send(ToggleMusicEvent()); } } pub fn play_sfx( mut commands: Commands, settings: Res, mut events_sfx: EventReader, sound_click: Res, sound_switch: Res, sound_incoming_message: Res, sound_ping: Res, sound_connect: Res, sound_bikestart: Res, sound_crash: Res, sound_wakeup: Res, ) { if settings.mute_sfx && !events_sfx.is_empty() { events_sfx.clear(); } for sfx in events_sfx.read() { match sfx.0 { Sfx::None => { continue; } _ => {} } commands.spawn(AudioBundle { source: match sfx.0 { Sfx::Switch => sound_switch.0.clone(), Sfx::Click => sound_click.0.clone(), Sfx::IncomingChatMessage => sound_incoming_message.0.clone(), Sfx::Ping => sound_ping.0.clone(), Sfx::Connect => sound_connect.0.clone(), Sfx::EnterVehicle => sound_bikestart.0.clone(), Sfx::Crash => sound_crash.0.clone(), Sfx::WakeUp => sound_wakeup.0.clone(), Sfx::None => sound_ping.0.clone(), }, settings: PlaybackSettings::DESPAWN, }); } } pub fn str2sfx(sfx_label: &str) -> Sfx { return match sfx_label { "switch" => Sfx::Switch, "click" => Sfx::Click, "chat" => Sfx::IncomingChatMessage, "ping" => Sfx::Ping, "connect" => Sfx::Connect, "entervehicle" => Sfx::EnterVehicle, "crash" => Sfx::Crash, _ => Sfx::None, }; } pub fn update_music( mut events: EventReader, bgm_controller: Query<&AudioSink, With>, settings: Res, ) { if !events.is_empty() { events.clear(); if let Ok(bgm_sink) = bgm_controller.get_single() { if settings.mute_music { bgm_sink.pause(); } else { bgm_sink.play(); } } } }