bevy_entitiles 0.2.7

A 2d tilemap library for bevy. With many useful algorithms/tools built in.
use bevy::{
    ecs::{entity::Entity, system::Commands},
    hierarchy::{BuildChildren, DespawnRecursiveExt},
    math::{IVec2, Vec2, Vec4},
    prelude::SpatialBundle,
    sprite::SpriteBundle,
    transform::components::Transform,
    utils::HashMap,
};

use crate::{
    math::aabb::IAabb2d,
    serializing::pattern::TilemapPattern,
    tilemap::{
        buffers::TileBuffer,
        bundles::TilemapBundle,
        map::{
            TileRenderSize, TilemapLayerOpacities, TilemapName, TilemapSlotSize, TilemapStorage,
            TilemapTexture, TilemapTransform, TilemapType,
        },
        tile::{TileBuilder, TileLayer, TileTexture},
    },
    DEFAULT_CHUNK_SIZE,
};

use super::{
    components::LayerIid,
    json::level::{LayerInstance, TileInstance},
    resources::{LdtkAssets, LdtkPatterns},
    LdtkLoaderMode,
};

#[cfg(feature = "algorithm")]
pub mod path;
#[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
pub mod physics;

pub type LayerOpacity = f32;

pub struct LdtkLayers<'a> {
    pub identifier: String,
    pub ty: LdtkLoaderMode,
    pub level_entity: Entity,
    pub layers: Vec<Option<(TilemapPattern, TilemapTexture, LayerIid, LayerOpacity)>>,
    pub tilesets: &'a HashMap<i32, TilemapTexture>,
    pub translation: Vec2,
    pub base_z_index: i32,
    pub background: SpriteBundle,
    #[cfg(feature = "algorithm")]
    pub path_layer: Option<(
        path::LdtkPathLayer,
        HashMap<IVec2, crate::tilemap::algorithm::path::PathTile>,
    )>,
    #[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
    pub physics_layer: Option<(physics::LdtkPhysicsLayer, physics::LdtkPhysicsAabbs)>,
}

impl<'a> LdtkLayers<'a> {
    pub fn new(
        identifier: String,
        level_entity: Entity,
        total_layers: usize,
        ldtk_assets: &'a LdtkAssets,
        translation: Vec2,
        base_z_index: i32,
        ty: LdtkLoaderMode,
        background: SpriteBundle,
    ) -> Self {
        Self {
            identifier,
            level_entity,
            layers: vec![None; total_layers],
            tilesets: &ldtk_assets.tilesets,
            translation,
            base_z_index,
            background,
            ty,
            #[cfg(feature = "algorithm")]
            path_layer: None,
            #[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
            physics_layer: None,
        }
    }

    pub fn set(&mut self, layer_index: usize, layer: &LayerInstance, tile: &TileInstance) {
        self.try_create_new_layer(layer_index, layer);

        let (pattern, texture, _, _) = self.layers[layer_index].as_mut().unwrap();
        let tile_size = texture.desc.tile_size;
        let tile_index = IVec2 {
            x: tile.px[0] / tile_size.x as i32,
            y: -tile.px[1] / tile_size.y as i32,
        };

        if let Some(ser_tile) = pattern.tiles.get_mut(tile_index) {
            let TileTexture::Static(tile_layers) = &mut ser_tile.texture else {
                unreachable!()
            };
            tile_layers.push(TileLayer::new().with_texture_index(tile.tile_id as u32));
        } else {
            let builder = TileBuilder::new()
                .with_layer(
                    0,
                    TileLayer::new()
                        .with_texture_index(tile.tile_id as u32)
                        .with_flip_raw(tile.flip as u32),
                )
                .with_color(Vec4::new(1., 1., 1., tile.alpha));
            pattern.tiles.tiles.insert(tile_index, builder);
        }
    }

    fn try_create_new_layer(&mut self, layer_index: usize, layer: &LayerInstance) {
        let tileset = self
            .tilesets
            .get(&layer.tileset_def_uid.unwrap())
            .cloned()
            .unwrap();

        if self.layers[layer_index].is_some() {
            return;
        }

        let aabb = IAabb2d {
            min: IVec2::new(0, -layer.c_hei + 1),
            max: IVec2::new(layer.c_wid - 1, 0),
        };

        self.layers[layer_index] = Some((
            TilemapPattern {
                label: Some(layer.identifier.clone()),
                tiles: TileBuffer {
                    aabb,
                    tiles: HashMap::new(),
                },
                #[cfg(feature = "algorithm")]
                path_tiles: TileBuffer {
                    aabb,
                    tiles: HashMap::new(),
                },
            },
            tileset,
            LayerIid(layer.iid.clone()),
            layer.opacity,
        ));
    }

