use bevy::{ prelude::*, utils::{HashMap, HashSet}, }; use bevy_ecs_ldtk::prelude::*; use bevy_rapier2d::{ dynamics::RigidBody, geometry::{Collider, Friction}, }; #[derive(Default, Component)] pub struct Wall; #[derive(Default, Bundle, LdtkIntCell)] pub struct WallBundle { wall: Wall, } pub fn spawn_wall_collision( mut commands: Commands, wall_query: Query<(&GridCoords, &Parent), Added>, parent_query: Query<&Parent, Without>, level_query: Query<(Entity, &LevelIid)>, ldtk_projects: Query<&Handle>, ldtk_project_assets: Res>, ) { /// Represents a wide wall that is 1 tile tall /// Used to spawn wall collisions #[derive(Clone, Eq, PartialEq, Debug, Default, Hash)] struct Plate { left: i32, right: i32, } /// A simple rectangle type representing a wall of any size struct Rect { left: i32, right: i32, top: i32, bottom: i32, } let mut level_to_wall_locations: HashMap> = HashMap::new(); wall_query.iter().for_each(|(&grid_coords, parent)| { if let Ok(grandparent) = parent_query.get(parent.get()) { level_to_wall_locations .entry(grandparent.get()) .or_default() .insert(grid_coords); } }); if !wall_query.is_empty() { level_query.iter().for_each(|(level_entity, level_iid)| { if let Some(level_walls) = level_to_wall_locations.get(&level_entity) { let ldtk_project = ldtk_project_assets .get(ldtk_projects.single()) .expect("Project has to be loaded at this point"); let level = ldtk_project .as_standalone() .get_loaded_level_by_iid(&level_iid.to_string()) .expect("Spawned level should exist in LDtk project"); let LayerInstance { c_wid: width, c_hei: height, grid_size, .. } = level.layer_instances()[0]; let mut plate_stack: Vec> = Vec::new(); for y in 0..height { let mut row_plates: Vec = Vec::new(); let mut plate_start = None; for x in 0..width + 1 { match (plate_start, level_walls.contains(&GridCoords { x, y })) { (Some(s), false) => { row_plates.push(Plate { left: s, right: x - 1, }); plate_start = None; } (None, true) => plate_start = Some(x), _ => (), } } plate_stack.push(row_plates); } // combine "plates" into rectangles across multiple rows let mut rect_builder: HashMap = HashMap::new(); let mut prev_row: Vec = Vec::new(); let mut wall_rects: Vec = Vec::new(); // an extra empty row so the algorithm "finishes" the rects that touch the top edge plate_stack.push(Vec::new()); for (y, current_row) in plate_stack.into_iter().enumerate() { for prev_plate in &prev_row { if !current_row.contains(prev_plate) { //remove rect so the same plate starts a new rectangle if let Some(rect) = rect_builder.remove(prev_plate) { wall_rects.push(rect); } } } for plate in ¤t_row { rect_builder .entry(plate.clone()) .and_modify(|e| e.top += 1) .or_insert(Rect { bottom: y as i32, top: y as i32, left: plate.left, right: plate.right, }); } prev_row = current_row; } commands.entity(level_entity).with_children(|level| { // Spawn colliders for every rectangle.. // Making the collider a child of the level serves two purposes: // 1. Adjusts the transforms to be relative to the level for free // 2. the colliders will be despawned automatically when levels unload for wall_rect in wall_rects { level .spawn_empty() .insert(Collider::cuboid( (wall_rect.right as f32 - wall_rect.left as f32 + 1.) * grid_size as f32 / 2., (wall_rect.top as f32 - wall_rect.bottom as f32 + 1.) * grid_size as f32 / 2., )) .insert(RigidBody::Fixed) .insert(Friction::new(1.0)) .insert(Transform::from_xyz( (wall_rect.left + wall_rect.right + 1) as f32 * grid_size as f32 / 2., (wall_rect.bottom + wall_rect.top + 1) as f32 * grid_size as f32 / 2., 0., //this averages xy so it in center yes ) )) .insert(GlobalTransform::default()); } }); } }); } } pub struct WallPlugin; impl Plugin for WallPlugin { fn build(&self, app: &mut App) { app.add_systems(Update, spawn_wall_collision) .register_ldtk_int_cell::(1) //dirt .register_ldtk_int_cell::(3); //stone } }