docs/bevy/examples/audio/soundtrack.rs 4.5 K · 147 lines · raw · history

1 //! This example illustrates how to load and play different soundtracks,
2 //! transitioning between them as the game state changes.
3
4 use bevy::{audio::Volume, prelude::*};
5
6 fn 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)]
17 enum GameState {
18 #[default]
19 Peaceful,
20 Battle,
21 }
22
23 // This timer simulates game state changes
24 #[derive(Resource)]
25 struct GameStateTimer(Timer);
26
27 // This resource will hold the track list for your soundtrack
28 #[derive(Resource)]
29 struct SoundtrackPlayer {
30 track_list: Vec<Handle<AudioSource>>,
31 }
32
33 impl 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)]
41 struct FadeIn;
42
43 // This component will be attached to an entity to fade the audio out
44 #[derive(Component)]
45 struct FadeOut;
46
47 fn 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.
63 fn 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
97 const 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.
101 fn 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.
119 fn 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.
135 fn 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 }