1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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,
        }
    }
}