Skip to content

Commit

Permalink
Use events for sound playing logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Zakru committed Jul 22, 2023
1 parent 1f656fc commit e96bae9
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 196 deletions.
30 changes: 1 addition & 29 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions crates/audio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
128 changes: 76 additions & 52 deletions crates/audio/src/spatial.rs
Original file line number Diff line number Diff line change
@@ -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::<PlaySpatialAudioEvent>()
.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::<PlaySpatialAudioEvent>()),
)
.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<Sound, Handle<KAudioSource>>);

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<AssetServer>) {
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<KAudioSource>,
pub spatial_sound: SpatialSound,
pub volume: Volume,
fn load(server: Res<AssetServer>, sounds: Res<Sounds>) -> 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<KAudioSource>,
&'s GlobalTransform,
Option<&'s Volume>,
);

fn start(
fn play(
mut commands: Commands,
starts: Query<UninitializedSound, (With<SpatialSound>, Without<Handle<AudioInstance>>)>,
camera: Query<&GlobalTransform, With<Camera>>,
focus: Res<CameraFocus>,
audio: Res<KAudio>,
sounds: Res<Sounds>,
mut play_events: EventReader<PlaySpatialAudioEvent>,
) {
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<AudioInstance>,
&'s GlobalTransform,
Option<&'s Volume>,
);
type InitializedSound<'s> = (Entity, &'s Handle<AudioInstance>, &'s GlobalTransform);

fn update_spatial(
mut commands: Commands,
Expand All @@ -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());
}
}
2 changes: 0 additions & 2 deletions crates/construction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit e96bae9

Please sign in to comment.