mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 12:13:25 +00:00
Programmed soundtrack example (#12774)
Created soundtrack example, fade-in and fade-out features, added new assets, and updated credits. # Objective - Fixes #12651 ## Solution - Created a resource to hold the track list. - The audio assets are then loaded by the asset server and added to the track list. - Once the game is in a specific state, an `AudioBundle` is spawned and plays the appropriate track. - The audio volume starts at zero and is then incremented gradually until it reaches full volume. - Once the game state changes, the current track fades out, and a new one fades in at the same time, offering a relatively seamless transition. - Once a track is completely faded out, it is despawned from the app. - Game state changes are simulated through a `Timer` for simplicity. - Track change system is only run if there is a change in the `GameState` resource. - All tracks are used according to their respective licenses. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
622f9a35b6
commit
df76fd4a1b
6 changed files with 171 additions and 0 deletions
|
@ -28,6 +28,8 @@
|
||||||
* FiraMono by The Mozilla Foundation and Telefonica S.A (SIL Open Font License, Version 1.1: assets/fonts/FiraMono-LICENSE)
|
* 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)
|
* 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)
|
* `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
|
[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
|
[fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1389,6 +1389,17 @@ description = "Shows how to create and register a custom audio source by impleme
|
||||||
category = "Audio"
|
category = "Audio"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "spatial_audio_2d"
|
name = "spatial_audio_2d"
|
||||||
path = "examples/audio/spatial_audio_2d.rs"
|
path = "examples/audio/spatial_audio_2d.rs"
|
||||||
|
|
BIN
assets/sounds/Epic orchestra music.ogg
Normal file
BIN
assets/sounds/Epic orchestra music.ogg
Normal file
Binary file not shown.
BIN
assets/sounds/Mysterious acoustic guitar.ogg
Normal file
BIN
assets/sounds/Mysterious acoustic guitar.ogg
Normal file
Binary file not shown.
|
@ -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
|
[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.
|
[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
|
[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 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
|
[Spatial Audio 3D](../examples/audio/spatial_audio_3d.rs) | Shows how to play spatial audio, and moving the emitter in 3D
|
||||||
|
|
||||||
|
|
157
examples/audio/soundtrack.rs
Normal file
157
examples/audio/soundtrack.rs
Normal file
|
@ -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<Handle<AudioSource>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundtrackPlayer {
|
||||||
|
fn new(track_list: Vec<Handle<AudioSource>>) -> 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<AssetServer>, 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::<AudioSource>("sounds/Mysterious acoustic guitar.ogg");
|
||||||
|
let track_2 = asset_server.load::<AudioSource>("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<SoundtrackPlayer>,
|
||||||
|
soundtrack: Query<Entity, With<AudioSink>>,
|
||||||
|
game_state: Res<GameState>,
|
||||||
|
) {
|
||||||
|
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<FadeIn>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (audio, entity) in audio_sink.iter_mut() {
|
||||||
|
audio.set_volume(audio.volume() + time.delta_seconds() / FADE_TIME);
|
||||||
|
if audio.volume() >= 1.0 {
|
||||||
|
audio.set_volume(1.0);
|
||||||
|
commands.entity(entity).remove::<FadeIn>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fades out the audio of entities that has the FadeOut component. Despawns the entities once audio
|
||||||
|
// volume reaches zero.
|
||||||
|
fn fade_out(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeOut>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (audio, entity) in audio_sink.iter_mut() {
|
||||||
|
audio.set_volume(audio.volume() - time.delta_seconds() / FADE_TIME);
|
||||||
|
if audio.volume() <= 0.0 {
|
||||||
|
commands.entity(entity).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every time the timer ends, switches between the "Peaceful" and "Battle" state.
|
||||||
|
fn cycle_game_state(
|
||||||
|
mut timer: ResMut<GameStateTimer>,
|
||||||
|
mut game_state: ResMut<GameState>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
timer.0.tick(time.delta());
|
||||||
|
if timer.0.just_finished() {
|
||||||
|
match game_state.as_ref() {
|
||||||
|
GameState::Battle => *game_state = GameState::Peaceful,
|
||||||
|
GameState::Peaceful => *game_state = GameState::Battle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue