From de75e25ca698857767a4d374fc8fd53c985bd28d Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 17 Dec 2025 17:18:18 -0600 Subject: [PATCH] Add ship thruster sound The player's Ship now has an AudioPlayer component constantly looping a thruster sound effect. It starts paused and is only resumed when the player fires the thruster. As noted in the TODO comment at the top of the input_ship_thruster(...) system, I need to figure out if I want to start using the `Single<>` query parameter instead of a `Query<>` and then doing my own null-ability checks (like what it does now). --- src/lib.rs | 24 ++++++++++++++++++++++-- src/objects.rs | 6 ++++++ src/resources.rs | 7 ++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 94b5b77..e5bd1e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,14 +111,28 @@ fn spawn_camera(mut commands: Commands) { /// Checks if "W" is pressed and increases velocity accordingly. fn input_ship_thruster( keyboard_input: Res>, - mut query: Query<(&mut physics::Velocity, &Transform, &mut Children), With>, + mut query: Query< + ( + &mut physics::Velocity, + &Transform, + Option<&mut AudioSink>, + &mut Children, + ), + With, + >, mut commands: Commands, game_assets: Res, ) { // TODO: Maybe change for a Single> so this only runs for the one ship // buuut... that would silently do nothing if there are 0 or >1 ships, and // I might want to crash on purpose in that case. - let Ok((mut velocity, transform, children)) = query.single_mut() else { + // + // The AudioSink component doesn't exist for just one frame, forcing it to + // be an optional system parameter. I'm not sure if I want to guard it with + // a check like it does now, or finally switch to using a Single<...> query + // parameter. I would lose ship control if the sound sink didn't spawn, but + // that should be fine -- any time that fails, more has likely also failed. + let Ok((mut velocity, transform, audio, children)) = query.single_mut() else { let count = query.iter().count(); panic!("There should be exactly one player ship! Instead, there seems to be {count}."); }; @@ -132,10 +146,16 @@ fn input_ship_thruster( commands .entity(*thrusters) .insert(MeshMaterial2d(game_assets.thruster_mat_active())); + if let Some(audio) = audio { + audio.play(); + } } else { commands .entity(*thrusters) .insert(MeshMaterial2d(game_assets.thruster_mat_inactive())); + if let Some(audio) = audio { + audio.pause(); + } } } diff --git a/src/objects.rs b/src/objects.rs index 6d9fcd2..30d3f1a 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -168,6 +168,12 @@ pub fn spawn_player(mut commands: Commands, game_assets: Res) { Mesh2d(game_assets.ship().0), MeshMaterial2d(game_assets.ship().1), Transform::default().with_scale(Vec3::new(20.0, 20.0, 20.0)), + AudioPlayer::new(game_assets.ship_thruster_sound()), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + paused: true, + ..Default::default() + }, )) .with_child(( Mesh2d(game_assets.thruster_mesh()), diff --git a/src/resources.rs b/src/resources.rs index 251d14d..0df6ca8 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -58,7 +58,7 @@ impl Default for WorldSize { pub struct GameAssets { meshes: [Handle; 5], materials: [Handle; 7], - sounds: [Handle; 3], + sounds: [Handle; 4], } impl GameAssets { @@ -109,6 +109,10 @@ impl GameAssets { pub fn asteroid_crack_sound(&self) -> Handle { self.sounds[2].clone() } + + pub fn ship_thruster_sound(&self) -> Handle { + self.sounds[3].clone() + } } impl FromWorld for GameAssets { @@ -141,6 +145,7 @@ impl FromWorld for GameAssets { loader.load("explosionCrunch_004.ogg"), loader.load("laserSmall_001.ogg"), loader.load("explosionCrunch_000.ogg"), + loader.load("thrusterFire_004.ogg"), ]; GameAssets { meshes,