diff --git a/src/lib.rs b/src/lib.rs index 98e21cd..282326c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub use bevy_input_actionmap; pub use bevy_openal; pub use bevy_rapier2d; pub use bevy_tts; +pub mod commands; pub use coord_2d; #[macro_use] pub mod core; @@ -19,6 +20,7 @@ pub mod map; pub use mapgen; pub mod navigation; pub mod pathfinding; +pub mod pitch_shift; pub use rand; pub use shadowcast; pub mod sound; diff --git a/src/pitch_shift.rs b/src/pitch_shift.rs new file mode 100644 index 0000000..df17c6f --- /dev/null +++ b/src/pitch_shift.rs @@ -0,0 +1,172 @@ +use std::collections::HashMap; + +use bevy::prelude::*; + +use crate::{ + bevy_openal::{Listener, Sound}, + commands::RunIfExistsExt, + derive_more::{Deref, DerefMut}, + sound::SoundIcon, +}; + +#[derive(Component, Clone, Copy, Debug, Default)] +struct Behind; + +#[derive(Clone, Copy, Debug)] +pub struct PitchShiftConfig { + pub downshift_behind: bool, + pub downshift: f32, +} + +impl Default for PitchShiftConfig { + fn default() -> Self { + Self { + downshift_behind: false, + downshift: 0.95, + } + } +} + +fn tag_behind( + mut commands: Commands, + config: Res, + sounds: Query<(Entity, &GlobalTransform, Option<&Sound>, Option<&SoundIcon>)>, + listener: Query<&GlobalTransform, With>, + behind: Query>, +) { + if config.downshift_behind { + if let Ok(listener_transform) = listener.get_single() { + let listener_forward = listener_transform.local_x(); + for (entity, transform, sound, icon) in sounds.iter() { + if icon.is_none() && sound.is_none() { + continue; + } + let v = transform.translation - listener_transform.translation; + let dot = v.dot(listener_forward); + let is_behind = dot <= 0.; + if is_behind { + commands.run_if_exists(entity, |mut entity| { + entity.insert(Behind); + }); + } else if !is_behind { + commands.run_if_exists(entity, |mut entity| { + entity.remove::(); + }); + } + } + } + } else { + for entity in behind.iter() { + commands.run_if_exists(entity, |mut entity| { + entity.remove::(); + }); + } + } +} + +#[derive(Clone, Debug, Default, Deref, DerefMut)] +struct LastIconPitch(HashMap); + +#[derive(Clone, Debug, Default, Deref, DerefMut)] +struct LastSoundPitch(HashMap); + +fn behind_added( + config: Res, + mut last_icon_pitch: ResMut, + mut last_sound_pitch: ResMut, + mut query: Query<(Entity, Option<&mut SoundIcon>, Option<&mut Sound>), Added>, +) { + for (entity, icon, sound) in query.iter_mut() { + if let Some(mut icon) = icon { + icon.pitch *= config.downshift; + last_icon_pitch.insert(entity, icon.pitch); + } else if let Some(mut sound) = sound { + sound.pitch *= config.downshift; + last_sound_pitch.insert(entity, sound.pitch); + } + } +} + +fn behind_removed( + config: Res, + mut last_icon_pitch: ResMut, + mut last_sound_pitch: ResMut, + removed: RemovedComponents, + mut icons: Query<&mut SoundIcon>, + mut sounds: Query<&mut Sound>, +) { + let downshift = 1. / config.downshift; + for entity in removed.iter() { + if let Ok(mut icon) = icons.get_mut(entity) { + icon.pitch *= downshift; + last_icon_pitch.remove(&entity); + } else if let Ok(mut sound) = sounds.get_mut(entity) { + sound.pitch *= downshift; + last_sound_pitch.remove(&entity); + } + } +} + +fn sound_icon_changed( + config: Res, + mut last_icon_pitch: ResMut, + mut icons: Query<(Entity, &mut SoundIcon), (With, Changed)>, +) { + for (entity, mut icon) in icons.iter_mut() { + let should_change = if let Some(pitch) = last_icon_pitch.get(&entity) { + *pitch != icon.pitch + } else { + false + }; + if should_change { + icon.pitch *= config.downshift; + last_icon_pitch.insert(entity, icon.pitch); + } + } +} + +fn sound_changed( + config: Res, + mut last_sound_pitch: ResMut, + mut sounds: Query<(Entity, &mut Sound), (With, Without, Changed)>, +) { + for (entity, mut sound) in sounds.iter_mut() { + let should_change = if let Some(pitch) = last_sound_pitch.get(&entity) { + *pitch != sound.pitch + } else { + false + }; + if should_change { + sound.pitch *= config.downshift; + last_sound_pitch.insert(entity, sound.pitch); + } + } +} + +fn sync_config( + mut commands: Commands, + config: Res, + behind: Query>, +) { + if config.is_changed() { + for entity in behind.iter() { + commands.entity(entity).remove::(); + } + } +} + +pub struct PitchShiftPlugin; + +impl Plugin for PitchShiftPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .init_resource::() + .init_resource::() + .add_system_to_stage(CoreStage::PreUpdate, tag_behind) + .add_system(behind_added) + .add_system_to_stage(CoreStage::PostUpdate, behind_removed) + .add_system(sound_icon_changed) + .add_system(sound_changed) + .add_system(sync_config); + } +}