add sound effects

This commit is contained in:
Jim 2025-09-02 21:13:21 -04:00
parent 86f2345667
commit 587b902e79
Signed by: jim
GPG key ID: 3D7D94BA53088BF4
39 changed files with 234 additions and 40 deletions

4
Cargo.lock generated
View file

@ -1628,6 +1628,7 @@ dependencies = [
"bevy",
"combat",
"input",
"rand",
"resources",
"state",
"ui_health_bar",
@ -5376,6 +5377,7 @@ version = "0.1.0"
dependencies = [
"bevy",
"bevy_egui",
"rand",
]
[[package]]
@ -5410,6 +5412,7 @@ dependencies = [
"input",
"resources",
"state",
"ui",
"ui_state",
"upgrade",
]
@ -5423,6 +5426,7 @@ dependencies = [
"combat",
"music",
"state",
"ui",
]
[[package]]

View file

@ -1,7 +1,7 @@
(
map: {
MouseButton(Middle): [
CameraOrbitEnable,
KeyCode(ShiftLeft): [
MultiPlaceTower,
],
MouseButton(Right): [
PlaceTower,
@ -9,8 +9,8 @@
MouseButton(Left): [
CameraPanEnable,
],
KeyCode(ShiftLeft): [
MultiPlaceTower,
MouseButton(Middle): [
CameraOrbitEnable,
],
MouseScroll: [
CameraZoom,

BIN
assets/sounds/boom1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/click1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/gasp1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/gasp2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/gasp3.ogg Normal file

Binary file not shown.

BIN
assets/sounds/gust1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/gust2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/megathump.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/sounds/pop1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/pop2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/pop3.ogg Normal file

Binary file not shown.

BIN
assets/sounds/revthump1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/revthump2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/thump1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/thump2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/weird1.ogg Normal file

Binary file not shown.

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
bevy = { workspace = true }
avian3d = { workspace = true }
rand = { workspace = true }
combat = { path = "../combat" }
state = { path = "../state" }
resources = { path = "../resources" }

View file

@ -10,6 +10,7 @@ use bevy::{
};
use combat::{Defense, Health, Range, Team};
use input::Selectable;
use rand::{Rng, thread_rng};
use resources::{Cost, Resources};
use state::GameSystemSet;
use ui_health_bar::BarSettings;
@ -45,6 +46,7 @@ pub struct BuildingInfo {
pub preview: Entity,
pub size: UVec3,
pub unlocked: bool,
pub place_sound: Handle<AudioSource>,
}
impl BuildingInfo {
@ -189,6 +191,16 @@ fn place_building(
continue;
}
commands.spawn((
AudioPlayer(info.place_sound.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Despawn,
speed: thread_rng().gen_range(0.8..1.2),
spatial: false,
..default()
},
Transform::from_translation(position),
));
let mut ec = commands.spawn((
// adjust model up slightly so base of all towers are aligned
Transform::from_translation(position),

View file

@ -8,7 +8,7 @@ use avian3d::prelude::{Collider, LinearVelocity, RigidBody};
use bevy::prelude::*;
use building::{BuildingInfo, BuildingPreview, PREVIEW_ALPHA, PopulatePlacedBuilding};
use combat::{
Defense, Health, Range,
DeathAudio, Defense, Health, Range,
projectiles::{Fire, Projectile, ProjectileLauncher, get_shotgun_launch_vectors},
};
use combat_damagers::ContactDamage;
@ -36,6 +36,8 @@ struct BuildingResources {
projectile_material: Handle<StandardMaterial>,
projectile_collider: Collider,
info_entity: Entity,
death_audio: Handle<AudioSource>,
shoot_audio: Handle<AudioSource>,
}
const SIZE: UVec3 = UVec3::splat(2);
@ -45,6 +47,7 @@ fn register(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
let mesh = meshes.add(Cuboid::from_length(2.0));
let base_material = StandardMaterial {
@ -149,6 +152,7 @@ fn register(
preview,
size: SIZE,
unlocked: true,
place_sound: assets.load("sounds/thump1.ogg"),
},
Range(10.0),
Health::new(20.0),
@ -167,6 +171,8 @@ fn register(
projectile_mesh: meshes.add(Cuboid::from_size(PROJ_SIZE)),
projectile_collider: Collider::cuboid(PROJ_SIZE.x, PROJ_SIZE.y, PROJ_SIZE.z),
info_entity: info,
death_audio: assets.load("sounds/thump1.ogg"),
shoot_audio: assets.load("sounds/pop1.ogg"),
});
}
@ -185,12 +191,14 @@ fn populate(
projectile_count: 5,
},
ContactDamage::new(2.0),
DeathAudio(res.death_audio.clone()),
ProjectileLauncher::new(
Duration::from_secs(1),
5.0,
Vec3::Y * (SIZE.y as f32 + 0.5),
Duration::from_secs(10),
10.0_f32.to_radians(),
res.shoot_audio.clone(),
),
Mesh3d(res.mesh.clone()),
MeshMaterial3d(res.material.clone()),

View file

@ -4,7 +4,7 @@
use bevy::prelude::*;
use building::{BuildingInfo, BuildingPreview, Core, PREVIEW_ALPHA, PopulatePlacedBuilding};
use combat::{Defense, Died, Health, Range};
use combat::{DeathAudio, Defense, Died, Health, Range};
use resources::{AvailableWorkers, Quantity};
use state::{CoreState, GameState};
use ui_range_indicator::CustomRangeIndicator;
@ -23,6 +23,7 @@ struct CoreResources {
mesh: Handle<Mesh>,
material: Handle<StandardMaterial>,
info_entity: Entity,
death_sound: Handle<AudioSource>,
}
#[derive(Component)]
@ -34,6 +35,7 @@ fn register(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
let mesh = meshes.add(Cuboid::from_length(2.0));
let base_material = StandardMaterial {
@ -65,6 +67,7 @@ fn register(
preview,
size: SIZE,
unlocked: false,
place_sound: assets.load("sounds/megathump.ogg"),
},
UpgradeTree { upgrades },
Name::new("Core"),
@ -76,6 +79,7 @@ fn register(
mesh: mesh,
material: material,
info_entity: info,
death_sound: assets.load("sounds/megathump2.ogg"),
});
}
@ -157,6 +161,7 @@ fn populate(
Core,
Mesh3d(res.mesh.clone()),
MeshMaterial3d(res.material.clone()),
DeathAudio(res.death_sound.clone()),
CustomRangeIndicator::new(materials.add(StandardMaterial {
alpha_mode: AlphaMode::Blend,
base_color: Color::srgba(0.6, 0.2, 0.2, 0.4),

View file

@ -4,7 +4,7 @@
use bevy::prelude::*;
use building::{BuildingInfo, BuildingPreview, PREVIEW_ALPHA, PopulatePlacedBuilding};
use combat::{Defense, Health};
use combat::{DeathAudio, Defense, Health};
use combat_damagers::ContactDamage;
use resources::{Cost, Quantity};
use upgrade::{Upgrade, UpgradeApplied, UpgradeTree};
@ -25,6 +25,7 @@ struct ImpalerResources {
mesh: Handle<Mesh>,
material: Handle<StandardMaterial>,
info_entity: Entity,
death_audio: Handle<AudioSource>,
}
const SIZE: UVec3 = UVec3::ONE;
@ -33,6 +34,7 @@ fn register(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
let mesh = meshes.add(Cuboid::from_length(1.0));
let base_material = StandardMaterial {
@ -143,6 +145,7 @@ fn register(
preview,
size: SIZE,
unlocked: true,
place_sound: assets.load("sounds/thump1.ogg"),
},
UpgradeTree { upgrades },
Cost(Quantity { souls: 5, bones: 0 }),
@ -153,6 +156,7 @@ fn register(
mesh: mesh,
material: material,
info_entity: info,
death_audio: assets.load("sounds/pop3.ogg"),
});
}
@ -168,6 +172,7 @@ fn populate(
Impaler,
ContactDamage::new(5.0),
Mesh3d(res.mesh.clone()),
DeathAudio(res.death_audio.clone()),
MeshMaterial3d(res.material.clone()),
));
}

View file

@ -8,7 +8,7 @@ use avian3d::prelude::{Collider, GravityScale, LinearVelocity, RigidBody};
use bevy::prelude::*;
use building::{BuildingInfo, BuildingPreview, PREVIEW_ALPHA, PopulatePlacedBuilding};
use combat::{
Defense, Health, Range, Team,
DeathAudio, Defense, Health, Range, Team,
projectiles::{Fire, Projectile, ProjectileLauncher},
};
use combat_damagers::ContactDamage;
@ -38,6 +38,8 @@ struct BuildingResources {
projectile_material: Handle<StandardMaterial>,
projectile_collider: Collider,
info_entity: Entity,
death_audio: Handle<AudioSource>,
shoot_audio: Handle<AudioSource>,
}
const SIZE: UVec3 = UVec3::new(1, 4, 1);
@ -47,6 +49,7 @@ fn register(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
let mesh = meshes.add(Cuboid::from_size(SIZE.as_vec3()));
let base_material = StandardMaterial {
@ -157,6 +160,7 @@ fn register(
preview,
size: SIZE,
unlocked: true,
place_sound: assets.load("sounds/thump1.ogg"),
},
Range(10.0),
Health::new(10.0),
@ -175,6 +179,8 @@ fn register(
projectile_mesh: meshes.add(Cuboid::from_size(PROJ_SIZE)),
projectile_collider: Collider::cuboid(PROJ_SIZE.x, PROJ_SIZE.y, PROJ_SIZE.z),
info_entity: info,
death_audio: assets.load("sounds/revthump1.ogg"),
shoot_audio: assets.load("sounds/pop2.ogg"),
});
}
@ -194,6 +200,7 @@ fn populate(
shot_velocity: 20.,
},
ContactDamage::new(2.0),
DeathAudio(res.death_audio.clone()),
ProjectileLauncher {
custom_targeting: true,
..ProjectileLauncher::new(
@ -202,6 +209,7 @@ fn populate(
Vec3::Y * (SIZE.y as f32 + 0.5),
Duration::from_secs(10),
0.,
res.shoot_audio.clone(),
)
},
Mesh3d(res.mesh.clone()),

View file

@ -11,7 +11,7 @@ use avian3d::prelude::*;
use bevy::prelude::*;
use building::{BuildingInfo, BuildingPreview, PREVIEW_ALPHA, PopulatePlacedBuilding};
use combat::{
Attacked, Defense, Died, Health, Range, Team,
Attacked, DeathAudio, Defense, Died, Health, Range, Team,
projectiles::{Fire, Projectile, ProjectileLauncher, get_launch_vector},
};
use combat_damagers::ContactDamage;
@ -47,6 +47,9 @@ struct BuildingResources {
projectile_material: Handle<StandardMaterial>,
projectile_collider: Collider,
info_entity: Entity,
death_audio: Handle<AudioSource>,
shoot_audio: Handle<AudioSource>,
explosion_audio: Handle<AudioSource>,
}
const SIZE: UVec3 = UVec3::new(3, 1, 3);
@ -56,6 +59,7 @@ fn register(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
let mesh = meshes.add(Cuboid::from_size(SIZE.as_vec3()));
let base_material = StandardMaterial {
@ -178,6 +182,7 @@ fn register(
preview,
size: SIZE,
unlocked: true,
place_sound: assets.load("sounds/thump2.ogg"),
},
Range(20.0),
Health::new(50.0),
@ -196,6 +201,9 @@ fn register(
projectile_mesh: meshes.add(Cuboid::from_size(PROJ_SIZE)),
projectile_collider: Collider::cuboid(PROJ_SIZE.x, PROJ_SIZE.y, PROJ_SIZE.z),
info_entity: info,
death_audio: assets.load("sounds/revthump2.ogg"),
shoot_audio: assets.load("sounds/gust2.ogg"),
explosion_audio: assets.load("sounds/boom1.ogg"),
});
}
@ -218,6 +226,7 @@ fn populate(
pellet_damage: 10.,
pellet_speed: 50.,
},
DeathAudio(res.death_audio.clone()),
ContactDamage::new(2.0),
ProjectileLauncher::new(
Duration::from_secs(3),
@ -225,6 +234,7 @@ fn populate(
Vec3::Y * (SIZE.y as f32 + 0.5),
Duration::from_secs(10),
0.,
res.shoot_audio.clone(),
),
Mesh3d(res.mesh.clone()),
MeshMaterial3d(res.material.clone()),
@ -256,6 +266,7 @@ fn on_fire(
Transform::from_translation(trigger.event().spawn_pos),
Projectile::from(&trigger.event().launcher),
Projectile::collision_layers(trigger.event().team),
DeathAudio(res.explosion_audio.clone()),
SuncannonProjectile(suncannon.clone()),
LinearVelocity(v),
res.projectile_collider.clone(),

View file

@ -9,6 +9,7 @@ use std::time::Duration;
use avian3d::prelude::*;
use bevy::prelude::*;
use physics::PhysicsLayers;
use rand::{Rng, thread_rng};
use resources::{Drops, Resources};
use state::GameSystemSet;
@ -96,6 +97,9 @@ pub struct Health {
pub current: f32,
}
#[derive(Component)]
pub struct DeathAudio(pub Handle<AudioSource>);
impl Health {
pub fn new(health: f32) -> Self {
Self {
@ -245,13 +249,26 @@ fn on_attack(
fn on_die(
trigger: Trigger<Died>,
names: Query<&Name>,
drops: Query<&Drops>,
dying_query: Query<(&Drops, &GlobalTransform, Option<&DeathAudio>)>,
mut resources: ResMut<Resources>,
mut commands: Commands,
) {
if let Ok(drop) = drops.get(trigger.target()) {
if let Ok((drop, gtf, opt_audio)) = dying_query.get(trigger.target()) {
resources.0 = resources.0 + drop.0;
if let Some(audio) = opt_audio {
commands.spawn((
AudioPlayer(audio.0.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Despawn,
speed: thread_rng().gen_range(0.8..1.2),
spatial: false,
..default()
},
Transform::from_translation(gtf.translation()),
));
}
}
if let Ok(mut ec) = commands.get_entity(trigger.target()) {
info!(
"{:?} killed {:?}!",

View file

@ -61,6 +61,7 @@ pub struct ProjectileLauncher {
pub damage: f32,
/// will set fields in the fire event to be defaults, responsiblity of the building to set them up
pub custom_targeting: bool,
pub shoot_sound: Handle<AudioSource>,
}
impl ProjectileLauncher {
@ -70,6 +71,7 @@ impl ProjectileLauncher {
fire_offset: Vec3,
lifetime: Duration,
spread: f32,
shoot_sound: Handle<AudioSource>,
) -> Self {
Self {
fire_timer: Timer::new(fire_interval, TimerMode::Once),
@ -78,6 +80,7 @@ impl ProjectileLauncher {
fire_offset,
lifetime,
custom_targeting: false,
shoot_sound: shoot_sound.clone(),
}
}
@ -142,6 +145,16 @@ fn fire_launcher(
},
[launcher_entity],
);
commands.spawn((
AudioPlayer(launcher.shoot_sound.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Despawn,
speed: thread_rng().gen_range(0.8..1.2),
spatial: false,
..default()
},
Transform::from_translation(gtf.translation()),
));
continue;
}
let Some((closest_enemy, enemy_pos)) = enemies
@ -180,6 +193,16 @@ fn fire_launcher(
},
[launcher_entity],
);
commands.spawn((
AudioPlayer(launcher.shoot_sound.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Despawn,
speed: thread_rng().gen_range(0.8..1.2),
spatial: false,
..default()
},
Transform::from_translation(gtf.translation()),
));
}
}

View file

@ -5,7 +5,7 @@
use avian3d::prelude::*;
use bevy::prelude::*;
use building::Core;
use combat::{GaveDamage, Health, Team};
use combat::{DeathAudio, GaveDamage, Health, Team};
use combat_damagers::ContactDamage;
use inhabitant::Inhabitant;
use state::{BasePhaseState, GameSystemSet};
@ -35,12 +35,14 @@ struct CoranthResources {
material: Handle<StandardMaterial>,
mesh: Handle<Mesh>,
charge_timer: Timer,
death_sound: Handle<AudioSource>,
}
fn init(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
commands.insert_resource(CoranthResources {
material: materials.add(StandardMaterial {
@ -51,6 +53,7 @@ fn init(
}),
mesh: meshes.add(Sphere::new(0.25)),
charge_timer: Timer::from_seconds(1.5, TimerMode::Repeating),
death_sound: assets.load("sounds/gasp1.ogg"),
});
}
fn spawn(
@ -69,6 +72,7 @@ fn spawn(
MeshMaterial3d(res.material.clone()),
MaxLinearSpeed(25.),
Coranth,
DeathAudio(res.death_sound.clone()),
transform.clone(),
Health::new(1.0 + phase as f32),
ContactDamage::new(if phase < 8 { 4.0 } else { 5.0 }),

View file

@ -5,7 +5,7 @@
use avian3d::prelude::*;
use bevy::prelude::*;
use building::Core;
use combat::{Defense, Health, Team};
use combat::{DeathAudio, Defense, Health, Team};
use combat_damagers::ContactDamage;
use inhabitant::Inhabitant;
use inhabitant_simbert::SpawnSimbert;
@ -40,6 +40,7 @@ pub struct SpawnEndrake {
struct EndrakeResources {
material: Handle<StandardMaterial>,
mesh: Handle<Mesh>,
death_sound: Handle<AudioSource>,
}
const BASE_RADIUS: f32 = 3.;
@ -50,6 +51,7 @@ fn init(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
commands.insert_resource(EndrakeResources {
material: materials.add(StandardMaterial {
@ -64,6 +66,7 @@ fn init(
radius_bottom: BASE_RADIUS,
height: HEIGHT,
}),
death_sound: assets.load("sounds/weird1.ogg"),
});
}
fn spawn(
@ -74,20 +77,23 @@ fn spawn(
) {
for SpawnEndrake { transform } in reader.read() {
commands.spawn((
RigidBody::Dynamic,
Collider::cuboid(TOP_RADIUS * 2., HEIGHT, TOP_RADIUS * 2.),
Mesh3d(res.mesh.clone()),
MeshMaterial3d(res.material.clone()),
MaxLinearSpeed(0.5),
Mass(10.),
Endrake {
spawn_timer: Timer::from_seconds(2.0, TimerMode::Repeating),
spawn_count: 10,
},
Drops(Quantity {
souls: 50,
bones: 25,
}),
(
RigidBody::Dynamic,
Collider::cuboid(TOP_RADIUS * 2., HEIGHT, TOP_RADIUS * 2.),
Mesh3d(res.mesh.clone()),
MeshMaterial3d(res.material.clone()),
MaxLinearSpeed(0.5),
Mass(10.),
Endrake {
spawn_timer: Timer::from_seconds(2.0, TimerMode::Repeating),
spawn_count: 10,
},
Drops(Quantity {
souls: 50,
bones: 25,
}),
),
DeathAudio(res.death_sound.clone()),
transform.clone(),
Health::new(2000.0 + 100. * phase.get().phase_count() as f32),
Defense::new(5.),

View file

@ -5,7 +5,7 @@
use avian3d::prelude::*;
use bevy::prelude::*;
use building::Building;
use combat::{GaveDamage, Health, Team};
use combat::{DeathAudio, GaveDamage, Health, Team};
use combat_damagers::ContactDamage;
use inhabitant::Inhabitant;
use resources::{Drops, Quantity};
@ -38,12 +38,14 @@ struct SimbertResources {
mesh: Handle<Mesh>,
mega_material: Handle<StandardMaterial>,
mega_scale: f32,
death_sound: Handle<AudioSource>,
}
fn init(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
assets: Res<AssetServer>,
) {
commands.insert_resource(SimbertResources {
material: materials.add(StandardMaterial {
@ -60,6 +62,7 @@ fn init(
..default()
}),
mega_scale: 1.3,
death_sound: assets.load("sounds/gasp3.ogg"),
});
}
fn spawn(
@ -93,6 +96,7 @@ fn spawn(
Mesh3d(res.mesh.clone()),
MeshMaterial3d(material),
MaxLinearSpeed(25.),
DeathAudio(res.death_sound.clone()),
Simbert,
drops,
transform,

View file

@ -2,7 +2,7 @@
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
use bevy::prelude::*;
use bevy::{audio::Volume, prelude::*};
use rand::{Rng, thread_rng};
use state::AppState;
@ -21,6 +21,7 @@ impl Plugin for MusicPlugin {
pub struct BackgroundMusicPlayer {
menu_music: Vec<Handle<AudioSource>>,
game_music: Vec<Handle<AudioSource>>,
pub settings: PlaybackSettings,
track_index: usize,
}
@ -35,6 +36,7 @@ fn init(mut commands: Commands, assets: Res<AssetServer>) {
assets.load("music/Egmont Overture.ogg"),
assets.load("music/Ghost Dance.ogg"),
],
settings: PlaybackSettings::default().with_volume(Volume::Linear(0.25)),
track_index: 0,
});
}
@ -48,7 +50,10 @@ fn play_menu_music(
commands
.entity(player_entity)
.remove::<AudioSink>()
.insert(AudioPlayer(player.menu_music[player.track_index].clone()));
.insert((
AudioPlayer(player.menu_music[player.track_index].clone()),
player.settings.clone(),
));
}
fn play_game_music(
@ -60,7 +65,10 @@ fn play_game_music(
commands
.entity(player_entity)
.remove::<AudioSink>()
.insert(AudioPlayer(player.game_music[player.track_index].clone()));
.insert((
AudioPlayer(player.game_music[player.track_index].clone()),
player.settings.clone(),
));
}
fn update_track(
@ -88,6 +96,6 @@ fn update_track(
commands
.entity(entity)
.remove::<AudioSink>()
.insert(AudioPlayer(next_track));
.insert((AudioPlayer(next_track), background_player.settings.clone()));
}
}

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
bevy = { workspace = true }
bevy_egui = { workspace = true }
rand = { workspace = true }
[lints]
workspace = true

View file

@ -4,11 +4,48 @@
use bevy::prelude::*;
use bevy_egui::EguiPlugin;
use rand::{Rng, thread_rng};
pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(EguiPlugin::default());
app.add_plugins(EguiPlugin::default())
.add_event::<PlayClickSound>()
.add_systems(Update, play_click_sound)
.add_systems(Startup, init);
}
}
fn init(assets: Res<AssetServer>, mut commands: Commands) {
commands.insert_resource(UIResources {
click_audio: assets.load("sounds/click1.ogg"),
});
}
#[derive(Resource)]
struct UIResources {
click_audio: Handle<AudioSource>,
}
#[derive(Event)]
pub struct PlayClickSound;
fn play_click_sound(
mut reader: EventReader<PlayClickSound>,
mut commands: Commands,
res: Res<UIResources>,
) {
if reader.is_empty() {
return;
}
reader.clear();
commands.spawn((
AudioPlayer(res.click_audio.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Despawn,
speed: thread_rng().gen_range(0.9..1.1),
..default()
},
));
}

View file

@ -16,6 +16,7 @@ building_placement = { path = "../building_placement" }
building_core = { path = "../building_core" }
upgrade = { path = "../upgrade" }
combat = { path = "../combat" }
ui = { path = "../ui" }
[lints]
workspace = true

View file

@ -18,6 +18,7 @@ use resources::{
AvailableWorkers, Cost, Quantity, ResourceDeposit, Resources, WorkedResourceDeposit,
};
use state::{BasePhaseState, CoreState, GameState, Phase};
use ui::PlayClickSound;
use ui_state::GameUIState;
use upgrade::{UnlockUpgrade, Upgradable, Upgrade};
@ -80,6 +81,7 @@ fn plan_ui(
mut contexts: EguiContexts,
curr_phase: Res<State<BasePhaseState>>,
mut next_phase: ResMut<NextState<BasePhaseState>>,
mut writer: EventWriter<PlayClickSound>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("PlanUI")
@ -92,6 +94,7 @@ fn plan_ui(
.button(RichText::new("EXECUTE").size(VERY_LARGE_FONT_SIZE))
.clicked()
{
writer.write(PlayClickSound);
next_phase.set(BasePhaseState::Execute {
phase: curr_phase.get().phase_count(),
});
@ -202,6 +205,7 @@ fn side_panel(
)>,
mut selected_preview_writer: EventWriter<SelectBuildingPreview>,
mut select_writer: EventWriter<SelectEntity>,
mut writer: EventWriter<PlayClickSound>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("Resources")
@ -214,6 +218,7 @@ fn side_panel(
if let Ok((selected, selected_name)) = selected.single() {
if ui.button("x").clicked() {
select_writer.write(SelectEntity(None));
writer.write(PlayClickSound);
}
ui.heading(selected_name.as_str());
@ -246,6 +251,7 @@ fn side_panel(
ui.add_enabled(false, egui::Button::new("Maximum workers reached"));
} else {
if ui.button("Add worker").clicked() {
writer.write(PlayClickSound);
available_workers.0 = available_workers.0.saturating_sub(1);
if let Some(ref mut worked) = opt_worked {
worked.workers = worked.workers.saturating_add(1);
@ -264,6 +270,7 @@ fn side_panel(
// remove
if let Some(ref mut worked) = opt_worked {
if ui.button("Remove worker").clicked() {
writer.write(PlayClickSound);
worked.workers = worked.workers.saturating_sub(1);
available_workers.0 = available_workers.0.saturating_add(1);
if worked.workers == 0 {
@ -305,6 +312,7 @@ fn side_panel(
RichText::new(upgrade_name.as_str()).size(LARGE_FONT_SIZE);
if resources.0.can_buy(upgrade.cost) {
if ui.button(button_text).clicked() {
writer.write(PlayClickSound);
resources.0 = resources.0 - upgrade.cost;
commands.trigger_targets(
UnlockUpgrade { building: selected },
@ -341,6 +349,7 @@ fn side_panel(
.clicked()
{
commands.entity(selected).despawn();
writer.write(PlayClickSound);
}
}
return;
@ -361,6 +370,7 @@ fn side_panel(
RichText::new(format!("{} - {}", name, cost.0.to_string())).size(FONT_SIZE);
if resources.can_buy(cost.0) {
if ui.button(button_text).clicked() {
writer.write(PlayClickSound);
selected_preview_writer.write(SelectBuildingPreview(Some(info.preview)));
}
} else {

View file

@ -9,6 +9,7 @@ bevy_egui = { workspace = true }
state = { path = "../state" }
combat = { path = "../combat" }
music = { path = "../music" }
ui = { path = "../ui" }
[lints]
workspace = true

View file

@ -10,6 +10,7 @@ use bevy_egui::{
use combat::Difficulty;
use music::BackgroundMusicPlayer;
use state::AppState;
use ui::PlayClickSound;
pub struct UiMenuPlugin;
@ -57,7 +58,11 @@ fn setup_menu(mut commands: Commands) {
));
}
fn main_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<MenuScreen>>) {
fn main_screen(
mut contexts: EguiContexts,
mut next_screen: ResMut<NextState<MenuScreen>>,
mut writer: EventWriter<PlayClickSound>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("Title")
.resizable(false)
@ -81,6 +86,7 @@ fn main_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<Men
.clicked()
{
next_screen.set(MenuScreen::Difficulty);
writer.write(PlayClickSound);
}
});
ui.centered_and_justified(|ui| {
@ -89,6 +95,7 @@ fn main_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<Men
.clicked()
{
next_screen.set(MenuScreen::Info);
writer.write(PlayClickSound);
}
});
ui.centered_and_justified(|ui| {
@ -97,6 +104,7 @@ fn main_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<Men
.clicked()
{
next_screen.set(MenuScreen::Settings);
writer.write(PlayClickSound);
}
});
ui.centered_and_justified(|ui| {
@ -105,6 +113,7 @@ fn main_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<Men
.clicked()
{
next_screen.set(MenuScreen::Credits);
writer.write(PlayClickSound);
}
});
});
@ -115,6 +124,7 @@ fn difficulty_screen(
mut next_screen: ResMut<NextState<MenuScreen>>,
mut next_state: ResMut<NextState<AppState>>,
mut difficulty: ResMut<Difficulty>,
mut writer: EventWriter<PlayClickSound>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("DifficultyScreen")
@ -131,6 +141,7 @@ fn difficulty_screen(
{
*difficulty = Difficulty::Standard;
next_state.set(AppState::Game);
writer.write(PlayClickSound);
}
columns[1].label("The intended experience, which is quite difficult.")
});
@ -145,6 +156,7 @@ fn difficulty_screen(
{
*difficulty = Difficulty::Chill;
next_state.set(AppState::Game);
writer.write(PlayClickSound);
}
columns[1].label(
"Enemies deal significantly less damage. Good to chill and check out the game!",
@ -157,6 +169,7 @@ fn difficulty_screen(
{
*difficulty = Difficulty::Casual;
next_state.set(AppState::Game);
writer.write(PlayClickSound);
}
columns[1].label(
"Enemies deal less damage. Good for players who don't want to sweat as much.",
@ -169,6 +182,7 @@ fn difficulty_screen(
{
*difficulty = Difficulty::Intense;
next_state.set(AppState::Game);
writer.write(PlayClickSound);
}
columns[1].label("Enemies deal more damage. Significantly harder.")
});
@ -184,11 +198,16 @@ fn difficulty_screen(
.clicked()
{
next_screen.set(MenuScreen::Main);
writer.write(PlayClickSound);
}
});
}
fn info_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<MenuScreen>>) {
fn info_screen(
mut contexts: EguiContexts,
mut next_screen: ResMut<NextState<MenuScreen>>,
mut writer: EventWriter<PlayClickSound>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("InfoScreen")
.resizable(false)
@ -215,6 +234,7 @@ fn info_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<Men
.clicked()
{
next_screen.set(MenuScreen::Main);
writer.write(PlayClickSound);
}
});
}
@ -223,7 +243,8 @@ fn settings_screen(
mut contexts: EguiContexts,
mut next_screen: ResMut<NextState<MenuScreen>>,
mut global_volume: ResMut<GlobalVolume>,
mut music_query: Query<&mut AudioSink, With<BackgroundMusicPlayer>>,
mut music_query: Query<(&mut AudioSink, &BackgroundMusicPlayer)>,
mut writer: EventWriter<PlayClickSound>,
mut volume: Local<f32>,
mut init: Local<bool>,
) {
@ -242,8 +263,9 @@ fn settings_screen(
ui.label(RichText::new("Volume").size(FONT_SIZE));
ui.add(egui::Slider::new(&mut *volume, 0.0..=100.));
global_volume.volume = Volume::Linear(*volume / 100.);
if let Ok(mut sink) = music_query.single_mut() {
sink.set_volume(Volume::Linear(*volume / 100.));
if let Ok((mut sink, player)) = music_query.single_mut() {
let player_volume = player.settings.volume.to_linear();
sink.set_volume(Volume::Linear(*volume / 100. * player_volume));
}
});
egui::Window::new("SettingsBack")
@ -257,11 +279,16 @@ fn settings_screen(
.clicked()
{
next_screen.set(MenuScreen::Main);
writer.write(PlayClickSound);
}
});
}
fn credits_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<MenuScreen>>) {
fn credits_screen(
mut contexts: EguiContexts,
mut writer: EventWriter<PlayClickSound>,
mut next_screen: ResMut<NextState<MenuScreen>>,
) {
let ctx = contexts.ctx_mut().unwrap();
egui::Window::new("SettingsScreen")
.resizable(false)
@ -325,6 +352,7 @@ fn credits_screen(mut contexts: EguiContexts, mut next_screen: ResMut<NextState<
.clicked()
{
next_screen.set(MenuScreen::Main);
writer.write(PlayClickSound);
}
});
}

View file

@ -1,7 +1,7 @@
--- VERY IMPORTANT ---
- sound
- music
x music
x limit workers per resource deposit
x main menu
x improve enemy AI