Skip to content

Commit

Permalink
Update audio system
Browse files Browse the repository at this point in the history
更新声音系统,改成由事件驱动
  • Loading branch information
cloudhu committed Dec 10, 2024
1 parent 3afbc3f commit e6c1f4f
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 136 deletions.
169 changes: 80 additions & 89 deletions src/audio.rs
Original file line number Diff line number Diff line change
@@ -1,110 +1,101 @@
use crate::config::GameConfig;
use crate::AppSet;
//! Exposes a plugin that starts, stops, and modulates in-game audio when events are emitted
use crate::assets::audio_assets::GameAudioAssets;
use crate::components::audio::{ChangeBackgroundMusicEvent, PlaySoundEffectEvent};
use bevy::prelude::*;
use bevy_kira_audio::prelude::*;
use std::time::Duration;

const SPATIAL_AUDIO_MAX_DISTANCE: f32 = 400.0;
/// Starts, stops, and modulates in-game audio when we receive a `PlaySoundEffectEvent` or `ChangeBackgroundMusicEvent`.
pub(super) fn plugin(app: &mut App) {
app.add_event::<PlaySoundEffectEvent>();
app.add_event::<ChangeBackgroundMusicEvent>();

pub fn play_sound_effects(
audio: &Res<Audio>,
config: &GameConfig,
source: Handle<AudioSource>,
position: &Option<Vec2>,
camera_position: Vec2,
) {
let volume = match position {
None => 1.0,
Some(p) => {
1.0 - ((*p - camera_position).length() / SPATIAL_AUDIO_MAX_DISTANCE)
.clamp(0.0, 1.0)
.powf(2.0)
}
};
app.add_audio_channel::<BackgroundMusicAudioChannel>()
.add_audio_channel::<MenuAudioChannel>()
.add_audio_channel::<SoundEffectsAudioChannel>();

let panning = match position {
None => 0.5,
Some(p) => 0.5 + ((p.x - camera_position.x) / SPATIAL_AUDIO_MAX_DISTANCE).clamp(-0.5, 0.5),
};
app.add_systems(Startup, set_audio_volume_system);

audio
.play(source.clone())
.with_volume(Volume::Amplitude((config.sfx_volume * volume) as f64))
.with_panning(panning as f64)
.handle();
app.add_systems(
Update,
(play_sound_effect_system, change_bg_music_system)
.run_if(resource_exists::<GameAudioAssets>),
);
}

/// 表示下一个播放的BGM的资源
#[derive(Resource, Default)]
pub struct NextBgm(pub Option<Handle<AudioSource>>);
// audio channels
#[derive(Resource)]
pub struct BackgroundMusicAudioChannel;
#[derive(Resource)]
pub struct MenuAudioChannel;
#[derive(Resource)]
pub struct SoundEffectsAudioChannel;

struct SourceAndInstance {
instance: Handle<AudioInstance>,
source: Handle<AudioSource>,
/// Sets the volume of the audio channels to "sane defaults"
fn set_audio_volume_system(
background_audio_channel: Res<AudioChannel<BackgroundMusicAudioChannel>>,
menu_audio_channel: Res<AudioChannel<MenuAudioChannel>>,
effects_audio_channel: Res<AudioChannel<SoundEffectsAudioChannel>>,
) {
background_audio_channel.set_volume(0.20);
menu_audio_channel.set_volume(0.05);
effects_audio_channel.set_volume(0.80);
}

#[derive(Resource, Default)]
struct CurrentBGM(Option<SourceAndInstance>);
/// Play sound effects when we receive events. This should be called every frame for snappy audio.
fn play_sound_effect_system(
mut play_sound_event_reader: EventReader<PlaySoundEffectEvent>,
audio_channel: Res<AudioChannel<SoundEffectsAudioChannel>>,
audio_assets: Res<GameAudioAssets>,
) {
for event in play_sound_event_reader.read() {
audio_channel.play(audio_assets.get_sound_effect(&event.sound_effect_type));
}
}

fn change_bgm(
mut current_bgm: ResMut<CurrentBGM>,
next_bgm: ResMut<NextBgm>,
audio: Res<Audio>,
mut audio_instances: ResMut<Assets<AudioInstance>>,
config: Res<GameConfig>,
/// System to handle background music changes based on events.
///
/// This system listens for `ChangeBackgroundMusicEvent` events and updates
/// the background music accordingly. It can stop the current music, start new
/// music, handle looping, and apply fade-in and fade-out effects if specified in the event.
///
/// - If an event specifies a fade-out duration, the current track will fade out before stopping.
/// - If a new background music type is provided, it will play the corresponding track from the `GameAudioAssets`.
/// - The system supports looping the new track from a specified point and applying a fade-in effect if specified.
///
/// Parameters:
/// - `EventReader<ChangeBackgroundMusicEvent>`: Reads events that dictate when and how to change background music.
/// - `AudioChannel<BackgroundMusicAudioChannel>`: Controls the background music audio channel, allowing for stop, play, and fade effects.
/// - `GameAudioAssets`: A resource that holds all available audio assets.
fn change_bg_music_system(
mut change_bg_music_event_reader: EventReader<ChangeBackgroundMusicEvent>,
audio_channel: Res<AudioChannel<BackgroundMusicAudioChannel>>,
audio_assets: Res<GameAudioAssets>,
) {
let NextBgm(ref next_bgm_or_none) = *next_bgm;
for event in change_bg_music_event_reader.read() {
// stop audio if playing sound
if audio_channel.is_playing_sound() {
let mut stop_command = audio_channel.stop();

if let Some(ref mut current_handle) = &mut current_bgm.0 {
if let Some(ref next) = *next_bgm_or_none {
if current_handle.source.id() != next.id() {
if let Some(instance) = audio_instances.get_mut(&current_handle.instance) {
instance.stop(AudioTween::new(Duration::from_secs(1), AudioEasing::Linear));
}
let instance = audio
.play(next.clone())
.with_volume(Volume::Amplitude(config.bgm_volume as f64))
.looped()
.handle();
current_bgm.0 = Some(SourceAndInstance {
instance: instance.clone(),
source: next.clone(),
});
// use fade if specified
if let Some(fade_out) = event.fade_out {
stop_command.fade_out(AudioTween::new(fade_out, AudioEasing::Linear));
}
}
} else if let Some(ref next) = *next_bgm_or_none {
let instance = audio
.play(next.clone())
.with_volume(Volume::Amplitude(config.bgm_volume as f64))
.looped()
.handle();
current_bgm.0 = Some(SourceAndInstance {
instance: instance.clone(),
source: next.clone(),
});
}
}
// info!("play_sound_effect_system: change_bg_music_event");
// play music if provided a type
if let Some(bg_music_type) = event.bg_music_type.clone() {
let mut start_command =
audio_channel.play(audio_assets.get_bg_music_asset(&bg_music_type));

fn update_bgm_volume(
mut current_bgm: ResMut<CurrentBGM>,
mut audio_instances: ResMut<Assets<AudioInstance>>,
config: Res<GameConfig>,
) {
if config.is_changed() {
if let Some(ref mut current_handle) = &mut current_bgm.0 {
if let Some(instance) = audio_instances.get_mut(&current_handle.instance) {
instance.set_volume(
Volume::Amplitude(config.bgm_volume as f64),
AudioTween::linear(Duration::from_millis(100)),
);
// loop if true
if let Some(loop_from) = event.loop_from {
start_command.loop_from(loop_from);
}

// use fade if specified
if let Some(fade_in) = event.fade_in {
start_command.fade_in(AudioTween::new(fade_in, AudioEasing::Linear));
}
}
}
}

pub(super) fn plugin(app: &mut App) {
app.add_systems(FixedUpdate, change_bgm.before(AppSet::RecordInput));
app.add_systems(Update, update_bgm_volume);
app.init_resource::<NextBgm>();
app.init_resource::<CurrentBGM>();
}
4 changes: 1 addition & 3 deletions src/gameplay/player.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::assets::player_assets::PlayerAssets;
use crate::components::character::CharacterType;
use crate::components::input::{InputsResource, MainMenuExplorer, MenuAction, PlayerAction};
use crate::components::player::{PlayerInput, PlayersResource};
use crate::components::input::{InputsResource, PlayerAction};
use crate::{
components::health::{FighterBundle, HealthComponent},
gameplay::{
Expand All @@ -15,7 +14,6 @@ use crate::{
util::RenderLayer,
AppSet, CameraShake, MainCamera,
};
use bevy::input::gamepad::GamepadButtonChangedEvent;
use bevy::input::mouse::MouseWheel;
use bevy::window::WindowMode;
use bevy::{
Expand Down
39 changes: 24 additions & 15 deletions src/screens/credits.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
//! A credits screen that can be accessed from the title screen.
use bevy::prelude::*;
use std::time::Duration;

use crate::assets::audio_assets::{Fonts, Music};
use crate::audio::NextBgm;
use crate::assets::audio_assets::Fonts;
use crate::components::audio::{BGMusicType, ChangeBackgroundMusicEvent};
use crate::{screens::AppStates, theme::prelude::*};

pub(super) fn plugin(app: &mut App) {
app.add_systems(OnEnter(AppStates::Credits), spawn_credits_screen);

app.add_systems(OnEnter(AppStates::Credits), play_credits_music);
app.add_systems(OnExit(AppStates::Credits), stop_music);
}

fn spawn_credits_screen(mut commands: Commands, fonts: Res<Fonts>) {
fn spawn_credits_screen(
mut commands: Commands,
fonts: Res<Fonts>,
mut change_bg_music_event_writer: EventWriter<ChangeBackgroundMusicEvent>,
) {
commands
.ui_root()
.insert(StateScoped(AppStates::Credits))
Expand All @@ -27,16 +29,23 @@ fn spawn_credits_screen(mut commands: Commands, fonts: Res<Fonts>) {
.button("Back", fonts.primary.clone())
.observe(enter_title_screen);
});
change_bg_music_event_writer.send(ChangeBackgroundMusicEvent {
bg_music_type: Some(BGMusicType::Game),
loop_from: Some(0.0),
fade_in: Some(Duration::from_secs(2)),
fade_out: Some(Duration::from_secs(2)),
});
}

fn enter_title_screen(_trigger: Trigger<OnPress>, mut next_screen: ResMut<NextState<AppStates>>) {
fn enter_title_screen(
_trigger: Trigger<OnPress>,
mut next_screen: ResMut<NextState<AppStates>>,
mut change_bg_music_event_writer: EventWriter<ChangeBackgroundMusicEvent>,
) {
// fade music out
change_bg_music_event_writer.send(ChangeBackgroundMusicEvent {
fade_out: Some(Duration::from_secs(1)),
..default()
});
next_screen.set(AppStates::MainMenu);
}

fn play_credits_music(mut next_bgm: ResMut<NextBgm>, music: Res<Music>) {
*next_bgm = NextBgm(Some(music.monkeys.clone()));
}

fn stop_music(mut next_bgm: ResMut<NextBgm>) {
*next_bgm = NextBgm(None);
}
37 changes: 22 additions & 15 deletions src/screens/gameplay.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
//! The screen state for the main gameplay.
use crate::assets::audio_assets::{Fonts, Music};
use crate::audio::NextBgm;
use crate::assets::audio_assets::Fonts;
use crate::components::audio::{BGMusicType, ChangeBackgroundMusicEvent};
use crate::gameplay::level::spawn_level as spawn_level_command;
use crate::gameplay::loot::Points;
use crate::gameplay::GameStates;
use crate::theme::interaction::OnPress;
use crate::{screens::AppStates, theme::prelude::*};
use bevy::input::common_conditions::input_just_pressed;
use bevy::prelude::*;
use std::time::Duration;

pub(super) fn plugin(app: &mut App) {
app.add_systems(OnEnter(AppStates::Game), spawn_level);
app.add_systems(OnEnter(AppStates::Game), play_gameplay_music);
app.add_systems(OnExit(AppStates::Game), stop_music);
app.add_systems(OnEnter(GameStates::GameOver), setup_game_over);
app.add_systems(
Update,
Expand All @@ -22,19 +20,28 @@ pub(super) fn plugin(app: &mut App) {
);
}

fn spawn_level(mut commands: Commands) {
fn spawn_level(
mut commands: Commands,
mut change_bg_music_event_writer: EventWriter<ChangeBackgroundMusicEvent>,
) {
change_bg_music_event_writer.send(ChangeBackgroundMusicEvent {
bg_music_type: Some(BGMusicType::Game),
loop_from: Some(0.0),
fade_in: Some(Duration::from_secs(2)),
fade_out: Some(Duration::from_secs(2)),
});
commands.add(spawn_level_command);
}

fn play_gameplay_music(mut next_bgm: ResMut<NextBgm>, music: Res<Music>) {
*next_bgm = NextBgm(Some(music.gameplay.clone()));
}

fn stop_music(mut next_bgm: ResMut<NextBgm>) {
*next_bgm = NextBgm(None);
}

fn return_to_title_screen(mut next_screen: ResMut<NextState<AppStates>>) {
fn return_to_title_screen(
mut next_screen: ResMut<NextState<AppStates>>,
mut change_bg_music_event_writer: EventWriter<ChangeBackgroundMusicEvent>,
) {
// fade music out
change_bg_music_event_writer.send(ChangeBackgroundMusicEvent {
fade_out: Some(Duration::from_secs(2)),
..default()
});
next_screen.set(AppStates::MainMenu);
}

Expand Down
27 changes: 13 additions & 14 deletions src/screens/title.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! The title screen that appears when the game starts.
use crate::assets::audio_assets::{Fonts, Music};
use crate::audio::NextBgm;
use crate::assets::audio_assets::Fonts;
use crate::components::audio::{BGMusicType, ChangeBackgroundMusicEvent};
use crate::components::character::CharacterType;
use crate::components::events::{ButtonActionEvent, PlayerJoinEvent};
use crate::components::input::{
Expand All @@ -14,24 +14,24 @@ use bevy::input::gamepad::GamepadButtonChangedEvent;
use bevy::prelude::*;
use bevy::window::WindowMode;
use leafwing_input_manager::action_state::ActionState;
use std::time::Duration;

pub(super) fn plugin(app: &mut App) {
app.add_event::<PlayerJoinEvent>();
app.add_event::<ButtonActionEvent>();
app.add_systems(OnEnter(AppStates::MainMenu), spawn_title_screen);
app.add_systems(OnEnter(AppStates::MainMenu), play_title_music);
app.add_systems(OnExit(AppStates::MainMenu), stop_title_music);
app.add_systems(OnEnter(AppStates::MainMenu), setup_title_screen);
app.add_systems(
Update,
(player_join_system,).run_if(in_state(AppStates::MainMenu)),
);
}

fn spawn_title_screen(
fn setup_title_screen(
mut commands: Commands,
mut localize: ResMut<Localize>,
config: Res<GameConfig>,
fonts: Res<Fonts>,
mut change_bg_music_event_writer: EventWriter<ChangeBackgroundMusicEvent>,
) {
localize.set_language(config.language.clone());
commands
Expand All @@ -55,6 +55,13 @@ fn spawn_title_screen(
.button("Exit", fonts.primary.clone())
.observe(exit_app);
});
// change music
change_bg_music_event_writer.send(ChangeBackgroundMusicEvent {
bg_music_type: Some(BGMusicType::Main),
loop_from: Some(0.0),
fade_in: Some(Duration::from_secs(2)),
fade_out: Some(Duration::from_secs(2)),
});
}

fn enter_gameplay_screen(
Expand Down Expand Up @@ -90,14 +97,6 @@ fn exit_app(_trigger: Trigger<OnPress>, mut app_exit: EventWriter<AppExit>) {
app_exit.send(AppExit::Success);
}

fn play_title_music(mut next_bgm: ResMut<NextBgm>, music: Res<Music>) {
*next_bgm = NextBgm(Some(music.title.clone()));
}

fn stop_title_music(mut next_bgm: ResMut<NextBgm>) {
*next_bgm = NextBgm(None);
}

fn toggle_fullscreen(_trigger: Trigger<OnPress>, mut window_query: Query<&mut Window>) {
let mut window = window_query.single_mut();
window.mode = match window.mode {
Expand Down

0 comments on commit e6c1f4f

Please sign in to comment.