Move sound functionality into separate plugins.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Nolan Darilek 2022-05-23 11:51:44 -05:00
parent 5db110ccc8
commit 1022037efe
3 changed files with 170 additions and 138 deletions

103
src/sound/footstep.rs Normal file
View File

@ -0,0 +1,103 @@
use std::collections::HashMap;
use bevy::{asset::HandleId, prelude::*, transform::TransformSystem};
use bevy_openal::{Buffer, Sound, SoundState};
use rand::random;
use crate::{commands::RunIfExistsExt, core::PointLike};
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Footstep {
pub sound: HandleId,
pub step_length: f32,
pub gain: f32,
pub reference_distance: f32,
pub max_distance: f32,
pub rolloff_factor: f32,
pub pitch: f32,
pub pitch_variation: Option<f32>,
}
impl Default for Footstep {
fn default() -> Self {
Self {
sound: "".into(),
step_length: 0.8,
gain: 1.,
reference_distance: 1.,
max_distance: f32::MAX,
rolloff_factor: 1.,
pitch: 1.,
pitch_variation: Some(0.15),
}
}
}
#[derive(Bundle, Default)]
pub struct FootstepBundle {
pub footstep: Footstep,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
fn added(
mut commands: Commands,
footsteps: Query<(Entity, &Footstep), Added<Footstep>>,
assets: Res<Assets<Buffer>>,
) {
for (entity, footstep) in footsteps.iter() {
let buffer = assets.get_handle(footstep.sound);
commands.run_if_exists(entity, move |mut entity| {
entity.insert(Sound {
buffer,
state: SoundState::Stopped,
..default()
});
});
}
}
fn update(
mut last_step_distance: Local<HashMap<Entity, (f32, Transform)>>,
mut footsteps: Query<(Entity, &Footstep, &Parent, &mut Sound), Changed<GlobalTransform>>,
transforms_storage: Query<&Transform>,
) {
for (entity, footstep, parent, mut sound) in footsteps.iter_mut() {
let coordinates = transforms_storage.get(**parent).unwrap();
if let Some(last) = last_step_distance.get(&entity) {
let distance = last.0 + (last.1.distance(coordinates));
if distance >= footstep.step_length {
last_step_distance.insert(entity, (0., *coordinates));
sound.gain = footstep.gain;
sound.reference_distance = footstep.reference_distance;
sound.max_distance = footstep.max_distance;
sound.rolloff_factor = footstep.rolloff_factor;
sound.pitch = footstep.pitch;
if let Some(pitch_variation) = footstep.pitch_variation {
let mut pitch = footstep.pitch - pitch_variation / 2.;
pitch += random::<f32>() * pitch_variation;
sound.pitch = pitch;
}
sound.play();
} else if last.1 != *coordinates {
last_step_distance.insert(entity, (distance, *coordinates));
}
} else {
last_step_distance.insert(entity, (0., *coordinates));
}
}
}
pub struct FootstepPlugin;
impl Plugin for FootstepPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Footstep>()
.add_system(added)
.add_system_to_stage(
CoreStage::PostUpdate,
update.after(TransformSystem::TransformPropagate),
);
}
}

View File

