diff --git a/Cargo.lock b/Cargo.lock index 9cdd6c7..b2fd78d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bevy_hanabi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "371c5bd9edcf42721cd4e769354c221f3c4090e7a1dba0eb0bd6ca29e7828142" +dependencies = [ + "anyhow", + "bevy", + "bitflags", + "bytemuck", + "copyless", + "rand", + "rand_pcg", + "ron", + "serde", +] + [[package]] name = "bevy_hierarchy" version = "0.8.0" @@ -898,12 +915,11 @@ version = "0.1.0" dependencies = [ "bevy", "bevy-inspector-egui", + "bevy_hanabi", "bevy_rapier2d", "cpal 0.14.0", "crossbeam-channel", "hexodsp", - "rand", - "rand_distr", "ticktock", ] @@ -3115,13 +3131,12 @@ dependencies = [ ] [[package]] -name = "rand_distr" -version = "0.4.3" +name = "rand_pcg" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "num-traits", - "rand", + "rand_core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f0c9072..5d6e75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,15 +7,13 @@ edition = "2021" [dependencies] bevy = "0.8.0" -# bevy_hanabi = "0.3.1" +bevy_hanabi = "0.3.1" bevy-inspector-egui = "0.12.1" bevy_rapier2d = "0.16.2" cpal = "0.14.0" crossbeam-channel = "0.5.6" hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP" } ticktock = "0.8.0" -rand = "0.8.5" -rand_distr = "0.4.3" [profile.dev.package."*"] opt-level = 3 diff --git a/src/game.rs b/src/game.rs index abadec2..7b170c1 100644 --- a/src/game.rs +++ b/src/game.rs @@ -10,6 +10,7 @@ use bevy::{ prelude::{shape::Quad, *}, sprite::Mesh2dHandle, }; +use bevy_hanabi::*; use bevy_rapier2d::prelude::*; use std::collections::BTreeSet; @@ -31,8 +32,7 @@ impl Plugin for GamePlugin { .add_system_set( SystemSet::on_update(AppState::Game) .with_system(crate::levels::post_setup_level) - .with_system(keyboard_input_system) - .with_system(character_particle_effect_system), + .with_system(keyboard_input_system), ) .add_system_set(SystemSet::on_update(AppState::Win).with_system(keyboard_input_system)) .add_system_to_stage(CoreStage::PostUpdate, collision_event_system); @@ -101,6 +101,7 @@ fn setup( pub fn spawn_character( commands: &mut Commands, character_meshes: &Res, + effects: &mut ResMut>, materials: &mut ResMut>, selected_character_id: &mut Mut, character_id_list: &mut Mut, @@ -117,6 +118,22 @@ pub fn spawn_character( ); character_id_list.0.insert(character_id); + let mut gradient = Gradient::new(); + gradient.add_key( + 0.0, + (Vec4::from(color) + Vec4::new(0.1, 0.1, 0.1, 0.0)) + .clamp(Vec4::new(0., 0., 0., 0.), Vec4::new(1., 1., 1., 0.)), + ); + gradient.add_key( + 0.2, + (Vec4::from(color) + Vec4::new(0.1, 0.1, 0.1, 0.0)) + .clamp(Vec4::new(0., 0., 0., 0.), Vec4::new(1., 1., 1., 1.)), + ); + gradient.add_key( + 1.0, + (Vec4::from(color) + Vec4::new(0.1, 0.1, 0.1, 0.0)) + .clamp(Vec4::new(0., 0., 0., 0.), Vec4::new(1., 1., 1., 0.)), + ); commands .spawn_bundle(ColorMesh2dBundle { mesh: character_meshes.square.clone(), @@ -139,7 +156,35 @@ pub fn spawn_character( angular_damping: 0.5, }) .insert(ExternalImpulse::default()) - .insert(ActiveEvents::COLLISION_EVENTS); + .insert(ActiveEvents::COLLISION_EVENTS) + .with_children(|c| { + c.spawn_bundle(ParticleEffectBundle { + effect: ParticleEffect::new( + effects.add( + EffectAsset { + name: "Particles".into(), + capacity: 4096, + spawner: Spawner::rate(30.0.into()) + .with_active(selected_character_id.0.is_none()), + ..Default::default() + } + .init(PositionCircleModifier { + radius: 30.0, + speed: 20.0.into(), + dimension: ShapeDimension::Surface, + ..Default::default() + }) + .init(ParticleLifetimeModifier { lifetime: 0.8 }) + .render(SizeOverLifetimeModifier { + gradient: Gradient::constant(Vec2::splat(4.0)), + }) + .render(ColorOverLifetimeModifier { gradient }), + ), + ), + transform: Transform::from_xyz(0., 0., 0.1), + ..Default::default() + }); + }); // If no character is selected, then select this one if selected_character_id.0.is_none() { @@ -154,6 +199,7 @@ fn collision_event_system( mut commands: Commands, character_meshes: Res, mut materials: ResMut>, + mut effects: ResMut>, mut collision_events: EventReader, character_query: Query<(&CharacterId, &CharacterColor, &Transform)>, mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, @@ -189,6 +235,7 @@ fn collision_event_system( spawn_character( &mut commands, &character_meshes, + &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, @@ -204,14 +251,27 @@ fn collision_event_system( fn keyboard_input_system( keyboard_input: Res>, - mut characters: Query<(&CharacterId, &mut Velocity, &CharacterColor)>, + mut characters: Query<(&CharacterId, &mut Velocity, &CharacterColor, &Children)>, mut level_query: Query<(&mut SelectedCharacterId, &CharacterIdList)>, + mut effect: Query<&mut ParticleEffect>, mut app_state: ResMut>, audio: Res>, ) { if let Ok((mut selected_character_id, character_id_list)) = level_query.get_single_mut() { if keyboard_input.just_pressed(KeyCode::Tab) { let selected = if let Some(selected_character_id) = &mut selected_character_id.0 { + if let Some((_character_id, _velocity, _color, children)) = characters + .iter_mut() + .find(|(character_id, _velocity, _color, _children)| { + *character_id == selected_character_id + }) { + effect + .get_mut(children[0]) + .unwrap() + .maybe_spawner() + .unwrap() + .set_active(false); + } *selected_character_id = *character_id_list .0 @@ -224,10 +284,16 @@ fn keyboard_input_system( CharacterId(0) }; - if let Some((_character_id, _velocity, color)) = characters + if let Some((_character_id, _velocity, color, children)) = characters .iter_mut() - .find(|(character_id, _velocity, _color)| **character_id == selected) + .find(|(character_id, _velocity, _color, _children)| **character_id == selected) { + effect + .get_mut(children[0]) + .unwrap() + .maybe_spawner() + .unwrap() + .set_active(true); audio .send(AudioMsg::Color([color.0.r(), color.0.g(), color.0.b()])) .ok(); @@ -240,9 +306,9 @@ fn keyboard_input_system( keyboard_input.pressed(KeyCode::Left) || keyboard_input.pressed(KeyCode::A); if let Some(selected_character_id) = &selected_character_id.0 { - if let Some((_character_id, mut velocity, _color)) = characters + if let Some((_character_id, mut velocity, _color, _children)) = characters .iter_mut() - .find(|(character_id, _velocity, _color)| { + .find(|(character_id, _velocity, _color, _children)| { *character_id == selected_character_id }) { velocity.linvel.x = 200. * (right_pressed as i8 - left_pressed as i8) as f32; @@ -260,25 +326,6 @@ fn keyboard_input_system( } } -fn character_particle_effect_system( - mut characters: Query<(&CharacterId, &Transform, &CharacterColor)>, - mut particle_effect: ResMut, - mut level_query: Query<(&SelectedCharacterId)>, -) { - if let Ok(selected_character_id) = level_query.get_single_mut() { - if let Some(selected_character_id) = &selected_character_id.0 { - if let Some((_character_id, transform, color)) = characters - .iter_mut() - .find(|(character_id, _transform, _color)| { - *character_id == selected_character_id - }) { - particle_effect.translation = transform.translation; - particle_effect.color = color.0; - } - } - } -} - fn win_setup(mut commands: Commands, asset_server: Res) { let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); commands diff --git a/src/levels.rs b/src/levels.rs index 22fda71..cd15cfe 100644 --- a/src/levels.rs +++ b/src/levels.rs @@ -7,6 +7,7 @@ mod level1; use crate::game::*; use bevy::prelude::*; +use bevy_hanabi::*; use std::collections::BTreeSet; pub fn setup_level( @@ -36,6 +37,7 @@ pub fn despawn_level(mut commands: Commands, level_query: Query, + mut effects: ResMut>, mut materials: ResMut>, current_level: Res, mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, @@ -52,6 +54,7 @@ pub fn post_setup_level( 0 => level0::setup( &mut commands, &character_meshes, + &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, @@ -60,6 +63,7 @@ pub fn post_setup_level( 1 => level1::setup( &mut commands, &character_meshes, + &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, diff --git a/src/levels/level0.rs b/src/levels/level0.rs index e95849d..33bcdc4 100644 --- a/src/levels/level0.rs +++ b/src/levels/level0.rs @@ -1,11 +1,13 @@ use crate::game::*; use bevy::prelude::*; +use bevy_hanabi::*; use bevy_rapier2d::prelude::*; pub fn setup( commands: &mut Commands, character_meshes: &Res, + effects: &mut ResMut>, materials: &mut ResMut>, selected_character_id: &mut Mut, character_id_list: &mut Mut, @@ -19,6 +21,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, @@ -29,6 +32,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, @@ -39,6 +43,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, diff --git a/src/levels/level1.rs b/src/levels/level1.rs index 4c29b4c..6489c56 100644 --- a/src/levels/level1.rs +++ b/src/levels/level1.rs @@ -1,11 +1,13 @@ use crate::game::*; use bevy::prelude::*; +use bevy_hanabi::*; use bevy_rapier2d::prelude::*; pub fn setup( commands: &mut Commands, character_meshes: &Res, + effects: &mut ResMut>, materials: &mut ResMut>, selected_character_id: &mut Mut, character_id_list: &mut Mut, @@ -25,6 +27,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, @@ -35,6 +38,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, @@ -45,6 +49,7 @@ pub fn setup( spawn_character( commands, character_meshes, + effects, materials, selected_character_id, character_id_list, diff --git a/src/main.rs b/src/main.rs index 8a48a3c..335358b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use bevy::{ prelude::*, render::settings::{WgpuFeatures, WgpuSettings}, }; +use bevy_hanabi::*; use bevy_rapier2d::prelude::*; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -32,11 +33,11 @@ fn main() { .insert_resource(audio_event_sender) .add_state(AppState::Menu) .add_plugins(DefaultPlugins) + .add_plugin(HanabiPlugin) .add_plugin(RapierPhysicsPlugin::::pixels_per_meter(64.0)) .add_plugin(RapierDebugRenderPlugin::default()) .add_plugin(menu::MenuPlugin) .add_plugin(game::GamePlugin) - .add_plugin(particle_effect::ParticleEffectPlugin) .add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new()) .add_startup_system(setup) .run(); diff --git a/src/particle_effect.rs b/src/particle_effect.rs index 42afdc0..fe922d0 100644 --- a/src/particle_effect.rs +++ b/src/particle_effect.rs @@ -1,13 +1,4 @@ use bevy::{prelude::*, sprite::Mesh2dHandle}; -use rand::Rng; -use rand_distr::{UnitCircle, Distribution}; - -pub const POOL_COUNT: usize = 100; - -pub const MIN_VELOCITY: f32 = 10.0; -pub const MAX_VELOCITY: f32 = 100.0; -pub const VELOCITY_SCALE: f32 = 0.1; -pub const PARTICLE_EFFECT_RADIUS: f32 = 70.0; pub struct ParticleEffectPlugin; @@ -15,9 +6,8 @@ impl Plugin for ParticleEffectPlugin { fn build(&self, app: &mut App) { app .init_resource::() - .insert_resource(ParticleEffectResource::new(Vec3::new(10000.0, 10000.0, 0.0))) - .add_startup_system(particle_effect_startup) - .add_system(particle_effect_system); + .insert_resource(PoolCount(1000)) + .add_startup_system(particle_effect_startup); } } @@ -28,7 +18,6 @@ pub struct ParticleMesh { impl FromWorld for ParticleMesh { fn from_world(world: &mut World) -> Self { let mut meshes = world.get_resource_mut::>().unwrap(); - Self { square: meshes .add(Mesh::from(shape::Quad { @@ -40,87 +29,29 @@ impl FromWorld for ParticleMesh { } } -#[derive(bevy_inspector_egui::Inspectable)] -pub struct ParticleEffectResource { - pub translation: Vec3, - pub prev_translation: Vec3, - pub radius_squared: f32, - pub color: Color, -} +pub struct PoolCount(usize); -impl ParticleEffectResource { - fn new(init_translation: Vec3) -> Self { - Self { - translation: init_translation, - prev_translation: init_translation, - radius_squared: PARTICLE_EFFECT_RADIUS * PARTICLE_EFFECT_RADIUS, - color: Color::WHITE, - } - } -} - -#[derive(Default, Component)] -pub struct ParticleComponent { - pub velocity: Vec3, -} - -impl ParticleComponent { - pub fn new() -> Self { - let mut particle_component: Self = Self::default(); - particle_component.randomize_velocity(MIN_VELOCITY, MAX_VELOCITY); - return particle_component; - } - - pub fn randomize_velocity(&mut self, min_velocity: f32, max_velocity: f32) { - let random_direction: [f32; 2] = UnitCircle.sample(&mut rand::thread_rng()); - let random_magnitude: f32 = rand::thread_rng().gen_range(min_velocity .. max_velocity); - self.velocity = Vec3::new(random_direction[0], random_direction[1], 0.0) * random_magnitude; - } -} +#[derive(Component)] +pub struct ParticleComponent; fn particle_effect_startup( mut commands: Commands, particle_mesh: Res, + pool_count: ResMut, mut materials: ResMut>, ) { - for _p in 0 .. POOL_COUNT { + for _p in 0 .. pool_count.0 { let color_mesh = ColorMesh2dBundle { mesh: particle_mesh.square.clone(), material: materials.add(ColorMaterial::from(Color::WHITE)), + // default to invisible + visibility: Visibility {is_visible: false}, ..default() }; commands .spawn_bundle(color_mesh) - .insert(ParticleComponent::new()); + .insert(ParticleComponent); } -} - -fn particle_effect_system( - mut materials: ResMut>, - mut query: Query<(&mut Transform, &mut ParticleComponent, &Handle)>, - mut particle_effect: ResMut, - time: Res