Move sound functionality into separate plugins.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5db110ccc8
commit
1022037efe
103
src/sound/footstep.rs
Normal file
103
src/sound/footstep.rs
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
48
src/sound/mod.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user