@ -1,45 +1,17 @@
use std::{collections::HashMap, fmt::Debug, hash::Hash, time::Duration}; use std::{fmt::Debug, hash::Hash, time::Duration};
use bevy::{asset::HandleId, prelude::*, transform::TransformSystem}; use bevy::{asset::HandleId, prelude::*, transform::TransformSystem};
use bevy_openal::{Buffer, Context, Sound, SoundState}; use bevy_openal::{Buffer, Sound, SoundState};
use rand::random; use rand::random;
use crate::{ use crate::{
commands::RunIfExistsExt, commands::RunIfExistsExt,
core::{CoreConfig, Player, PointLike}, core::Player,
exploration::ExplorationFocused, exploration::ExplorationFocused,
visibility::{Visible, VisibleEntities}, visibility::{Visible, VisibleEntities},
}; };
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Footstep {
pub sound: HandleId,
pub step_length: f32,
pub gain: f32,
pub reference_distance: f32,
pub max_distance: f32,
pub rolloff_factor: f32,
pub pitch: f32,
pub pitch_variation: Option<f32>,
}
impl Default for Footstep {
fn default() -> Self {
Self {
sound: "".into(),
step_length: 0.8,
gain: 1.,
reference_distance: 1.,
max_distance: f32::MAX,
rolloff_factor: 1.,
pitch: 1.,
pitch_variation: Some(0.15),
}
}
}
#[derive(Component, Clone, Debug)] #[derive(Component, Clone, Debug)]
pub struct SoundIcon { pub struct SoundIcon {
pub sound: HandleId, pub sound: HandleId,
@ -71,13 +43,6 @@ impl Default for SoundIcon {
} }
} }
#[derive(Bundle, Default)]
pub struct FootstepBundle {
pub footstep: Footstep,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
#[derive(Bundle, Clone, Debug, Default)] #[derive(Bundle, Clone, Debug, Default)]
pub struct SoundIconBundle { pub struct SoundIconBundle {
pub sound_icon: SoundIcon, pub sound_icon: SoundIcon,
@ -85,54 +50,6 @@ pub struct SoundIconBundle {
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
} }
fn add_footstep_sounds(
mut commands: Commands,
footsteps: Query<(Entity, &Footstep), Added<Footstep>>,
assets: Res<Assets<Buffer>>,
) {
for (entity, footstep) in footsteps.iter() {
let buffer = assets.get_handle(footstep.sound);
commands.run_if_exists(entity, move |mut entity| {
entity.insert(Sound {
buffer,
state: SoundState::Stopped,
..default()
});
});
}
}
fn footstep(
mut last_step_distance: Local<HashMap<Entity, (f32, Transform)>>,
mut footsteps: Query<(Entity, &Footstep, &Parent, &mut Sound), Changed<GlobalTransform>>,
transforms_storage: Query<&Transform>,
) {
for (entity, footstep, parent, mut sound) in footsteps.iter_mut() {
let coordinates = transforms_storage.get(**parent).unwrap();
if let Some(last) = last_step_distance.get(&entity) {
let distance = last.0 + (last.1.distance(coordinates));
if distance >= footstep.step_length {
last_step_distance.insert(entity, (0., *coordinates));
sound.gain = footstep.gain;
sound.reference_distance = footstep.reference_distance;
sound.max_distance = footstep.max_distance;
sound.rolloff_factor = footstep.rolloff_factor;
sound.pitch = footstep.pitch;
if let Some(pitch_variation) = footstep.pitch_variation {
let mut pitch = footstep.pitch - pitch_variation / 2.;
pitch += random::<f32>() * pitch_variation;
sound.pitch = pitch;
}
sound.play();
} else if last.1 != *coordinates {
last_step_distance.insert(entity, (distance, *coordinates));
}
} else {
last_step_distance.insert(entity, (0., *coordinates));
}
}
}
fn add_sound_icon_sounds( fn add_sound_icon_sounds(
mut commands: Commands, mut commands: Commands,
icons: Query<(Entity, &SoundIcon), Added<SoundIcon>>, icons: Query<(Entity, &SoundIcon), Added<SoundIcon>>,
@ -162,8 +79,8 @@ fn add_sound_icon_sounds(
} }
} }
fn sound_icon<S>( fn update<S>(
config: Res<SoundConfig<S>>, config: Res<SoundIconConfig<S>>,
state: Res<State<S>>, state: Res<State<S>>,
time: Res<Time>, time: Res<Time>,
viewers: Query<&VisibleEntities, With<Player>>, viewers: Query<&VisibleEntities, With<Player>>,
@ -178,9 +95,7 @@ fn sound_icon<S>(
) where ) where
S: 'static + Clone + Debug + Eq + Hash + Send + Sync, S: 'static + Clone + Debug + Eq + Hash + Send + Sync,
{ {
if !(*config).sound_icon_states.is_empty() if !(*config).states.is_empty() && !config.states.contains(state.current()) {
&& !config.sound_icon_states.contains(state.current())
{
return; return;
} }
for visible in viewers.iter() { for visible in viewers.iter() {
@ -218,7 +133,7 @@ fn sound_icon<S>(
} }
} }
fn sound_icon_exploration_focus_changed( fn exploration_focus_changed(
mut focused: Query<(Entity, Option<&Children>), Changed<ExplorationFocused>>, mut focused: Query<(Entity, Option<&Children>), Changed<ExplorationFocused>>,
mut icons: Query<&mut SoundIcon>, mut icons: Query<&mut SoundIcon>,
) { ) {
@ -237,7 +152,7 @@ fn sound_icon_exploration_focus_changed(
} }
} }
fn sound_icon_exploration_focus_removed( fn exploration_focus_removed(
removed: RemovedComponents<ExplorationFocused>, removed: RemovedComponents<ExplorationFocused>,
mut query: Query<&mut SoundIcon>, mut query: Query<&mut SoundIcon>,
children: Query<&Children>, children: Query<&Children>,
@ -257,71 +172,37 @@ fn sound_icon_exploration_focus_removed(
} }
} }
fn scale_sounds(config: Res<CoreConfig>, mut sounds: Query<&mut Sound>) { #[derive(Clone, Debug, Default)]
let pixels_per_unit = config.pixels_per_unit as f32; pub struct SoundIconConfig<S> {
for mut sound in sounds.iter_mut() { pub states: Vec<S>,
sound.reference_distance *= pixels_per_unit;
if sound.max_distance != f32::MAX {
sound.max_distance *= pixels_per_unit;
}
}
} }
#[derive(Clone, Debug)] pub struct SoundIconPlugin<'a, S>(std::marker::PhantomData<&'a S>);
pub struct SoundConfig<S> {
pub sound_icon_states: Vec<S>,
}
impl<S> Default for SoundConfig<S> { impl<'a, S> Default for SoundIconPlugin<'a, S> {
fn default() -> Self {
Self {
sound_icon_states: vec![],
}
}
}
pub struct SoundPlugin<'a, S>(std::marker::PhantomData<&'a S>);
impl<'a, S> Default for SoundPlugin<'a, S> {
fn default() -> Self { fn default() -> Self {
Self(std::marker::PhantomData) Self(std::marker::PhantomData)
} }
} }
impl<'a, S> Plugin for SoundPlugin<'a, S> impl<'a, S> Plugin for SoundIconPlugin<'a, S>
where where
S: 'static + Clone + Debug + Eq + Hash + Send + Sync, S: 'static + Clone + Debug + Eq + Hash + Send + Sync,
'a: 'static, 'a: 'static,
{ {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
if !app.world.contains_resource::<SoundConfig<S>>() { app.add_system(add_sound_icon_sounds)
app.insert_resource(SoundConfig::<S>::default());
}
let core_config = *app.world.get_resource::<CoreConfig>().unwrap();
if let Some(context) = app.world.get_resource::<Context>() {
context
.set_meters_per_unit(1. / core_config.pixels_per_unit as f32)
.unwrap();
}
app.register_type::<Footstep>()
.add_system(add_footstep_sounds)
.add_system_to_stage( .add_system_to_stage(
CoreStage::PostUpdate, CoreStage::PostUpdate,
footstep.after(TransformSystem::TransformPropagate), update::<S>.after(TransformSystem::TransformPropagate),
)
.add_system(add_sound_icon_sounds)
.add_system_to_stage(
CoreStage::PostUpdate,
sound_icon::<S>.after(TransformSystem::TransformPropagate),
) )
.add_system_to_stage( .add_system_to_stage(
CoreStage::PostUpdate, CoreStage::PostUpdate,
sound_icon_exploration_focus_changed.after(sound_icon::<S>), exploration_focus_changed.after(update::<S>),
) )
.add_system_to_stage( .add_system_to_stage(
CoreStage::PostUpdate, CoreStage::PostUpdate,
sound_icon_exploration_focus_removed.after(sound_icon_exploration_focus_changed), exploration_focus_removed.after(exploration_focus_changed),
) );
.add_system(scale_sounds);
} }
} }

