#![allow(clippy::precedence)] #![allow(clippy::too_many_arguments)] use crate::AppState; use bevy::{ input::{keyboard::KeyCode, Input}, prelude::{shape::Quad, *}, sprite::Mesh2dHandle, }; use bevy_fundsp::prelude::*; use bevy_hanabi::*; use bevy_rapier2d::prelude::*; use std::collections::BTreeSet; pub struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_event::() .init_resource::() .insert_resource(CurrentLevel(None)) .add_system_set(SystemSet::on_enter(AppState::Game).with_system(setup)) .add_system_set( SystemSet::on_update(AppState::Game) .with_system(post_setup_level) .with_system(keyboard_input_system), ) .add_system_to_stage(CoreStage::PostUpdate, collision_event_system); } } // Events struct LevelStartupEvent(Entity); // Resources struct CurrentLevel(Option); struct CharacterMeshes { square: Mesh2dHandle, } impl FromWorld for CharacterMeshes { fn from_world(world: &mut World) -> Self { let mut meshes = world.get_resource_mut::>().unwrap(); Self { square: meshes .add(Mesh::from(Quad { size: Vec2 { x: 64.0, y: 64.0 }, flip: false, })) .into(), } } } // Components #[derive(Clone, Component, Copy, Eq, Hash, PartialEq)] struct LevelId(u32); #[derive(Clone, Component, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] struct CharacterId(u32); #[derive(Clone, Component, Copy, Eq, Hash, PartialEq)] struct SelectedCharacterId(Option); #[derive(Component)] struct CharacterIdList(BTreeSet); #[derive(Clone, Component, PartialEq)] struct CharacterColor(Color); // Systems fn setup( mut commands: Commands, mut current_level: ResMut, mut level_startup_event: EventWriter, ) { let level_entity = commands .spawn() .insert(LevelId(0)) .insert(SelectedCharacterId(None)) .insert(CharacterIdList(BTreeSet::new())) .id(); current_level.0 = Some(level_entity); commands .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, -256.0, 0.0))) .insert(Collider::cuboid(400., 10.)); level_startup_event.send(LevelStartupEvent(level_entity)); } // This is a bad design, but it's the only way I found fn post_setup_level( mut commands: Commands, character_meshes: Res, mut effects: ResMut>, mut materials: ResMut>, mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, mut level_startup_event: EventReader, ) { for LevelStartupEvent(level_entity) in level_startup_event.iter() { if let Ok((mut selected_character_id, mut character_id_list)) = level_query.get_mut(*level_entity) { spawn_character( &mut commands, &character_meshes, &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, Transform::from_xyz(-128., -64., 0.), Color::RED, ); spawn_character( &mut commands, &character_meshes, &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, Transform::from_xyz(0., -64., 0.), Color::GREEN, ); spawn_character( &mut commands, &character_meshes, &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, Transform::from_xyz(128., -64., 0.), Color::BLUE, ); } } } 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, transform: Transform, color: Color, ) { let character_id = CharacterId( character_id_list .0 .iter() .last() .map_or(0, |last_character_id| last_character_id.0 + 1), ); 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(), material: materials.add(ColorMaterial::from(color)), transform, ..default() }) .insert(character_id) .insert(CharacterColor(color)) .insert(RigidBody::Dynamic) .insert(Collider::cuboid(32., 32.)) .insert(ExternalForce::default()) .insert(Velocity::default()) .insert(GravityScale(10.0)) .insert(LockedAxes::ROTATION_LOCKED) .insert(Friction::new(0.8)) .insert(Damping { linear_damping: 0.5, angular_damping: 0.5, }) .insert(ExternalImpulse::default()) .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() { selected_character_id.0 = Some(character_id); } } fn collision_event_system( mut commands: Commands, character_meshes: Res, mut materials: ResMut>, mut effects: ResMut>, current_level: Res, mut collision_events: EventReader, character_query: Query<(&CharacterId, &CharacterColor, &Transform)>, mut level_query: Query<(&mut SelectedCharacterId, &mut CharacterIdList)>, ) { if let Some(level_entity) = current_level.0 { for collision_event in collision_events.iter() { if let CollisionEvent::Started(e1, e2, flags) = collision_event { if flags.is_empty() { if let ( Ok((c1_id, c1_color, c1_transform)), Ok((c2_id, c2_color, _c2_transform)), ) = (character_query.get(*e1), character_query.get(*e2)) { let (mut selected_character_id, mut character_id_list) = level_query.get_mut(level_entity).unwrap(); character_id_list.0.remove(c1_id); character_id_list.0.remove(c2_id); selected_character_id.0 = None; // TODO completely remove particles commands.entity(*e1).despawn_recursive(); commands.entity(*e2).despawn_recursive(); spawn_character( &mut commands, &character_meshes, &mut effects, &mut materials, &mut selected_character_id, &mut character_id_list, *c1_transform, (Vec4::from(c1_color.0) + Vec4::from(c2_color.0)).into(), ); } } } } } } fn keyboard_input_system( keyboard_input: Res>, current_level: Res, mut characters: Query<( &CharacterId, &mut Velocity, &mut ExternalImpulse, &mut ExternalForce, &Children, )>, mut level_query: Query<(&mut SelectedCharacterId, &CharacterIdList)>, mut effect: Query<&mut ParticleEffect>, dsp_assets: Res, audio: Res