use std::collections::HashMap; use bevy::{asset::HandleId, prelude::*, transform::TransformSystem}; use bevy_synthizer::{DistanceMax, DistanceRef, Rolloff, Sound}; 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: f64, pub reference_distance: Option, pub max_distance: Option, pub rolloff: Option, pub pitch: Option, pub pitch_variation: Option, } impl Default for Footstep { fn default() -> Self { Self { sound: "".into(), step_length: 0.8, gain: 1., reference_distance: None, max_distance: None, rolloff: None, pitch: None, 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, asset_server: Res, footsteps: Query<(Entity, &Footstep), Added>, ) { for (entity, footstep) in footsteps.iter() { let buffer = asset_server.get_handle(footstep.sound); commands.run_if_exists(entity, move |mut entity| { entity.insert(Sound { buffer, paused: true, ..default() }); }); } } fn update( mut commands: Commands, mut last_step_distance: Local>, mut footsteps: Query< ( Entity, &Footstep, &Parent, &mut Sound, Option<&DistanceRef>, Option<&DistanceMax>, Option<&Rolloff>, ), Changed, >, transforms_storage: Query<&Transform>, ) { for (entity, footstep, parent, mut sound, reference_distance, max_distance, rolloff) 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; if let Some(v) = footstep.reference_distance { let insert = if let Some(v2) = reference_distance { v != **v2 } else { true }; if insert { commands.run_if_exists(entity, move |mut entity| { entity.insert(DistanceRef(v)); }) } } else if reference_distance.is_some() { commands.run_if_exists(entity, |mut entity| { entity.remove::(); }); } if let Some(v) = footstep.max_distance { let insert = if let Some(v2) = max_distance { v != **v2 } else { true }; if insert { commands.run_if_exists(entity, move |mut entity| { entity.insert(DistanceMax(v)); }) } } else if max_distance.is_some() { commands.run_if_exists(entity, |mut entity| { entity.remove::(); }); } if let Some(v) = footstep.rolloff { let insert = if let Some(v2) = rolloff { v != **v2 } else { true }; if insert { commands.run_if_exists(entity, move |mut entity| { entity.insert(Rolloff(v)); }) } } else if rolloff.is_some() { commands.run_if_exists(entity, |mut entity| { entity.remove::(); }); } sound.pitch = footstep.pitch.unwrap_or(1.); if let Some(pitch_variation) = footstep.pitch_variation { let mut pitch = sound.pitch - pitch_variation / 2.; pitch += random::() * pitch_variation; sound.pitch = pitch; } sound.paused = false; sound.restart = true; } 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::() .add_system(added) .add_system_to_stage( CoreStage::PostUpdate, update.after(TransformSystem::TransformPropagate), ); } }