diff --git a/.gitattributes b/.gitattributes index f872481a..a725c82c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ assets/audio/**/*.mp3 filter=lfs diff=lfs merge=lfs -text +assets/audio/**/*.ogg filter=lfs diff=lfs merge=lfs -text assets/fonts/**/*.ttf filter=lfs diff=lfs merge=lfs -text assets/maps/* filter=lfs diff=lfs merge=lfs -text assets/models/* filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index 27cc54f0..c8b04d35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -669,6 +669,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-arena" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5450eca8ce5abcfd5520727e975ebab30ccca96030550406b0ca718b224ead10" + [[package]] name = "atomic-polyfill" version = "0.1.11" @@ -1076,6 +1082,19 @@ dependencies = [ "bevy_winit", ] +[[package]] +name = "bevy_kira_audio" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc50583e632d0173437cb618077f957c1eb7a9eef9bce107fbf9f90b22e4f84" +dependencies = [ + "anyhow", + "bevy", + "kira", + "parking_lot 0.12.1", + "thiserror", +] + [[package]] name = "bevy_log" version = "0.11.0" @@ -2153,8 +2172,11 @@ name = "de_audio" version = "0.1.0-dev" dependencies = [ "bevy", + "bevy_kira_audio", + "de_camera", "de_conf", "de_core", + "enum-map", "iyes_progress", ] @@ -2188,6 +2210,7 @@ name = "de_combat" version = "0.1.0-dev" dependencies = [ "bevy", + "de_audio", "de_behaviour", "de_core", "de_index", @@ -2246,6 +2269,7 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.8.3", "bevy", + "de_audio", "de_core", "de_index", "de_objects", @@ -2262,6 +2286,7 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.8.3", "bevy", + "de_audio", "de_behaviour", "de_camera", "de_combat", @@ -2314,6 +2339,7 @@ name = "de_game" version = "0.1.0-dev" dependencies = [ "bevy", + "bevy_kira_audio", "de_audio", "de_behaviour", "de_camera", @@ -2576,6 +2602,7 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.8.3", "bevy", + "de_audio", "de_core", "de_energy", "de_index", @@ -3135,6 +3162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" dependencies = [ "bytemuck", + "mint", "serde", ] @@ -3773,6 +3801,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "kira" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ce67549ab39d620131643a37f60b520ec4ae4450dc230f4c6296d3533272a3" +dependencies = [ + "atomic-arena", + "cpal", + "glam", + "mint", + "ringbuf", + "symphonia", +] + [[package]] name = "kqueue" version = "1.0.7" @@ -3829,17 +3871,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder", - "ogg", - "tinyvec", -] - [[package]] name = "libc" version = "0.2.147" @@ -4039,6 +4070,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mint" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" + [[package]] name = "mio" version = "0.8.8" @@ -4469,15 +4506,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ogg" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" -dependencies = [ - "byteorder", -] - [[package]] name = "once_cell" version = "1.18.0" @@ -5121,6 +5149,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ringbuf" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "robust" version = "0.2.3" @@ -5134,8 +5171,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" dependencies = [ "cpal", - "lewton", - "symphonia", ] [[package]] @@ -5699,7 +5734,9 @@ checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" dependencies = [ "lazy_static", "symphonia-bundle-mp3", + "symphonia-codec-vorbis", "symphonia-core", + "symphonia-format-ogg", "symphonia-metadata", ] @@ -5716,6 +5753,17 @@ dependencies = [ "symphonia-metadata", ] +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3953397e3506aa01350c4205817e4f95b58d476877a42f0458d07b665749e203" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-core" version = "0.5.3" @@ -5729,6 +5777,18 @@ dependencies = [ "log", ] +[[package]] +name = "symphonia-format-ogg" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf1a00ccd11452d44048a0368828040f778ae650418dbd9d8765b7ee2574c8d" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-metadata" version = "0.5.3" @@ -5741,6 +5801,16 @@ dependencies = [ "symphonia-core", ] +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a450ca645b80d69aff8b35576cbfdc7f20940b29998202aab910045714c951f8" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 4bd552db..6fb2d967 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ de_terrain.workspace = true # Other bevy.workspace = true +bevy_kira_audio.workspace = true tracing.workspace = true [workspace] @@ -111,7 +112,7 @@ assert_cmd = "2.0.10" async-compat = "0.2.1" async-std = "1.11" async-tar = "0.4.2" -bevy = { version = "0.11", features = ["mp3"] } +bevy_kira_audio = { version = "0.16.0", features = ["mp3"] } bincode = "2.0.0-rc.3" chrono = "0.4.24" clap = { version = "4.0", features = ["derive"] } @@ -154,3 +155,33 @@ tracing-subscriber = { version = "0.3.17", features = ["json"] } trybuild = "1.0.80" url = { version = "2.3.1", features = ["serde"] } urlencoding = "2.1.2" + +[workspace.dependencies.bevy] +version = "0.11" +default-features = false +features = [ + "animation", + "bevy_asset", + "bevy_gilrs", + "bevy_scene", + "bevy_winit", + "bevy_core_pipeline", + "bevy_pbr", + "bevy_gltf", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_ui", + "multi-threaded", + "png", + "hdr", + "ktx2", + "zstd", + "x11", + "filesystem_watcher", + "bevy_gizmos", + "android_shared_stdcxx", + "tonemapping_luts", + "default_font", + "webgl2", +] diff --git a/assets/audio/sounds/construct.ogg b/assets/audio/sounds/construct.ogg new file mode 100644 index 00000000..cb0ab916 --- /dev/null +++ b/assets/audio/sounds/construct.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ff3c63efcbee88d0a1dc2beb0f69dd54c43f23ac7e86377471b7974ef450353 +size 79121 diff --git a/assets/audio/sounds/destruction_building.ogg b/assets/audio/sounds/destruction_building.ogg new file mode 100644 index 00000000..360368da --- /dev/null +++ b/assets/audio/sounds/destruction_building.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a2db392df94fa3d31a8649339ec2f1454177582fd704b185c2d1061e5aefa72 +size 164193 diff --git a/assets/audio/sounds/destruction_unit.ogg b/assets/audio/sounds/destruction_unit.ogg new file mode 100644 index 00000000..795d1b1e --- /dev/null +++ b/assets/audio/sounds/destruction_unit.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc242ad9851b0fccd8584ccd548b29d96a4920fcbd0d9bdd3525963458efdfac +size 74811 diff --git a/assets/audio/sounds/laser.ogg b/assets/audio/sounds/laser.ogg new file mode 100644 index 00000000..c63630ed --- /dev/null +++ b/assets/audio/sounds/laser.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c5eb8c4e70d23db38f64bc0e118ae694a2bfc39241e146ed15162e1dc62f4fc +size 69940 diff --git a/assets/audio/sounds/manufacture.ogg b/assets/audio/sounds/manufacture.ogg new file mode 100644 index 00000000..77db5630 --- /dev/null +++ b/assets/audio/sounds/manufacture.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fc7b0e1b41ec37ef9d136f8f0c376ff9d806ccfac1e36c3ef5d2cac2a5fa1f9 +size 50650 diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 7fa6c924..8b233ae8 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -13,9 +13,12 @@ categories.workspace = true [dependencies] # DE +de_camera.workspace = true de_conf.workspace = true de_core.workspace = true # Other bevy.workspace = true +bevy_kira_audio.workspace = true +enum-map.workspace = true iyes_progress.workspace = true diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs index e66d7d03..8e6a53ed 100644 --- a/crates/audio/src/lib.rs +++ b/crates/audio/src/lib.rs @@ -1,13 +1,17 @@ use bevy::{app::PluginGroupBuilder, prelude::PluginGroup}; use crate::music::MusicPlugin; +use crate::spatial::SpatialSoundPlugin; mod music; +pub mod spatial; pub struct AudioPluginGroup; impl PluginGroup for AudioPluginGroup { fn build(self) -> PluginGroupBuilder { - PluginGroupBuilder::start::().add(MusicPlugin) + PluginGroupBuilder::start::() + .add(MusicPlugin) + .add(SpatialSoundPlugin) } } diff --git a/crates/audio/src/music.rs b/crates/audio/src/music.rs index 9fb02ad9..d1aa7630 100644 --- a/crates/audio/src/music.rs +++ b/crates/audio/src/music.rs @@ -1,4 +1,8 @@ -use bevy::{asset::LoadState, audio::Volume, prelude::*}; +use bevy::{asset::LoadState, prelude::*}; +use bevy_kira_audio::{ + prelude::{Audio as KAudio, AudioSource as KAudioSource, Volume}, + AudioControl, +}; use de_conf::Configuration; use de_core::state::AppState; use iyes_progress::prelude::*; @@ -17,7 +21,7 @@ impl Plugin for MusicPlugin { } #[derive(Resource)] -struct Tracks(Handle); +struct Tracks(Handle); fn setup(mut commands: Commands, server: Res) { commands.insert_resource(Tracks(server.load("audio/music/menu_loop.mp3"))); @@ -31,14 +35,13 @@ fn load(server: Res, tracks: Res) -> Progress { } } -fn start(mut commands: Commands, tracks: Res, config: Res) { +fn start(audio: Res, tracks: Res, config: Res) { if !config.audio().music_enabled() { return; } - let volume = Volume::new_relative(config.audio().music_volume()); - commands.spawn(AudioBundle { - source: tracks.0.clone(), - settings: PlaybackSettings::LOOP.with_volume(volume), - }); + audio + .play(tracks.0.clone()) + .looped() + .with_volume(Volume::Amplitude(config.audio().music_volume().into())); } diff --git a/crates/audio/src/spatial.rs b/crates/audio/src/spatial.rs new file mode 100644 index 00000000..3bb77376 --- /dev/null +++ b/crates/audio/src/spatial.rs @@ -0,0 +1,183 @@ +use std::f32::consts::PI; + +use bevy::{asset::LoadState, prelude::*}; +use bevy_kira_audio::{ + prelude::{Audio as KAudio, AudioSource as KAudioSource}, + AudioControl, AudioInstance, +}; +use de_camera::CameraFocus; +use de_core::{gamestate::GameState, state::AppState}; +use enum_map::{enum_map, Enum, EnumMap}; +use iyes_progress::{Progress, ProgressSystem}; + +// Angle occlusion parameters + +/// The angle is calculated from a point this many units behind the camera +const SOUND_VIEW_ANGLE_OFFSET: f32 = 32.; + +pub(crate) struct SpatialSoundPlugin; + +impl Plugin for SpatialSoundPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_systems(OnEnter(AppState::AppLoading), setup) + .add_systems( + Update, + load.track_progress().run_if(in_state(AppState::AppLoading)), + ) + .add_systems( + PostUpdate, + ( + play.run_if(on_event::()), + update_spatial.after(play), + ) + .run_if(in_state(GameState::Playing)), + ); + } +} + +#[derive(Clone, Copy, Enum)] +pub enum Sound { + Construct, + Manufacture, + DestroyBuilding, + DestroyUnit, + LaserFire, +} + +#[derive(Event)] +pub struct PlaySpatialAudioEvent { + pub sound: Sound, + pub position: Vec3, +} + +impl PlaySpatialAudioEvent { + pub fn new(sound: Sound, position: Vec3) -> Self { + Self { sound, position } + } +} + +#[derive(Resource)] +struct Sounds(EnumMap>); + +#[derive(Component, Default)] +struct SpatialSound; + +fn setup(mut commands: Commands, server: Res) { + use Sound::*; + commands.insert_resource(Sounds(enum_map! { + Construct => server.load("audio/sounds/construct.ogg"), + Manufacture => server.load("audio/sounds/manufacture.ogg"), + DestroyBuilding => server.load("audio/sounds/destruction_building.ogg"), + DestroyUnit => server.load("audio/sounds/destruction_unit.ogg"), + LaserFire => server.load("audio/sounds/laser.ogg"), + })); +} + +fn load(server: Res, sounds: Res) -> Progress { + Progress { + done: sounds + .0 + .values() + .map(|handle| match server.get_load_state(handle) { + LoadState::Loaded => 1, + LoadState::NotLoaded | LoadState::Loading => 0, + _ => panic!("Unexpected loading state."), + }) + .sum(), + total: sounds + .0 + .len() + .try_into() + .expect("Trying to load an ungodly number of sounds"), + } +} + +fn calculate_volume_and_pan( + camera: &GlobalTransform, + focus: &CameraFocus, + sound_position: Vec3, +) -> (f64, f64) { + let cam_right = camera.right(); + let sound_dir = (sound_position - camera.translation()).normalize(); + let pan = cam_right.dot(sound_dir) * 0.5 + 0.5; + + // Simulates sounds becoming quieter when further away + let attenuation_factor = { + let distance_from_camera_squared = camera.translation().distance_squared(sound_position); + // Anything closer than 70% of zoom distance is at full volume. + let min_distance_squared = (0.7 * focus.distance().inner()).powi(2); + + min_distance_squared / distance_from_camera_squared + }; + + // Silences sounds whose sources are not in view + let occlusion_factor = { + // Volume is 1 from 0-25.5 degrees from the view direction, then linearly goes to 0 from 25.5-45 degrees. + // Angle is calculated from a point slightly behind the camera. + let camera_offset = camera.translation() + camera.back() * SOUND_VIEW_ANGLE_OFFSET; + let angle = (sound_position - camera_offset).angle_between(camera.forward()); + + // Slope parameter of linear function (-1 unit in 25.5 degrees or pi/8) + const SLOPE: f32 = -1.0 / (PI / 8.); + // Constant parameter of linear function + const CONSTANT: f32 = 2.0; + // Let's limit this to non-negative to avoid the weirdness that + // occurs if both factors end up being negative at some point + (SLOPE * angle + CONSTANT).max(0.0) + }; + + let volume = (attenuation_factor * occlusion_factor).clamp(0., 1.); + + (volume as f64, pan as f64) +} + +fn play( + mut commands: Commands, + camera: Query<&GlobalTransform, With>, + focus: Res, + audio: Res, + sounds: Res, + mut play_events: EventReader, +) { + let camera = camera.single(); + + for PlaySpatialAudioEvent { sound, position } in &mut play_events { + let (volume, pan) = calculate_volume_and_pan(camera, &focus, *position); + let handle = audio + .play(sounds.0[*sound].clone()) + .with_volume(volume) + .with_panning(pan) + .handle(); + + commands.spawn(( + TransformBundle::from_transform(Transform::from_translation(*position)), + handle, + SpatialSound, + )); + } +} + +type InitializedSound<'s> = (Entity, &'s Handle, &'s GlobalTransform); + +fn update_spatial( + mut commands: Commands, + spatial_audios: Query>, + camera: Query<&GlobalTransform, With>, + focus: Res, + mut audio_instances: ResMut>, +) { + let camera = camera.single(); + + for (entity, audio, transform) in &spatial_audios { + let Some(audio_instance) = audio_instances.get_mut(audio) else { + commands.entity(entity).despawn_recursive(); + continue; + }; + + let (volume, pan) = calculate_volume_and_pan(camera, &focus, transform.translation()); + + audio_instance.set_volume(volume, default()); + audio_instance.set_panning(pan, default()); + } +} diff --git a/crates/camera/src/camera.rs b/crates/camera/src/camera.rs index 1eeb055e..509d0391 100644 --- a/crates/camera/src/camera.rs +++ b/crates/camera/src/camera.rs @@ -200,17 +200,17 @@ impl ZoomCameraEvent { } #[derive(Resource)] -struct CameraFocus { +pub struct CameraFocus { point: Vec3, distance: Metre, } impl CameraFocus { - fn point(&self) -> Vec3 { + pub fn point(&self) -> Vec3 { self.point } - fn distance(&self) -> Metre { + pub fn distance(&self) -> Metre { self.distance } diff --git a/crates/camera/src/lib.rs b/crates/camera/src/lib.rs index 14b35cc9..e654adf0 100644 --- a/crates/camera/src/lib.rs +++ b/crates/camera/src/lib.rs @@ -1,8 +1,8 @@ use bevy::{app::PluginGroupBuilder, prelude::*}; use camera::CameraPlugin; pub use camera::{ - CameraSet, MoveCameraHorizontallyEvent, MoveFocusEvent, RotateCameraEvent, TiltCameraEvent, - ZoomCameraEvent, + CameraFocus, CameraSet, MoveCameraHorizontallyEvent, MoveFocusEvent, RotateCameraEvent, + TiltCameraEvent, ZoomCameraEvent, }; use distance::DistancePlugin; pub use distance::{CameraDistance, DistanceSet}; diff --git a/crates/combat/Cargo.toml b/crates/combat/Cargo.toml index 4115a995..42c14ba8 100644 --- a/crates/combat/Cargo.toml +++ b/crates/combat/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true [dependencies] # DE +de_audio.workspace = true de_core.workspace = true de_objects.workspace = true de_terrain.workspace = true diff --git a/crates/combat/src/laser.rs b/crates/combat/src/laser.rs index 076c7d36..c47cca42 100644 --- a/crates/combat/src/laser.rs +++ b/crates/combat/src/laser.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::gamestate::GameState; use de_objects::Health; use de_signs::UpdateBarValueEvent; @@ -79,6 +80,7 @@ fn fire( mut susceptible: Query<&mut Health>, mut bar: EventWriter, mut trail: EventWriter, + mut start_sound: EventWriter, ) { for fire in fires.iter() { if susceptible @@ -100,5 +102,10 @@ fn fire( health.hit(fire.damage()); bar.send(UpdateBarValueEvent::new(entity, health.fraction())); } + + start_sound.send(PlaySpatialAudioEvent::new( + Sound::LaserFire, + fire.ray().origin.into(), + )); } } diff --git a/crates/construction/Cargo.toml b/crates/construction/Cargo.toml index 23244a03..b6d483d7 100644 --- a/crates/construction/Cargo.toml +++ b/crates/construction/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true [dependencies] # DE +de_audio.workspace = true de_core.workspace = true de_index.workspace = true de_objects.workspace = true diff --git a/crates/construction/src/manufacturing.rs b/crates/construction/src/manufacturing.rs index dc0ecff7..f67bed4c 100644 --- a/crates/construction/src/manufacturing.rs +++ b/crates/construction/src/manufacturing.rs @@ -2,6 +2,7 @@ use std::{collections::VecDeque, time::Duration}; use ahash::AHashMap; use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::{ cleanup::DespawnOnGameExit, gamestate::GameState, @@ -409,6 +410,7 @@ fn deliver( mut deliver_events: EventReader, mut path_events: EventWriter, factories: Query<(&Transform, &ObjectType, &Player, &DeliveryLocation)>, + mut play_audio: EventWriter, ) { for delivery in deliver_events.iter() { info!( @@ -440,6 +442,8 @@ fn deliver( false, ), )); + + play_audio.send(PlaySpatialAudioEvent::new(Sound::Manufacture, spawn_point)); } } diff --git a/crates/controller/Cargo.toml b/crates/controller/Cargo.toml index db88ef77..c0368ee4 100644 --- a/crates/controller/Cargo.toml +++ b/crates/controller/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true [dependencies] # DE +de_audio.workspace = true de_behaviour.workspace = true de_camera.workspace = true de_combat.workspace = true diff --git a/crates/controller/src/draft.rs b/crates/controller/src/draft.rs index a866a83c..c4114c4f 100644 --- a/crates/controller/src/draft.rs +++ b/crates/controller/src/draft.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::{ cleanup::DespawnOnGameExit, gamestate::GameState, @@ -79,6 +80,7 @@ fn spawn( mut commands: Commands, game_config: Res, drafts: Query<(Entity, &Transform, &ObjectType, &DraftAllowed)>, + mut play_audio: EventWriter, ) { for (entity, &transform, &object_type, draft) in drafts.iter() { if draft.allowed() { @@ -88,6 +90,11 @@ fn spawn( game_config.locals().playable(), DespawnOnGameExit, )); + + play_audio.send(PlaySpatialAudioEvent::new( + Sound::Construct, + transform.translation, + )); } } } diff --git a/crates/spawner/Cargo.toml b/crates/spawner/Cargo.toml index 30b4dc6e..f585541c 100644 --- a/crates/spawner/Cargo.toml +++ b/crates/spawner/Cargo.toml @@ -16,6 +16,7 @@ godmode = [] [dependencies] # DE +de_audio.workspace = true de_core.workspace = true de_index.workspace = true de_map.workspace = true diff --git a/crates/spawner/src/despawner.rs b/crates/spawner/src/despawner.rs index 4091fed9..8411ce2f 100644 --- a/crates/spawner/src/despawner.rs +++ b/crates/spawner/src/despawner.rs @@ -2,6 +2,8 @@ use std::marker::PhantomData; use bevy::ecs::query::{ReadOnlyWorldQuery, WorldQuery}; use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; +use de_core::objects::ActiveObjectType; use de_core::{objects::ObjectType, player::Player, state::AppState}; use de_objects::Health; @@ -43,13 +45,22 @@ pub struct DespawnEvent(Entity); /// Find all entities with low health and mark them for despawning fn find_dead( mut counter: ResMut, - entities: Query<(Entity, &Player, &ObjectType, &Health), Changed>, + entities: Query<(Entity, &Player, &ObjectType, &Health, &Transform), Changed>, mut event_writer: EventWriter, + mut play_audio: EventWriter, ) { - for (entity, &player, &object_type, health) in entities.iter() { + for (entity, &player, &object_type, health, transform) in entities.iter() { if health.destroyed() { if let ObjectType::Active(active_type) = object_type { counter.player_mut(player).unwrap().update(active_type, -1); + + play_audio.send(PlaySpatialAudioEvent::new( + match active_type { + ActiveObjectType::Building(_) => Sound::DestroyBuilding, + ActiveObjectType::Unit(_) => Sound::DestroyUnit, + }, + transform.translation, + )); } event_writer.send(DespawnEvent(entity)); } diff --git a/src/main.rs b/src/main.rs index a4a49204..1bde9462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use bevy::{ prelude::*, window::WindowMode, }; +use bevy_kira_audio::AudioPlugin; use de_audio::AudioPluginGroup; use de_behaviour::BehaviourPluginGroup; use de_camera::CameraPluginGroup; @@ -62,6 +63,7 @@ fn main() { }) .disable::(), ) + .add_plugins(AudioPlugin) .add_plugins(( LogDiagnosticsPlugin { debug: false,