We’ll take a brief detour from texturings to introduce sound!
Audio is great for creating a more engaging experience.
Bevy does have some basic sound support built-in but we’ll be using a more robust plugin called bevy_kira_audio
. bevy_kira_audio integrates the kira audio library with Bevy.
cargo add bevy_kira_audio
We need to add bevy_kira_audio
's plugin to our game in main.rs
.
use bevy_kira_audio::AudioPlugin;
...
.add_plugins(DefaultPlugins)
.add_plugin(AudioPlugin)
.add_plugin(ControlsPlugin)
Our asset loader can handle audio assets as well as the images we’ve been using, so set up an AudioAssets
struct with the same macros we used before. Don’t forget to initialize the collection in the AssetsPlugin
.
We’ve used .ogg
audio files here because ogg is a free, open audio container format that is unrestricted by software patents. If we wanted to publish our bevy game to the web, ogg would be a good choice whereas mp3 might cause us some issues.
impl Plugin for AssetsPlugin {
fn build(&self, app: &mut App) {
app.init_collection::<ImageAssets>()
.init_collection::<AudioAssets>();
}
}
#[derive(AssetCollection)]
pub struct AudioAssets {
#[asset(path = "gameover.ogg")]
pub gameover: Handle<bevy_kira_audio::AudioSource>,
#[asset(path = "apple.ogg")]
pub apple: Handle<bevy_kira_audio::AudioSource>,
}
In tick
in lib.rs
, we’ll play sounds when the snake eats an apple or a gameover happens. We need to use the Audio
resource as well as our AudioAssets
.
pub fn tick(
...
query_board: Query<&Board>,
audio: Res<Audio>,
sounds: Res<AudioAssets>,
) {
Notably, Bevy also has an Audio
struct, so we need to make sure we bring the Audio
from bevy_kira_audio
into scope as that’s the one we’ll be using.
use assets::AudioAssets;
use bevy::prelude::*;
use bevy_kira_audio::Audio;
Once we’re all set up we can play sounds from our audio assets!
In the game over match, we’ll play a one-off gameover
sound by passing a clone of the the gameover
handle to the audio.play
function.
Some(GameOverReason::HitWall)
| Some(GameOverReason::HitSnake)
| Some(GameOverReason::Win) => {
commands.insert_resource(NextState(
GameState::Menu,
));
audio.play(sounds.gameover.clone());
}
We’ll do the same right below where we send the food event.
food_events.send(NewFoodEvent);
audio.play(sounds.apple.clone());
And now when we cargo run
, the snake plays a nice cronch when we eat apples and a gameover sound when we hit a wall.