    pub fn apply_all(&mut self, commands: &mut Commands, ldtk_patterns: &mut LdtkPatterns) {
        match self.ty {
            LdtkLoaderMode::Tilemap => {
                commands.entity(self.level_entity).insert(SpatialBundle {
                    transform: Transform::from_translation(self.translation.extend(0.)),
                    ..Default::default()
                });

                self.layers
                    .drain(..)
                    .filter_map(|e| if let Some(e) = e { Some(e) } else { None })
                    .enumerate()
                    .for_each(|(index, (pattern, texture, iid, opacity))| {
                        let tilemap_entity = commands.spawn_empty().id();
                        let mut tilemap = TilemapBundle {
                            name: TilemapName(pattern.label.clone().unwrap()),
                            ty: TilemapType::Square,
                            tile_render_size: TileRenderSize(texture.desc.tile_size.as_vec2()),
                            slot_size: TilemapSlotSize(texture.desc.tile_size.as_vec2()),
                            texture: texture.clone(),
                            storage: TilemapStorage::new(DEFAULT_CHUNK_SIZE, tilemap_entity),
                            tilemap_transform: TilemapTransform {
                                translation: self.translation,
                                z_index: self.base_z_index - index as i32 - 1,
                                ..Default::default()
                            },
                            layer_opacities: TilemapLayerOpacities([opacity; 4].into()),
                            ..Default::default()
                        };

                        tilemap
                            .storage
                            .fill_with_buffer(commands, IVec2::NEG_Y, pattern.tiles);

                        #[cfg(feature = "algorithm")]
                        if let Some((path_layer, path_tilemap)) = &self.path_layer {
                            if path_layer.parent == tilemap.name.0 {
                                commands.entity(tilemap_entity).insert(
                                    crate::tilemap::algorithm::path::PathTilemap {
                                        storage:
                                            crate::tilemap::chunking::storage::ChunkedStorage::from_mapper(
                                                path_tilemap.clone(),
                                                None,
                                            ),
                                    },
                                );
                            }
                        }

                        #[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
                        if let Some((physics_layer, aabbs)) = &self.physics_layer {
                            if physics_layer.parent == tilemap.name.0 {
                                aabbs.generate_colliders(
                                    commands,
                                    tilemap_entity,
                                    &tilemap.ty,
                                    &tilemap.tilemap_transform,
                                    &tilemap.tile_pivot,
                                    &tilemap.slot_size,
                                    physics_layer.frictions.as_ref(),
                                    Vec2::ZERO,
                                );
                            }
                        }

                        commands.entity(self.level_entity).add_child(tilemap_entity);
                        commands.entity(tilemap_entity).insert((tilemap, iid));
                    });

                let bg = commands.spawn(self.background.clone()).id();
                commands.entity(self.level_entity).add_child(bg);
            }
            LdtkLoaderMode::MapPattern => {
                let level = self
                    .layers
                    .drain(..)
                    .filter_map(|p| {
                        #[allow(unused_mut)]
                        if let Some(mut p) = p {
                            #[cfg(feature = "algorithm")]
                            if let Some((path_layer, path_tiles)) = &self.path_layer {
                                if path_layer.parent == p.0.label.clone().unwrap() {
                                    p.0.path_tiles.tiles = path_tiles.clone();
                                }
                            }

                            Some((p.0, p.1))
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();

                #[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
                {
                    let (physics_layer, aabbs) = self.physics_layer.as_ref().unwrap();
                    ldtk_patterns.insert_physics_aabbs(self.identifier.clone(), aabbs.clone());
                    ldtk_patterns.frictions = physics_layer.frictions.clone();
                }

                ldtk_patterns.insert(self.identifier.clone(), level, self.background.clone());
                commands.entity(self.level_entity).despawn_recursive();
            }
        }
    }

    #[cfg(feature = "algorithm")]
    pub fn assign_path_layer(
        &mut self,
        path: path::LdtkPathLayer,
        tilemap: HashMap<IVec2, crate::tilemap::algorithm::path::PathTile>,
    ) {
        self.path_layer = Some((path, tilemap));
    }

    #[cfg(any(feature = "physics_xpbd", feature = "physics_rapier"))]
    pub fn assign_physics_layer(
        &mut self,
        physics: physics::LdtkPhysicsLayer,
        aabbs: physics::LdtkPhysicsAabbs,
    ) {
        self.physics_layer = Some((physics, aabbs));
    }
}