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>
		
			
				
	
	
		
			158 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! 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,
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |