#[RESOLVED]Simple code for loading audio files to play later

37 messages · Page 1 of 1 (latest)

ebon crow
#

The audio examples available show how to play a sound, once, immediately, and then look focused around music.

I want to load a series of sounds, then play them on demand. I do not want them constantly playing, I want them playing once then stopping.

What I've tried:

pub struct AudioPlugin;

pub struct Sfx;

#[derive(Event)]
pub struct PlaySound { pub sid: usize }

#[derive(Resource)]
pub struct AudioLibrary {
  pub sounds: Vec<Handle<AudioSource>>,
}

impl Plugin for AudioPlugin {
    fn build(&self, app: &mut App) {
         app.add_event::<PlaySound>()
         .insert_resource( AudioLibrary { sounds: Vec::with_capacity(50) } )
         .add_systems(
            OnEnter(GameState::InGame),
            (init_audio, play_audio),
        );
    }
}

fn init_audio(
   asset_server: Res<AssetServer>,
   mut audio_library: ResMut<AudioLibrary>,
) {
   let handle: Handle<AudioSource> = asset_server.load("sfx/stab1.wav");
   audio_library.sounds.push(handle);
   return;
}

fn play_audio(
  mut events: EventReader<PlaySound>,
  mut commands: Commands,
  audio_library: Res<AudioLibrary>,
) {
  for event in events.read() {
    let sid = event.sid;
    let handle = audio_library.sounds[sid];
    commands.spawn(AudioPlayer::new(handle));
  }
}

I get a borrow error because I cannot copy AudioSource:

error[E0507]: cannot move out of index of `std::vec::Vec<bevy::prelude::Handle<bevy::prelude::AudioSource>>`
  --> src/audio.rs:45:18
   |
45 |     let handle = audio_library.sounds[sid];
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `bevy::prelude::Handle<bevy::prelude::AudioSource>`, which does not implement the `Copy` trait
   |

How can I load sounds and play them, later?

fickle stirrup
#

Try clone: let handle = audio_library.sounds[sid].clone();

ebon crow
#

I want to be able to play the same sound effect often, ideally creating a player that self-destructs each time, to support the same sound effect playing over itself

burnt ivy
#

afaik bevy despawns the audio entity by default when it stops playing, but you can control it

ebon crow
#

ok that's good to know!

burnt ivy
ebon crow
#

hm, my audio module isn't reading its event, so I tried to play it manually in the collision module, and I got a crash

burnt ivy
#

you insert this component along with the audio source, and set the mode field

burnt ivy
ebon crow
#
thread 'Compute Task Pool (1)' panicked at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_audio-0.15.1/src/audio_source.rs:102:56:
#
    let handle: Handle<AudioSource> = asset_server.load("sfx/stab1.wav");
    commands.spawn(AudioPlayer::new(handle));
#
src]$ ls ../assets/sfx/
stab1.wav
#

the file exists

burnt ivy
#

hmm, the crash happened in engine code. let me check that real quick

ebon crow
#

oh,

The data must be one of the file formats supported by Bevy (wav, ogg, flac, or mp3). However, support for these file formats is not part of Bevy’s default feature set. In order to be able to use these file formats, you will have to enable the appropriate optional features.

burnt ivy
#

yeah, line 102 in audio_source.rs tris to decode the data and it's probably failed because no audio formats where loaded

ebon crow
#

I see, I need to add an optional feature setting somewhere it sounds like

#

the "wav" feature

burnt ivy
#

you add it to your Cargo.toml:

[dependencies]
bevy = { version = "0.15.1", features = ["wav"] }
ebon crow
#

looks like I do that in the cargo?

#

cool

#

that works!

#

cool

#

hm, I need to get the audio playing from an event - to use the AudioLibrary instead of triggering it directly

#

so in the same spot where I tried manually spawning the component, I have:

#
(fn params mut ew2: EventWriter<PlaySound>,)
...
     ew2.send(PlaySound { sid: 0});
#

then the audio module where PlaySound is imported from:

#
use bevy::prelude::*;

use crate::state::GameState;
use bevy::audio::AudioPlayer;

pub struct AudioPlugin;

#[derive(Event)]
pub struct PlaySound { pub sid: usize }

#[derive(Resource)]
pub struct AudioLibrary {
  pub sounds: Vec<Handle<AudioSource>>,
}

impl Plugin for AudioPlugin {
    fn build(&self, app: &mut App) {
         app.add_event::<PlaySound>()
         .insert_resource( AudioLibrary { sounds: Vec::with_capacity(50) } )
         .add_systems(
            OnEnter(GameState::InGame),
            (init_audio, play_audio),
        );
    }
}

fn init_audio(
   asset_server: Res<AssetServer>,
   mut audio_library: ResMut<AudioLibrary>,
) {
   let handle: Handle<AudioSource> = asset_server.load("sfx/stab1.wav");
   audio_library.sounds.push(handle);
   return;
}

fn play_audio(
  mut events: EventReader<PlaySound>,
  mut commands: Commands,
  audio_library: Res<AudioLibrary>,
) {
  for event in events.read() {
    let sid = event.sid;
    info!("Playing sound {}", sid);
    let handle = audio_library.sounds[sid].clone();
    commands.spawn(AudioPlayer::new(handle));
  }
}
#

the log doesn't trigger. Am I doing the app.add_event right?

burnt ivy
#

OnEnter is only executed once when you enter the state. you certainly want the play_audio system running constantly to process the events:

.add_systems(OnEnter(GameState::InGame), init_audio)
.add_systems(in_state(GameState::InGame), play_audio)
#

ah sorry, that's wrong

#

in_state is a run condition, not a schedule

#
.add_systems(OnEnter(GameState::InGame), init_audio)
.add_systems(Update, play_audio.run_if(in_state(GameState::InGame))
#

now the log should appear normally, and the sound will play

ebon crow
#

sick, it works. thank you!

#

[RESOLVED[Simple code for loading audio files to play later