#![allow(clippy::precedence)] use crate::AppState; use bevy::{ input::{keyboard::KeyCode, Input}, prelude::{shape::Quad, *}, sprite::{MaterialMesh2dBundle, 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::() .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 CharacterMaterials { red: Handle, green: Handle, blue: Handle, yellow: Handle, magenta: Handle, cyan: Handle, } impl FromWorld for CharacterMaterials { fn from_world(world: &mut World) -> Self { let mut materials = world.get_resource_mut::>().unwrap(); Self { red: materials.add(ColorMaterial::from(Color::rgba(1.0, 0., 0., 1.))), green: materials.add(ColorMaterial::from(Color::rgba(0., 1.0, 0., 1.))), blue: materials.add(ColorMaterial::from(Color::rgba(0., 0., 1.0, 1.))), yellow: materials.add(ColorMaterial::from(Color::rgba(1.0, 1.0, 0., 1.))), magenta: materials.add(ColorMaterial::from(Color::rgba(1.0, 0., 1.0, 1.))), cyan: materials.add(ColorMaterial::from(Color::rgba(0., 1.0, 1.0, 1.))), } } } 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); // 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_materials: Res, character_meshes: Res, mut effects: 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 selected_character_id, &mut character_id_list, character_materials.red.clone(), Transform::from_xyz(-128., -64., 0.), ); spawn_character( &mut commands, &character_meshes, &mut effects, &mut selected_character_id, &mut character_id_list, character_materials.green.clone(), Transform::from_xyz(0., -64., 0.), ); spawn_character( &mut commands, &character_meshes, &mut effects, &mut selected_character_id, &mut character_id_list, character_materials.blue.clone(), Transform::from_xyz(128., -64., 0.), ); } } } fn spawn_character( commands: &mut Commands, character_meshes: &Res, effects: &mut ResMut>, selected_character_id: &mut Mut, character_id_list: &mut Mut, material: Handle, transform: Transform, ) { 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::new(1.0, 0.1, 0.1, 0.0)); gradient.add_key(0.2, Vec4::new(1.0, 0.1, 0.1, 1.0)); gradient.add_key(1.0, Vec4::new(1.0, 0.1, 0.1, 0.0)); commands .spawn_bundle(MaterialMesh2dBundle { mesh: character_meshes.square.clone(), material, transform, ..default() }) .insert(character_id) .insert(RigidBody::Dynamic) .insert(Collider::cuboid(32., 32.)) .insert(ExternalForce::default()) .insert(Friction::new(1.0)) .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: "Red 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, current_level: Res, mut collision_events: EventReader, character_query: Query<&CharacterId>, 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), Ok(c2_id)) = (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(c2_id); if selected_character_id.0 == Some(*c2_id) { selected_character_id.0 = Some(*c1_id); } commands.entity(*e2).despawn_recursive(); } } } } } } fn keyboard_input_system( keyboard_input: Res>, current_level: Res, mut characters: Query<( &CharacterId, &mut ExternalImpulse, &mut ExternalForce, &Children, )>, mut level_query: Query<(&mut SelectedCharacterId, &CharacterIdList)>, mut effect: Query<&mut ParticleEffect>, dsp_assets: Res, audio: Res