bevy_sparse_tilemap 0.4.0

A Tilemap crate for the Bevy game engine with a focus on large map sizes and ECS sparse maps
Documentation
pub mod tilemap_layer_builder;

use crate::map::chunk::{Chunk, ChunkLayer, ChunkLayerType, Chunks};
use crate::map::{MapData, MapLayer, Tilemap};
use crate::tilemap_builder::tilemap_layer_builder::TilemapLayer;
use bevy::prelude::{BuildChildren, Commands, Entity, UVec2};
use bevy::utils::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;

/// Helper struct used to construct a new tilemap.
pub struct TilemapBuilder<TileData, MapLayers, Chunk, MapType>
where
    TileData: Hash + Clone + Copy + Sized + Default + Send + Sync + 'static,
    MapLayers: MapLayer + Clone + Copy + Send + Sync + 'static,
    Chunk: ChunkLayer<TileData> + Send + Sync + 'static + Default,
    MapType: MapData + Default,
{
    main_layer: Option<TilemapLayer<TileData>>,
    layer_info: HashMap<u32, TilemapLayer<TileData>>,
    map_size: UVec2,
    map_type: MapType,
    chunk_settings: Chunk::ChunkSettings,
    // All phantom data below
    td_phantom: PhantomData<TileData>,
    ml_phantom: PhantomData<MapLayers>,
    ct_phantom: PhantomData<Chunk>,
}

impl<TileData, MapLayers, MapChunk, MapType> Default
    for TilemapBuilder<TileData, MapLayers, MapChunk, MapType>
where
    TileData: Hash + Clone + Copy + Sized + Default + Send + Sync + 'static,
    MapLayers: MapLayer + Clone + Copy + Send + Sync + 'static,
    MapChunk: ChunkLayer<TileData> + Send + Sync + 'static + Default,
    MapType: MapData + Default,
{
    fn default() -> Self {
        Self {
            main_layer: None,
            layer_info: Default::default(),
            map_size: Default::default(),
            map_type: Default::default(),
            chunk_settings: MapChunk::ChunkSettings::default(),
            td_phantom: PhantomData,
            ml_phantom: PhantomData,
            ct_phantom: PhantomData,
        }
    }
}

