diff --git a/Cargo.toml b/Cargo.toml index a2318e628ba62ff3bdde6314e64da3be196adab3..47e31e64d779ff8f953a7c18d8ba25a7dc3043e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://lab.lcr.gr/microhacks/micro_bevy_musicbox" description = "Opinionated service interface for bevy_kira_audio" [features] -default-features = [] +default = [] serde = ["dep:serde"] [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 2a52e91f53d8132d9708af96d184dd0be81c2a75..0a68d8c4c1d3b63b2bb35805e5586e669a7c9bc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,28 @@ +//! micro_musicbox provides a convenience wrapper around bevy_kira_audio, handling all the +//! setup for the common game audio scenario. This includes channel management, giving you +//! control of the audio levels for your music, ambiance, sound effects and UI separately +//! from the start. +//! +//! ## Quickstart +//! +//! - Implement `SuppliesAudio` for a resource (or use the built-in impl on `AssetServer`) +//! - Include the MusixBocPlugin plugin, or the CombinedAudioPlugins plugin group in your app, providing your `SuppliesAudio` impl as the generic parameter +//! - Use `MusicBox<T: SuppliesAudio>` as a parameter on a system +//! - Call one of the `MusicBox::play_*` methods to play sounds +//! +//! ```rust +//! # use bevy::prelude::*; +//! # use micro_musicbox::prelude::*; +//! # use micro_musicbox::CombinedAudioPlugins; +//! fn main() { +//! App::new() +//! .add_plugins(CombinedAudioPlugins::<AssetServer>::new()) +//! .add_startup_system(|mut music_box: MusicBox<AssetServer>| { +//! music_box.play_music("music/bing_bong.mp3"); +//! }); +//! } +//! ``` + use std::marker::PhantomData; use bevy::app::{App, CoreStage, Plugin, PluginGroup, PluginGroupBuilder}; @@ -9,8 +34,7 @@ use crate::channels::{ use crate::music_box::MusicBoxState; use crate::utilities::{AudioSettings, SuppliesAudio}; -/// Musicbox encapsulates the concept of different sound types in a number of preset channels, that -/// can be used in certain ways +/// The available channels that you can play audio on pub mod channels; pub mod music_box; pub mod utilities; @@ -26,6 +50,10 @@ pub mod prelude { pub use super::utilities::AudioSettings; } +/// A Bevy plugin that sets up all of the audio channels, +/// creates the required settings resources, and configures +/// syncing volume levels for a `MusicBox` that uses the supplied +/// `T` parameter for fetching audio pub struct MusicBoxPlugin<T: SuppliesAudio> { _t: PhantomData<T>, } @@ -39,6 +67,7 @@ impl<T: SuppliesAudio> Default for MusicBoxPlugin<T> { } impl<T: SuppliesAudio> MusicBoxPlugin<T> { + /// Create a new MusicBoxPlugin pub fn new() -> MusicBoxPlugin<T> { Default::default() } @@ -56,6 +85,8 @@ impl<T: SuppliesAudio> Plugin for MusicBoxPlugin<T> { } } +/// A Bevy plugin group that adds `bevy_kira_audio` as well as the +/// plugin required to be able to use a `MusicBox` pub struct CombinedAudioPlugins<T: SuppliesAudio> { _t: PhantomData<T>, } diff --git a/src/music_box.rs b/src/music_box.rs index 7028bf29ee860dd41d263c5c570e63adf3339705..1d7c74824d585d32602f90bc692ba2f9e4336a81 100644 --- a/src/music_box.rs +++ b/src/music_box.rs @@ -7,6 +7,7 @@ use bevy_kira_audio::{AudioChannel, AudioControl, AudioInstance, AudioSource, Au use crate::utilities::{AudioSettings, SuppliesAudio, TrackType}; use crate::{AmbianceAudioChannel, MusicAudioChannel, SfxAudioChannel, UiSfxAudioChannel}; +/// A wrapper for each of the audio channels created and controlled by MusicBox #[derive(SystemParam)] pub struct AudioChannels<'w, 's> { pub music_channel: Res<'w, AudioChannel<MusicAudioChannel>>, @@ -18,6 +19,12 @@ pub struct AudioChannels<'w, 's> { _p: PhantomData<&'s ()>, } +/// The service object used to control your game's audio +/// +/// ## `T: SuppliesAudio` +/// +/// The particular implementation of `SuppliesAudio` will be used to verify that a music track exists, +/// and then to retrieve the associated `AudioSource`. #[derive(SystemParam)] pub struct MusicBox<'w, 's, T: SuppliesAudio> { channels: AudioChannels<'w, 's>, @@ -36,6 +43,12 @@ pub struct MusicBoxState { } impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { + /// Start playing a new audio track on the Music channel. The provided tween will be used + /// to fade in the new track, and to fade out the old track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn cross_fade_music<Name: ToString>( &mut self, name: Name, @@ -45,6 +58,13 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { self.fade_in_music(name, fade) } + /// Start playing a new audio track on the Music channel. The "in_tween" will be used to fade + /// in the new audio track, while the "out_tween" will be used to fade out the old audio + /// track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn in_out_fade_music<Name: ToString>( &mut self, name: Name, @@ -55,15 +75,24 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { self.fade_in_music(name, in_tween) } + /// Start playing a new audio track on the Music channel. If another track is playing, it will + /// be immediately stopped + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_music<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { self.stop_music(); self.fade_in_music(name, AudioTween::default()) } + /// Stop playing music on the Music channel pub fn stop_music(&mut self) { self.fade_out_music(AudioTween::default()); } + /// Stop playing music on the music channel. The supplied tween will be used to fade out the track + /// before it ends pub fn fade_out_music(&mut self, fade: AudioTween) { let handle = std::mem::replace(&mut self.state.active_music, None) .and_then(|handle| self.audio_instances.get_mut(&handle)); @@ -72,6 +101,12 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { } } + /// Start playing a new track on the music channel. If another track is playing, it will be + /// immediately stopped. The provided tween will be used to fade in the new track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn fade_in_music<Name: ToString>( &mut self, name: Name, @@ -94,6 +129,12 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { } } + /// Start playing a new audio track on the Music channel. The provided tween will be used + /// to fade in the new track, and to fade out the old track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn cross_fade_ambiance<Name: ToString>( &mut self, name: Name, @@ -103,6 +144,13 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { self.fade_in_ambiance(name, fade) } + /// Start playing a new audio track on the Music channel. The "in_tween" will be used to fade + /// in the new audio track, while the "out_tween" will be used to fade out the old audio + /// track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn in_out_fade_ambiance<Name: ToString>( &mut self, name: Name, @@ -113,15 +161,24 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { self.fade_in_ambiance(name, in_tween) } + /// Start playing a new audio track on the Music channel. If another track is playing, it will + /// be immediately stopped + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_ambiance<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { self.stop_ambiance(); self.fade_in_ambiance(name, AudioTween::default()) } + /// Stop playing ambiance on the Music channel pub fn stop_ambiance(&mut self) { self.fade_out_ambiance(AudioTween::default()); } + /// Stop playing ambiance on the ambiance channel. The supplied tween will be used to fade out the track + /// before it ends pub fn fade_out_ambiance(&mut self, fade: AudioTween) { let handle = std::mem::replace(&mut self.state.active_ambiance, None) .and_then(|handle| self.audio_instances.get_mut(&handle)); @@ -130,6 +187,12 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { } } + /// Start playing a new track on the ambiance channel. If another track is playing, it will be + /// immediately stopped. The provided tween will be used to fade in the new track + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn fade_in_ambiance<Name: ToString>( &mut self, name: Name, @@ -152,6 +215,11 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { } } + /// Play a new sound effect on the SFX channel + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { @@ -161,7 +229,11 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { TrackType::Missing => None, } } - + /// Play a new sound effect on the UI SFX channel + /// + /// # Returns + /// + /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_ui_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { @@ -172,6 +244,8 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { } } + /// Sync the actual volumes of each track to their values defined in the `AudioSettings` resource, + /// modulated by the master volume setting pub fn sync_settings(&self) { self.channels .music_channel @@ -187,26 +261,38 @@ impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { .set_volume((self.settings.ui_volume * self.settings.master_volume) as f64); } + /// Get a reference to the settings object pub fn settings(&self) -> &AudioSettings { &self.settings } + /// Get a mutable reference to the settings object pub fn settings_mut(&mut self) -> &mut AudioSettings { &mut self.settings } + /// Sets the master volume level, as a percentage between 0-1. + /// Master volume is used to adjust all of the other volume levels pub fn set_master_volume(&mut self, level: f32) { self.settings.master_volume = level; } + + /// Sets the music volume level, as a percentage between 0-1. pub fn set_music_volume(&mut self, level: f32) { self.settings.music_volume = level; } + + /// Sets the ambiance volume level, as a percentage between 0-1. pub fn set_ambiance_volume(&mut self, level: f32) { self.settings.ambiance_volume = level; } + + /// Sets the sfx volume level, as a percentage between 0-1. pub fn set_sfx_volume(&mut self, level: f32) { self.settings.sfx_volume = level; } + + /// Sets the ui sfx volume level, as a percentage between 0-1. pub fn set_ui_sfx_volume(&mut self, level: f32) { self.settings.ui_volume = level; } diff --git a/src/utilities.rs b/src/utilities.rs index 1c02aa9404439422c19953095a4b73501bab1ed2..24d3a4f03dc21649d24eb912f9210a24db35c7bb 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -4,6 +4,29 @@ use bevy_kira_audio::AudioSource; use crate::music_box::MusicBox; +/// This trait provides a `MusicBox` with a way of resolving a track name +/// into usable audio resources that is generic over however you implement your +/// asset loading +/// +/// # Example +/// +/// The canonical example is also included in the crate; using the `AssetServer` +/// as a `SuppliesAudio` extension: +/// +/// ```rust +/// # use micro_musicbox::utilities::{TrackType, SuppliesAudio}; +/// # use bevy::prelude::*; +/// +/// impl SuppliesAudio for AssetServer { +/// fn resolve_track_name<T: ToString>(&self, name: T) -> TrackType<String> { +/// TrackType::Single(name.to_string()) +/// } +/// +/// fn get_audio_track<T: ToString>(&self, name: T) -> Option<Handle<AudioSource>> { +/// Some(self.load(&name.to_string())) +/// } +/// } +/// ``` pub trait SuppliesAudio: Resource { fn resolve_track_name<T: ToString>(&self, name: T) -> TrackType<String>; fn get_audio_track<T: ToString>(&self, name: T) -> Option<Handle<AudioSource>>; @@ -31,9 +54,14 @@ impl Default for AudioSettings { } } +/// Defines the type of track that a given name represents pub enum TrackType<T> { + /// A single audio track that should be played as-is Single(T), + /// Represents two tracks; the first is played once as the intro, and the second track will then be looped + #[deprecated = "bevy_kira_audio no longer supports this functionality; the first track in the tuple will be ignored"] WithIntro(T, T), + /// The requested track could not be found Missing, } @@ -47,6 +75,9 @@ impl SuppliesAudio for AssetServer { } } +/// A bevy system that triggers the MusicBox to sync its internal channels with +/// the AudioSettings resource. This is automatically added when you use `MusicBoxPlugin` or +/// `CombinedAudioPlugins` pub fn sync_music_volume<T: SuppliesAudio>(music: MusicBox<T>) { music.sync_settings(); }