diff --git a/Cargo.lock b/Cargo.lock index 8f2142531..57a51c0d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2118,6 +2118,7 @@ dependencies = [ "de_camera", "de_conf", "de_core", + "enum-map", "iyes_progress", ] @@ -2209,7 +2210,6 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.7.6", "bevy", - "bevy_kira_audio", "de_audio", "de_core", "de_index", @@ -2217,7 +2217,6 @@ dependencies = [ "de_pathing", "de_signs", "de_spawner", - "iyes_progress", "parry2d", "parry3d", ] @@ -2228,7 +2227,6 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.7.6", "bevy", - "bevy_kira_audio", "de_audio", "de_behaviour", "de_camera", @@ -2247,7 +2245,6 @@ dependencies = [ "de_terrain", "enum-map", "glam", - "iyes_progress", "parry2d", "parry3d", ] @@ -2545,7 +2542,6 @@ version = "0.1.0-dev" dependencies = [ "ahash 0.7.6", "bevy", - "bevy_kira_audio", "de_audio", "de_core", "de_energy", @@ -2553,7 +2549,6 @@ dependencies = [ "de_map", "de_objects", "de_terrain", - "iyes_progress", "parry2d", "parry3d", ] @@ -5618,11 +5613,9 @@ checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" dependencies = [ "lazy_static", "symphonia-bundle-mp3", - "symphonia-codec-pcm", "symphonia-codec-vorbis", "symphonia-core", "symphonia-format-ogg", - "symphonia-format-wav", "symphonia-metadata", ] @@ -5639,16 +5632,6 @@ dependencies = [ "symphonia-metadata", ] -[[package]] -name = "symphonia-codec-pcm" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f1fbd220a06a641c8ce2ddad10f5ef6ee5cc0c54d9044d25d43b0d3119deaa" -dependencies = [ - "log", - "symphonia-core", -] - [[package]] name = "symphonia-codec-vorbis" version = "0.5.3" @@ -5685,17 +5668,6 @@ dependencies = [ "symphonia-utils-xiph", ] -[[package]] -name = "symphonia-format-wav" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da76614728fa27c003bdcdfbac51396bd8fcbf94c95fe8e62f1d2bac58ef03a4" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", -] - [[package]] name = "symphonia-metadata" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 891511f03..8d301517e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,7 +112,7 @@ assert_cmd = "2.0.10" async-compat = "0.2.1" async-std = "1.11" async-tar = "0.4.2" -bevy_kira_audio = { version = "0.15.0", features = ["mp3", "wav"] } +bevy_kira_audio = { version = "0.15.0", features = ["mp3"] } bincode = "2.0.0-rc.3" chrono = "0.4.24" clap = { version = "4.0", features = ["derive"] } diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 8eaedd6b4..8b233ae89 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -20,4 +20,5 @@ 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/spatial.rs b/crates/audio/src/spatial.rs index 12fd7039d..284308ee0 100644 --- a/crates/audio/src/spatial.rs +++ b/crates/audio/src/spatial.rs @@ -1,106 +1,130 @@ -use bevy::prelude::*; +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; +use de_core::{baseset::GameSet, gamestate::GameState, state::AppState}; +use enum_map::{enum_map, Enum, EnumMap}; +use iyes_progress::{Progress, ProgressSystem}; pub(crate) struct SpatialSoundPlugin; impl Plugin for SpatialSoundPlugin { fn build(&self, app: &mut App) { - app.add_system(start.run_if(in_state(GameState::Playing))) + app.add_event::() + .add_system(setup.in_schedule(OnEnter(AppState::AppLoading))) + .add_system(load.track_progress().run_if(in_state(AppState::AppLoading))) + .add_system( + play.in_base_set(GameSet::PostUpdate) + .run_if(in_state(GameState::Playing)) + .run_if(on_event::()), + ) .add_system( update_spatial - .in_base_set(CoreSet::PostUpdate) + .after(play) + .in_base_set(GameSet::PostUpdate) .run_if(in_state(GameState::Playing)), ); } } -#[derive(Component, Default)] -pub struct SpatialSound; +#[derive(Clone, Copy, Enum)] +pub enum Sound { + Construct, + Manufacture, + DestroyBuilding, + DestroyUnit, +} -#[derive(Component)] -pub struct Volume(pub f32); +pub struct PlaySpatialAudioEvent { + pub sound: Sound, + pub position: Vec3, +} -impl Default for Volume { - fn default() -> Self { - Self(1.) +impl PlaySpatialAudioEvent { + pub fn new(sound: Sound, position: Vec3) -> Self { + Self { sound, position } } } -trait VolumeOptionExt { - fn volume(&self) -> f32; -} +#[derive(Resource)] +struct Sounds(EnumMap>); -impl VolumeOptionExt for Option<&Volume> { - fn volume(&self) -> f32 { - match self { - Some(volume) => volume.0, - None => 1., - } - } +#[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"), + })); } -#[derive(Bundle, Default)] -pub struct SpatialSoundBundle { - pub sound: Handle, - pub spatial_sound: SpatialSound, - pub volume: Volume, +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: &GlobalTransform, + sound_position: Vec3, ) -> (f64, f64) { let cam_right = camera.right(); - let sound_dir = (sound.translation() - camera.translation()).normalize(); + let sound_dir = (sound_position - camera.translation()).normalize(); let pan = cam_right.dot(sound_dir) * 0.5 + 0.5; - let distance_from_camera = camera.translation().distance(sound.translation()); + let distance_from_camera = camera.translation().distance(sound_position); let camera_zoom_distance = focus.distance().inner(); let distance_attenuation = (1.0 - distance_from_camera / (camera_zoom_distance + 32.) + 0.5).clamp(0.0, 1.0); (distance_attenuation as f64, pan as f64) } -type UninitializedSound<'s> = ( - Entity, - &'s Handle, - &'s GlobalTransform, - Option<&'s Volume>, -); - -fn start( +fn play( mut commands: Commands, - starts: Query, Without>)>, camera: Query<&GlobalTransform, With>, focus: Res, audio: Res, + sounds: Res, + mut play_events: EventReader, ) { let camera = camera.single(); - for (entity, sound, transform, sound_volume) in &starts { - let (volume, pan) = calculate_volume_and_pan(camera, &focus, transform); + for PlaySpatialAudioEvent { sound, position } in &mut play_events { + let (volume, pan) = calculate_volume_and_pan(camera, &focus, *position); let handle = audio - .play(sound.clone()) - .with_volume(volume * sound_volume.volume() as f64) + .play(sounds.0[*sound].clone()) + .with_volume(volume) .with_panning(pan) .handle(); - commands.entity(entity).insert(handle); + commands.spawn(( + TransformBundle::from_transform(Transform::from_translation(*position)), + handle, + )); } } -type InitializedSound<'s> = ( - Entity, - &'s Handle, - &'s GlobalTransform, - Option<&'s Volume>, -); +type InitializedSound<'s> = (Entity, &'s Handle, &'s GlobalTransform); fn update_spatial( mut commands: Commands, @@ -111,15 +135,15 @@ fn update_spatial( ) { let camera = camera.single(); - for (entity, audio, transform, sound_volume) in &spatial_audios { + for (entity, audio, transform) in &spatial_audios { let Some(audio_instance) = audio_instances.get_mut(audio) else { commands.entity(entity).despawn(); continue; }; - let (volume, pan) = calculate_volume_and_pan(camera, &focus, transform); + let (volume, pan) = calculate_volume_and_pan(camera, &focus, transform.translation()); - audio_instance.set_volume(volume * sound_volume.volume() as f64, default()); + audio_instance.set_volume(volume, default()); audio_instance.set_panning(pan, default()); } } diff --git a/crates/construction/Cargo.toml b/crates/construction/Cargo.toml index adefe3ee4..b6d483d7b 100644 --- a/crates/construction/Cargo.toml +++ b/crates/construction/Cargo.toml @@ -24,7 +24,5 @@ de_spawner.workspace = true # Other ahash.workspace = true bevy.workspace = true -bevy_kira_audio.workspace = true -iyes_progress.workspace = true parry2d.workspace = true parry3d.workspace = true diff --git a/crates/construction/src/manufacturing.rs b/crates/construction/src/manufacturing.rs index 081ebdedb..8e0ae567b 100644 --- a/crates/construction/src/manufacturing.rs +++ b/crates/construction/src/manufacturing.rs @@ -1,9 +1,8 @@ use std::{collections::VecDeque, time::Duration}; use ahash::AHashMap; -use bevy::{asset::LoadState, prelude::*}; -use bevy_kira_audio::AudioSource as KAudioSource; -use de_audio::spatial::{SpatialSoundBundle, Volume}; +use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::{ baseset::GameSet, cleanup::DespawnOnGameExit, @@ -21,7 +20,6 @@ use de_signs::{ LineLocation, UpdateLineEndEvent, UpdateLineLocationEvent, UpdatePoleLocationEvent, }; use de_spawner::{ObjectCounter, SpawnBundle}; -use iyes_progress::{Progress, ProgressSystem}; use parry2d::bounding_volume::Aabb; use parry3d::math::Isometry; @@ -35,8 +33,6 @@ impl Plugin for ManufacturingPlugin { app.add_event::() .add_event::() .add_event::() - .add_system(setup.in_schedule(OnEnter(AppState::AppLoading))) - .add_system(load.track_progress().run_if(in_state(AppState::AppLoading))) .add_system( configure .in_base_set(GameSet::PostUpdate) @@ -145,9 +141,6 @@ impl DeliverEvent { } } -#[derive(Resource)] -struct AssemblySound(Handle); - #[derive(Component)] struct DeliveryLocation(Vec2); @@ -314,18 +307,6 @@ impl ProductionItem { } } -fn setup(mut commands: Commands, server: Res) { - commands.insert_resource(AssemblySound(server.load("audio/sounds/manufacture.ogg"))); -} - -fn load(server: Res, sounds: Res) -> Progress { - match server.get_load_state(&sounds.0) { - LoadState::Loaded => true.into(), - LoadState::NotLoaded | LoadState::Loading => false.into(), - _ => panic!("Unexpected loading state."), - } -} - fn configure( mut commands: Commands, solids: SolidObjects, @@ -448,7 +429,7 @@ fn deliver( mut deliver_events: EventReader, mut path_events: EventWriter, factories: Query<(&Transform, &ObjectType, &Player, &DeliveryLocation)>, - sound: Res, + mut play_audio: EventWriter, ) { for delivery in deliver_events.iter() { info!( @@ -481,14 +462,7 @@ fn deliver( ), )); - commands.spawn(( - TransformBundle::from_transform(Transform::from_translation(spawn_point)), - SpatialSoundBundle { - sound: sound.0.clone(), - volume: Volume(0.4), - ..Default::default() - }, - )); + play_audio.send(PlaySpatialAudioEvent::new(Sound::Manufacture, spawn_point)); } } diff --git a/crates/controller/Cargo.toml b/crates/controller/Cargo.toml index 7ac7311b2..c0368ee4a 100644 --- a/crates/controller/Cargo.toml +++ b/crates/controller/Cargo.toml @@ -33,9 +33,7 @@ de_terrain.workspace = true # Other ahash.workspace = true bevy.workspace = true -bevy_kira_audio.workspace = true enum-map.workspace = true glam.workspace = true -iyes_progress.workspace = true parry2d.workspace = true parry3d.workspace = true diff --git a/crates/controller/src/draft.rs b/crates/controller/src/draft.rs index 846fef952..cb86fc7bf 100644 --- a/crates/controller/src/draft.rs +++ b/crates/controller/src/draft.rs @@ -1,6 +1,5 @@ -use bevy::{asset::LoadState, prelude::*}; -use bevy_kira_audio::AudioSource as KAudioSource; -use de_audio::spatial::SpatialSoundBundle; +use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::{ baseset::GameSet, cleanup::DespawnOnGameExit, @@ -10,7 +9,6 @@ use de_core::{ state::AppState, }; use de_spawner::{DraftAllowed, DraftBundle, SpawnBundle}; -use iyes_progress::{Progress, ProgressSystem}; use crate::mouse::{Pointer, PointerSet}; @@ -21,8 +19,6 @@ impl Plugin for DraftPlugin { app.add_event::() .add_event::() .add_event::() - .add_system(setup.in_schedule(OnEnter(AppState::AppLoading))) - .add_system(load.track_progress().run_if(in_state(AppState::AppLoading))) .add_system( spawn .in_base_set(GameSet::Input) @@ -85,26 +81,11 @@ impl NewDraftEvent { } } -#[derive(Resource)] -struct ConstructionSound(Handle); - -fn setup(mut commands: Commands, server: Res) { - commands.insert_resource(ConstructionSound(server.load("audio/sounds/construct.ogg"))); -} - -fn load(server: Res, sound: Res) -> Progress { - match server.get_load_state(&sound.0) { - LoadState::Loaded => true.into(), - LoadState::NotLoaded | LoadState::Loading => false.into(), - _ => panic!("Unexpected loading state."), - } -} - fn spawn( mut commands: Commands, game_config: Res, drafts: Query<(Entity, &Transform, &ObjectType, &DraftAllowed)>, - sound: Res, + mut play_audio: EventWriter, ) { for (entity, &transform, &object_type, draft) in drafts.iter() { if draft.allowed() { @@ -115,12 +96,9 @@ fn spawn( DespawnOnGameExit, )); - commands.spawn(( - TransformBundle::from_transform(transform), - SpatialSoundBundle { - sound: sound.0.clone(), - ..Default::default() - }, + play_audio.send(PlaySpatialAudioEvent::new( + Sound::Construct, + transform.translation, )); } } diff --git a/crates/spawner/Cargo.toml b/crates/spawner/Cargo.toml index 47d50111e..f585541c0 100644 --- a/crates/spawner/Cargo.toml +++ b/crates/spawner/Cargo.toml @@ -27,7 +27,5 @@ de_energy.workspace = true # Other ahash.workspace = true bevy.workspace = true -bevy_kira_audio.workspace = true -iyes_progress.workspace = true parry2d.workspace = true parry3d.workspace = true diff --git a/crates/spawner/src/destroyer.rs b/crates/spawner/src/destroyer.rs index cb4b57710..9af401554 100644 --- a/crates/spawner/src/destroyer.rs +++ b/crates/spawner/src/destroyer.rs @@ -1,6 +1,5 @@ -use bevy::{asset::LoadState, prelude::*}; -use bevy_kira_audio::AudioSource as KAudioSource; -use de_audio::spatial::{SpatialSoundBundle, Volume}; +use bevy::prelude::*; +use de_audio::spatial::{PlaySpatialAudioEvent, Sound}; use de_core::{ baseset::GameSet, objects::{ActiveObjectType, ObjectType}, @@ -8,7 +7,6 @@ use de_core::{ state::AppState, }; use de_objects::Health; -use iyes_progress::{Progress, ProgressSystem}; use crate::{ObjectCounter, SpawnerSet}; @@ -16,41 +14,12 @@ pub(crate) struct DestroyerPlugin; impl Plugin for DestroyerPlugin { fn build(&self, app: &mut App) { - app.add_system(setup.in_schedule(OnEnter(AppState::AppLoading))) - .add_system(load.track_progress().run_if(in_state(AppState::AppLoading))) - .add_system( - destroy - .in_base_set(GameSet::Update) - .run_if(in_state(AppState::InGame)) - .in_set(SpawnerSet::Destroyer), - ); - } -} - -#[derive(Resource)] -struct DestructionSounds { - building: Handle, - unit: Handle, -} - -fn setup(mut commands: Commands, server: Res) { - commands.insert_resource(DestructionSounds { - building: server.load("audio/sounds/destruction_building.ogg"), - unit: server.load("audio/sounds/destruction_unit.ogg"), - }); -} - -fn load(server: Res, sounds: Res) -> Progress { - Progress { - done: [&sounds.building, &sounds.unit] - .into_iter() - .map(|state| match server.get_load_state(state) { - LoadState::Loaded => 1, - LoadState::NotLoaded | LoadState::Loading => 0, - _ => panic!("Unexpected loading state."), - }) - .sum(), - total: 2, + app.add_system( + destroy + .in_base_set(GameSet::Update) + .run_if(in_state(AppState::InGame)) + .in_set(SpawnerSet::Destroyer), + ); } } @@ -58,25 +27,19 @@ fn destroy( mut commands: Commands, mut counter: ResMut, entities: Query<(Entity, &Player, &ObjectType, &Health, &Transform), Changed>, - sounds: Res, + mut play_audio: EventWriter, ) { 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); - commands.spawn(( - TransformBundle::from_transform(*transform), + play_audio.send(PlaySpatialAudioEvent::new( match active_type { - ActiveObjectType::Building(_) => SpatialSoundBundle { - sound: sounds.building.clone(), - ..Default::default() - }, - ActiveObjectType::Unit(_) => SpatialSoundBundle { - sound: sounds.unit.clone(), - ..Default::default() - }, + ActiveObjectType::Building(_) => Sound::DestroyBuilding, + ActiveObjectType::Unit(_) => Sound::DestroyUnit, }, + transform.translation, )); }