impl<TileData, MapLayers, MapChunk, MapType> TilemapBuilder<TileData, MapLayers, MapChunk, MapType>
where
    TileData: Hash + Clone + Copy + Sized + Default + Send + Sync + 'static,
    MapLayers: MapLayer + Clone + Copy + Send + Sync + 'static,
    MapChunk: ChunkLayer<TileData> + Send + Sync + 'static + Default,
    MapType: MapData + Default + Send + Sync + 'static,
{
    /// Converts all the data from the tilemap builder and spawns the tilemap returning the Tilemaps [`Entity`]
    #[must_use]
    pub fn spawn_tilemap(mut self, commands: &mut Commands) -> Option<Entity> {
        let layer = self.main_layer.take()?;

        let mut chunks = self.create_new_chunks_from_layer(
            &layer,
            self.chunk_settings,
            self.map_type.max_chunk_size(),
        );

        let layers: Vec<(u32, TilemapLayer<TileData>)> = self.layer_info.drain().collect();

        for (id, layer) in layers {
            self.add_layer_to_chunks(id, &mut chunks, &layer, self.map_type.max_chunk_size())
        }

        let mut chunk_entities: Vec<Vec<Entity>> = vec![];

        let map_x = chunks[0].len();

        for chunk in &mut chunks {
            let mut vec: Vec<Entity> = vec![];
            for _ in 0..map_x {
                let entity = commands.spawn(chunk.remove(0)).id();
                vec.push(entity);
            }
            chunk_entities.push(vec);
        }

        let mut flattened_chunk_entities: Vec<Entity> = vec![];

        for chunk_entity in chunk_entities.iter_mut() {
            flattened_chunk_entities.extend(chunk_entity.iter().cloned())
        }

        let chunks = Chunks::new(
            Chunks::new_chunk_entity_grid(chunk_entities),
            self.map_type.max_chunk_size(),
        );

        let tilemap_entity = commands
            .spawn((Tilemap::new(chunks), self.map_type))
            .add_children(flattened_chunk_entities.as_slice())
            .id();
        Some(tilemap_entity)
    }

    /// Makes a new [`TilemapBuilder`] with the given [`TilemapLayer`] as the main layer.
    pub fn new(
        layer_data: TilemapLayer<TileData>,
        map_type: MapType,
        chunk_settings: MapChunk::ChunkSettings,
    ) -> Self {
        let dimensions = layer_data.dimensions();
        TilemapBuilder::<TileData, MapLayers, MapChunk, MapType> {
            main_layer: Some(layer_data),
            layer_info: Default::default(),
            map_size: dimensions,
            map_type,
            chunk_settings,
            td_phantom: Default::default(),
            ml_phantom: Default::default(),
            ct_phantom: PhantomData,
        }
    }

    /// Adds the given [`TilemapLayer`] to the tilemap keyed to the given [`MapLayer`]
    pub fn add_layer(&mut self, layer_data: TilemapLayer<TileData>, map_layer: MapLayers) {
        assert_eq!(
            self.map_size,
            layer_data.dimensions(),
            "New layers must be the same size as the map dimensions"
        );
        self.layer_info.insert(map_layer.to_bits(), layer_data);
    }

    /// Function which creates new chunks and inserts the given tilemap layer into those chunks
    pub fn create_new_chunks_from_layer(
        &mut self,
        tilemap_layer: &TilemapLayer<TileData>,
        chunk_settings: MapChunk::ChunkSettings,
        max_chunk_size: UVec2,
    ) -> Vec<Vec<Chunk<MapChunk, TileData>>>
    where
        TileData: Hash + Clone + Copy + Sized + Default + Send + Sync + 'static,
    {
        match tilemap_layer {
            TilemapLayer::Sparse(data, map_size, entities) => {
                let mut chunks = self.map_type.break_hashmap_into_chunks(
                    MapLayers::default(),
                    data,
                    *map_size,
                    max_chunk_size,
                    chunk_settings,
                );
                self.map_type.add_entities_to_layer(
                    MapLayers::default().to_bits(),
                    &mut chunks,
                    entities,
                );
                chunks
            }
            TilemapLayer::Dense(data, entities) => {
                let mut chunks =
                    self.map_type
                        .break_data_vecs_into_chunks(data, max_chunk_size, chunk_settings);
                self.map_type.add_entities_to_layer(
                    MapLayers::default().to_bits(),
                    &mut chunks,
                    entities,
                );
                chunks
            }
        }
    }

    /// Adds the given layer to the tilemap
    pub fn add_layer_to_chunks(
        &mut self,
        map_layer: u32,
        chunks: &mut Vec<Vec<Chunk<MapChunk, TileData>>>,
        tilemap_layer: &TilemapLayer<TileData>,
        max_chunk_size: UVec2,
    ) {
        match tilemap_layer {
            TilemapLayer::Sparse(data, .., entities) => {
                for y in chunks.iter_mut() {
                    for chunk in y.iter_mut() {
                        chunk.add_layer(map_layer, ChunkLayerType::Sparse(HashMap::new()));
                    }
                }
                for (cell, tile_data) in data.iter() {
                    let chunk_pos = self.map_type.into_chunk_pos(*cell);
                    let chunk = &mut chunks[chunk_pos.y() as usize][chunk_pos.x() as usize];
                    chunk.set_tile_data(
                        map_layer,
                        MapChunk::into_chunk_cell(*cell, &chunk.chunk_settings),
                        *tile_data,
                    );
                }
                self.map_type
                    .add_entities_to_layer(map_layer, chunks, entities);
            }
            TilemapLayer::Dense(data, entities) => {
                for y in chunks.iter_mut() {
                    for chunk in y.iter_mut() {
                        let vec = self.map_type.break_data_vecs_down_into_chunk_data(
                            data,
                            chunk.chunk_pos,
                            max_chunk_size,
                        );
                        chunk.add_layer(map_layer, ChunkLayerType::Dense(vec));
                    }
                }
                self.map_type
                    .add_entities_to_layer(map_layer, chunks, entities);
            }
        }
    }
}