#![allow(clippy::precedence)] #![allow(clippy::too_many_arguments)] pub use crate::filters::*; use crate::levels; use crate::{audio_system, AppState}; use bevy::{ ecs::system::EntityCommands, input::{keyboard::KeyCode, Input}, prelude::{shape::Quad, *}, sprite::Mesh2dHandle, }; use bevy_rapier2d::prelude::*; use rapier2d::geometry::CollisionEventFlags; use std::collections::BTreeSet; pub struct FirstLevel(pub LevelId); #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct LevelId(pub u32); pub struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_event::() .add_event::() .init_resource::() .insert_resource(CurrentLevel(None)) .init_resource::() .init_resource::() .add_system_set(SystemSet::on_enter(AppState::Game).with_system(setup)) .add_system_set(SystemSet::on_enter(AppState::Win).with_system(win_setup)) .add_system_set( SystemSet::on_exit(AppState::Win).with_system(crate::levels::despawn_level), ) .add_system_set( SystemSet::on_update(AppState::Game) .with_system(crate::levels::post_setup_level) .with_system(change_character_system) .with_system(player_movement_system) .with_system(level_keyboard_system) .with_system(camera_system) .with_system(character_particle_effect_system) .with_system(kill_character_system), ) .add_system_set( SystemSet::on_update(AppState::Win) .with_system(player_movement_system) .with_system(level_keyboard_system) .with_system(camera_system) .with_system(character_particle_effect_system) .with_system(move_win_text_system), ) .add_system_to_stage(CoreStage::PostUpdate, char_char_collision_event_system) .add_system_to_stage(CoreStage::PostUpdate, char_platform_collision_event_system) // collision event system might remove items, therefore, we should detect platforms first before removing them .add_system_to_stage( CoreStage::PostUpdate, collision_event_system.after(char_platform_collision_event_system), ); } } // Events pub struct LevelStartupEvent; pub struct ChangeCharacterEvent; // Resources pub struct CurrentLevel(pub Option); #[derive(Default)] pub struct CharacterList(pub BTreeSet); pub 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(Component)] pub struct Level; #[derive(Clone, Component, PartialEq)] pub struct CharacterColor(pub Color); #[derive(Component)] pub struct Player; #[derive(Component)] pub struct Platform; #[derive(Component)] pub struct PlatformCount(usize); impl PlatformCount { fn increment(&mut self) { self.0 += 1; } fn decrement(&mut self) { self.0 = self.0.saturating_sub(1); } fn reset(&mut self) { self.0 = 0; } fn is_landed(&self) -> bool { self.0 != 0 } } #[derive(Component)] pub struct Melty(pub Color); #[derive(Component)] pub struct WinText; // Systems fn setup( first_level: Res, mut current_level: ResMut, mut level_startup_event: EventWriter, mut camera_query: Query<&mut Transform, With>, mut zoom_timer: ResMut, ) { if current_level.0.is_none() { current_level.0 = Some(first_level.0); } crate::levels::setup_level(&mut level_startup_event, &mut camera_query, &mut zoom_timer); } pub fn spawn_characters>( commands: &mut Commands, character_meshes: &Res, materials: &mut ResMut>, character_list: &mut ResMut, characters: I, ) { for (i, (transform, color)) in characters.into_iter().enumerate() { spawn_character( commands, character_meshes, materials, character_list, transform, color, i == 0, ); } } pub fn spawn_character( commands: &mut Commands, character_meshes: &Res, materials: &mut ResMut>, character_list: &mut ResMut, mut transform: Transform, color: Color, is_player: bool, ) { transform.translation.z = transform.translation.z.max(1.0); let color_mesh_2d_bundle: ColorMesh2dBundle = ColorMesh2dBundle { mesh: character_meshes.square.clone(), material: materials.add(ColorMaterial::from(color)), transform, ..default() }; let mut entity_commands: EntityCommands = commands.spawn_bundle(color_mesh_2d_bundle); entity_commands .insert(Level) .insert(CharacterColor(color)) .insert(RigidBody::Dynamic) .insert(Collider::cuboid(32., 32.)) .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(ActiveEvents::COLLISION_EVENTS) .with_children(|c| { c.spawn_bundle(TransformBundle::from_transform(Transform::from_xyz( 0., -33., 0., ))) .insert(Sensor) .insert(Collider::cuboid(30., 0.5)) .insert(ActiveEvents::COLLISION_EVENTS) .insert(PlatformCount(0)); }); character_list.0.insert(entity_commands.id()); if is_player { entity_commands.insert(Player); } } pub fn spawn_platforms>( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, platforms: I, ) { for (transform, size) in platforms.into_iter() { spawn_platform(commands, meshes, materials, transform, size); } } pub fn spawn_platform( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, transform: Transform, size: Vec2, ) { commands .spawn_bundle(ColorMesh2dBundle { mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(), material: materials.add(ColorMaterial::from(Color::GRAY)), transform, ..default() }) .insert(Collider::cuboid(size.x / 2., size.y / 2.)) .insert(Level) .insert(Platform); } pub fn spawn_melty_platform( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, asset_server: &Res, transform: Transform, color: Color, ) { commands .spawn_bundle(ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Quad { size: Vec2 { x: 96., y: 16. }, flip: false, })) .into(), material: materials.add(ColorMaterial::from(color)), transform, ..default() }) .insert(Collider::cuboid(48., 8.)) .insert(Melty(color)) .insert(Level) .insert(Platform) .with_children(|c| { c.spawn_bundle(SpriteBundle { texture: asset_server.get_handle("melty.png"), transform: Transform::from_xyz(0., 0., 0.5), ..default() }); }); } fn char_char_collision_event_system( mut commands: Commands, mut collision_events: EventReader, character_query: Query<(&CharacterColor, &Transform, Option<&Player>)>, mut character_list: ResMut, mut app_state: ResMut>, character_meshes: Res, mut materials: ResMut>, audio_assets: Res, audio: Res