Skip to main content

soundtrack/
soundtrack.rs

1//! This example illustrates how to load and play different soundtracks,
2//! transitioning between them as the game state changes.
3
4use bevy::{audio::Volume, prelude::*};
5
6fn main() {
7    App::new()
8        .add_plugins(DefaultPlugins)
9        .add_systems(Startup, setup)
10        .add_systems(Update, (cycle_game_state, fade_in, fade_out))
11        .add_systems(Update, change_track)
12        .run();
13}
14
15// This resource simulates game states
16#[derive(Resource, Default)]
17enum GameState {
18    #[default]
19    Peaceful,
20    Battle,
21}
22
23// This timer simulates game state changes
24#[derive(Resource)]
25struct GameStateTimer(Timer);
26
27//  This resource will hold the track list for your soundtrack
28#[derive(Resource)]
29struct SoundtrackPlayer {
30    track_list: Vec<Handle<AudioSource>>,
31}
32
33impl SoundtrackPlayer {
34    fn new(track_list: Vec<Handle<AudioSource>>) -> Self {
35        Self { track_list }
36    }
37}
38
39// This component will be attached to an entity to fade the audio in
40#[derive(Component)]
41struct FadeIn;
42
43// This component will be attached to an entity to fade the audio out
44#[derive(Component)]
45struct FadeOut;
46
47fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
48    // Instantiate the game state resources
49    commands.insert_resource(GameState::default());
50    commands.insert_resource(GameStateTimer(Timer::from_seconds(
51        10.0,
52        TimerMode::Repeating,
53    )));
54
55    // Create the track list
56    let track_1 = asset_server.load::<AudioSource>("sounds/Mysterious acoustic guitar.ogg");
57    let track_2 = asset_server.load::<AudioSource>("sounds/Epic orchestra music.ogg");
58    let track_list = vec![track_1, track_2];
59    commands.insert_resource(SoundtrackPlayer::new(track_list));
60}
61
62// Every time the GameState resource changes, this system is run to trigger the song change.
63fn change_track(
64    mut commands: Commands,
65    soundtrack_player: Res<SoundtrackPlayer>,
66    soundtrack: Query<Entity, With<AudioSink>>,
67    game_state: Res<GameState>,
68) {
69    if game_state.is_changed() {
70        // Fade out all currently running tracks
71        for track in soundtrack.iter() {
72            commands.entity(track).insert(FadeOut);
73        }
74
75        // Spawn a new `AudioPlayer` with the appropriate soundtrack based on
76        // the game state.
77        //
78        // Volume is set to start at zero and is then increased by the fade_in system.
79        let track = match game_state.as_ref() {
80            GameState::Peaceful => soundtrack_player.track_list.first().unwrap().clone(),
81            GameState::Battle => soundtrack_player.track_list.get(1).unwrap().clone(),
82        };
83
84        commands.spawn((
85            AudioPlayer(track),
86            PlaybackSettings {
87                mode: bevy::audio::PlaybackMode::Loop,
88                volume: Volume::SILENT,
89                ..default()
90            },
91            FadeIn,
92        ));
93    }
94}
95
96// Fade effect duration
97const FADE_TIME: f32 = 2.0;
98
99// Fades in the audio of entities that has the FadeIn component. Removes the FadeIn component once
100// full volume is reached.
101fn fade_in(
102    mut commands: Commands,
103    mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeIn>>,
104    timer: Res<GameStateTimer>,
105) {
106    for (mut audio, entity) in audio_sink.iter_mut() {
107        audio.set_volume(
108            Volume::SILENT.fade_towards(Volume::Linear(1.0), timer.0.elapsed_secs() / FADE_TIME),
109        );
110        if timer.0.elapsed_secs() >= FADE_TIME {
111            audio.set_volume(Volume::Linear(1.0));
112            commands.entity(entity).remove::<FadeIn>();
113        }
114    }
115}
116
117// Fades out the audio of entities that has the FadeOut component. Despawns the entities once audio
118// volume reaches zero.
119fn fade_out(
120    mut commands: Commands,
121    mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeOut>>,
122    timer: Res<GameStateTimer>,
123) {
124    for (mut audio, entity) in audio_sink.iter_mut() {
125        audio.set_volume(
126            Volume::Linear(1.0).fade_towards(Volume::SILENT, timer.0.elapsed_secs() / FADE_TIME),
127        );
128        if timer.0.elapsed_secs() >= FADE_TIME {
129            commands.entity(entity).despawn();
130        }
131    }
132}
133
134// Every time the timer ends, switches between the "Peaceful" and "Battle" state.
135fn cycle_game_state(
136    mut timer: ResMut<GameStateTimer>,
137    mut game_state: ResMut<GameState>,
138    time: Res<Time>,
139) {
140    timer.0.tick(time.delta());
141    if timer.0.just_finished() {
142        match game_state.as_ref() {
143            GameState::Battle => *game_state = GameState::Peaceful,
144            GameState::Peaceful => *game_state = GameState::Battle,
145        }
146    }
147}