diff --git a/CREDITS.md b/CREDITS.md index 8da7203793..cc8b15083a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -28,6 +28,8 @@ * FiraMono by The Mozilla Foundation and Telefonica S.A (SIL Open Font License, Version 1.1: assets/fonts/FiraMono-LICENSE) * Barycentric from [mk_bary_gltf](https://github.com/komadori/mk_bary_gltf) (MIT OR Apache-2.0) * `MorphStressTest.gltf`, [MorphStressTest] ([CC-BY 4.0] by Analytical Graphics, Inc, Model and textures by Ed Mackey) +* Mysterious acoustic guitar music sample from [florianreichelt](https://freesound.org/people/florianreichelt/sounds/412429/) (CC0 license) +* Epic orchestra music sample, modified to loop, from [Migfus20](https://freesound.org/people/Migfus20/sounds/560449/) ([CC BY 4.0 DEED](https://creativecommons.org/licenses/by/4.0/)) [MorphStressTest]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest [fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox diff --git a/Cargo.toml b/Cargo.toml index 95975225b6..019ce0c6e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1389,6 +1389,17 @@ description = "Shows how to create and register a custom audio source by impleme category = "Audio" wasm = true +[[example]] +name = "soundtrack" +path = "examples/audio/soundtrack.rs" +doc-scrape-examples = true + +[package.metadata.example.soundtrack] +name = "Soundtrack" +description = "Shows how to play different soundtracks based on game state" +category = "Audio" +wasm = true + [[example]] name = "spatial_audio_2d" path = "examples/audio/spatial_audio_2d.rs" diff --git a/assets/sounds/Epic orchestra music.ogg b/assets/sounds/Epic orchestra music.ogg new file mode 100644 index 0000000000..c88b99a237 Binary files /dev/null and b/assets/sounds/Epic orchestra music.ogg differ diff --git a/assets/sounds/Mysterious acoustic guitar.ogg b/assets/sounds/Mysterious acoustic guitar.ogg new file mode 100644 index 0000000000..5dae3e7080 Binary files /dev/null and b/assets/sounds/Mysterious acoustic guitar.ogg differ diff --git a/examples/README.md b/examples/README.md index f4c15815dc..3f749784e7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -220,6 +220,7 @@ Example | Description [Audio Control](../examples/audio/audio_control.rs) | Shows how to load and play an audio file, and control how it's played [Decodable](../examples/audio/decodable.rs) | Shows how to create and register a custom audio source by implementing the `Decodable` type. [Pitch](../examples/audio/pitch.rs) | Shows how to directly play a simple pitch +[Soundtrack](../examples/audio/soundtrack.rs) | Shows how to play different soundtracks based on game state [Spatial Audio 2D](../examples/audio/spatial_audio_2d.rs) | Shows how to play spatial audio, and moving the emitter in 2D [Spatial Audio 3D](../examples/audio/spatial_audio_3d.rs) | Shows how to play spatial audio, and moving the emitter in 3D diff --git a/examples/audio/soundtrack.rs b/examples/audio/soundtrack.rs new file mode 100644 index 0000000000..58473def76 --- /dev/null +++ b/examples/audio/soundtrack.rs @@ -0,0 +1,157 @@ +//! This example illustrates how to load and play different soundtracks, +//! transitioning between them as the game state changes. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, (cycle_game_state, fade_in, fade_out)) + .add_systems(Update, change_track) + .run(); +} + +// This resource simulates game states +#[derive(Resource, Default)] +enum GameState { + #[default] + Peaceful, + Battle, +} + +// This timer simulates game state changes +#[derive(Resource)] +struct GameStateTimer(Timer); + +// This resource will hold the track list for your soundtrack +#[derive(Resource)] +struct SoundtrackPlayer { + track_list: Vec>, +} + +impl SoundtrackPlayer { + fn new(track_list: Vec>) -> Self { + Self { track_list } + } +} + +// This component will be attached to an entity to fade the audio in +#[derive(Component)] +struct FadeIn; + +// This component will be attached to an entity to fade the audio out +#[derive(Component)] +struct FadeOut; + +fn setup(asset_server: Res, mut commands: Commands) { + // Instantiate the game state resources + commands.insert_resource(GameState::default()); + commands.insert_resource(GameStateTimer(Timer::from_seconds( + 10.0, + TimerMode::Repeating, + ))); + + // Create the track list + let track_1 = asset_server.load::("sounds/Mysterious acoustic guitar.ogg"); + let track_2 = asset_server.load::("sounds/Epic orchestra music.ogg"); + let track_list = vec![track_1, track_2]; + commands.insert_resource(SoundtrackPlayer::new(track_list)); +} + +// Every time the GameState resource changes, this system is run to trigger the song change. +fn change_track( + mut commands: Commands, + soundtrack_player: Res, + soundtrack: Query>, + game_state: Res, +) { + if game_state.is_changed() { + // Fade out all currently running tracks + for track in soundtrack.iter() { + commands.entity(track).insert(FadeOut); + } + + // Spawn a new `AudioBundle` with the appropriate soundtrack based on + // the game state. + // + // Volume is set to start at zero and is then increased by the fade_in system. + match game_state.as_ref() { + GameState::Peaceful => { + commands.spawn(( + AudioBundle { + source: soundtrack_player.track_list.first().unwrap().clone(), + settings: PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + volume: bevy::audio::Volume::ZERO, + ..default() + }, + }, + FadeIn, + )); + } + GameState::Battle => { + commands.spawn(( + AudioBundle { + source: soundtrack_player.track_list.get(1).unwrap().clone(), + settings: PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + volume: bevy::audio::Volume::ZERO, + ..default() + }, + }, + FadeIn, + )); + } + } + } +} + +// Fade effect duration +const FADE_TIME: f32 = 2.0; + +// Fades in the audio of entities that has the FadeIn component. Removes the FadeIn component once +// full volume is reached. +fn fade_in( + mut commands: Commands, + mut audio_sink: Query<(&mut AudioSink, Entity), With>, + time: Res