48
src/sound/mod.rs Normal file
View File

@ -0,0 +1,48 @@
use std::{fmt::Debug, hash::Hash};
use bevy::prelude::*;
use bevy_openal::{Context, Sound};
use crate::core::CoreConfig;
pub mod footstep;
pub mod icon;
pub use footstep::{Footstep, FootstepBundle};
pub use icon::{SoundIcon, SoundIconBundle};
fn scale_sounds(config: Res<CoreConfig>, mut sounds: Query<&mut Sound>) {
let pixels_per_unit = config.pixels_per_unit as f32;
for mut sound in sounds.iter_mut() {
sound.reference_distance *= pixels_per_unit;
if sound.max_distance != f32::MAX {
sound.max_distance *= pixels_per_unit;
}
}
}
pub struct SoundPlugin<'a, S>(std::marker::PhantomData<&'a S>);
impl<'a, S> Default for SoundPlugin<'a, S> {
fn default() -> Self {
Self(std::marker::PhantomData)
}
}
impl<'a, S> Plugin for SoundPlugin<'a, S>
where
S: 'static + Clone + Debug + Eq + Hash + Send + Sync,
'a: 'static,
{
fn build(&self, app: &mut App) {
let core_config = *app.world.get_resource::<CoreConfig>().unwrap();
if let Some(context) = app.world.get_resource::<Context>() {
context
.set_meters_per_unit(1. / core_config.pixels_per_unit as f32)
.unwrap();
}
app.add_plugin(footstep::FootstepPlugin)
.add_plugin(icon::SoundIconPlugin::<S>::default())
.add_system(scale_sounds);
}
}