Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a sound effects example #14554

Closed

Conversation

alice-i-cecile
Copy link
Member

@alice-i-cecile alice-i-cecile commented Jul 31, 2024

Objective

Using sound effects is a common use case, and robust idiomatic patterns are hard to discover.

While we could add this as a feature to bevy_audio, the underlying logic is simple and the exact strategies for how these things should be customized are flexible enough that I think it makes more sense as an example, at least for now.

Fixes #12652.

Solution

This PR adds a fairly basic example, demonstrating how to load and play randomized, non-repeating sound effects.

I've tried my best to balance not overengineering the design (but we could use string parsing and folder loading! add in volume control and spatial audio!) and making sure that users don't design themselves into a corner with a nice AsRef<str> pattern that supports both string sound effect ids and enums.

After feedback from reviewers, I've cut the "don't repeat the sound effects" in favor of adding a bit more complexity in the form of custom commands and optional PlaybackSettings support for the sound effects, which allows callers to customize the speed and volume of the SFX.

Testing

The example runs and behaves as expected without crashing. Just use cargo run --example sound_effects.

Showcase

image

@alice-i-cecile alice-i-cecile added C-Examples An addition or correction to our examples A-Audio Sounds playback and modification D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 31, 2024
@alice-i-cecile alice-i-cecile marked this pull request as ready for review August 1, 2024 00:15
) {
for interaction in button_query.iter() {
if *interaction == Interaction::Pressed {
commands.play_sound_effect("button_press");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a const for this to lead by example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved; won't block on that

examples/audio/sound_effects.rs Show resolved Hide resolved
examples/audio/sound_effects.rs Show resolved Hide resolved

#[derive(Resource)]
struct SoundEffects {
map: HashMap<String, Vec<Handle<AudioSource>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't the example use an enum key again? Is it showcase the filepath/asset workflow?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I wanted to showcase both, but couldn't think of a good way to do so without substantially increasing the size of the example. The doc test was my compromise, but I'm not thrilled.

Copy link
Member

@janhohenheim janhohenheim Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the doc comment conveys the intent fairly well. While this example in its current form could be simplified by using enums, I would prefer to merge the current, more future-proof code.

Edit: thinking about this, I'm not so sure anymore. Maybe a comment saying how one could extend the code would be enough?

Copy link
Member

@janhohenheim janhohenheim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally got around to properly looking at this. Looks pretty good, thanks!

There's a bit much of explaining general Rust concepts—like default implementations—for my taste, but I'll trust your intuition here. It's very much possible that this is advanced enough to warrant explanation and I'm simply biased because of my familiarity.

examples/audio/sound_effects.rs Outdated Show resolved Hide resolved
Co-authored-by: Jan Hohenheim <[email protected]>
@alice-i-cecile
Copy link
Member Author

There's a bit much of explaining general Rust concepts—like default implementations—for my taste, but I'll trust your intuition here. It's very much possible that this is advanced enough to warrant explanation and I'm simply biased because of my familiarity.

Yep, this example is targeted at "beginner to Rust who just wants to make games". This example is relatively basic / focused on showcasing good patterns, so I want to be explicit about the techniques being used. When learning, I found these small tricks really confusing and hard to look-up: there's no obvious term to Google!


world.spawn(AudioBundle {
source,
// We want the sound effect to play once and then despawn.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment may be here by mistake, since we pass the settings in general.


#[derive(Resource)]
struct SoundEffects {
map: HashMap<String, Vec<Handle<AudioSource>>>,
Copy link
Member

@janhohenheim janhohenheim Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this not be a tiny bit simpler with a newtype deriving Deref and DeferMut? Imo the name map doesn't add anything here.

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 5, 2024
name: impl AsRef<str>,
settings: PlaybackSettings,
) {
let name = name.as_ref().to_string();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as_ref().to_string() seems like an antipattern. In general converting to owned types should be explicit or be done by the user. Here, it seems like play_sound_effect_with_settings wants to borrow name, but it actually wants to own it! This should probably accept Into<String> instead.

Comment on lines +75 to +78
let index = rng.sample(Uniform::from(0..sfx_list.len()));
// We don't need a (slightly) more expensive strong handle here (which are used to keep assets loaded in memory)
// because a copy is always stored in the SoundEffects resource.
let source = sfx_list[index].clone_weak();
Copy link
Contributor

@bash bash Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this use choose?

///
/// This technique allows us to implement methods for types that we don't own,
/// which can be used as long as the trait is in scope.
trait SfxCommands {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the structs use SoundEffects over Sfx. I have no preference for one or the other, but it would be nice to be consistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropping my preference for Sfx here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some back and forth in bevy_quickstart, I also prefer SFX, although it should not matter much for an example

Co-authored-by: Tau Gärtli <[email protected]>
}
}

impl<'w, 's> SfxCommands for Commands<'w, 's> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl<'w, 's> SfxCommands for Commands<'w, 's> {
impl SfxCommands for Commands<'_, '_> {

@alice-i-cecile
Copy link
Member Author

Closing per TheBevyFlock/bevy_new_2d#264 (comment). We don't have the tools in bevy_audio to do a nonhacky solution IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Audio Sounds playback and modification C-Examples An addition or correction to our examples D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Add an audio example demonstrating how to play randomized sound effects
5 participants