bevy_entitiles 0.9.0

A 2d tilemap library for bevy. With many useful algorithms/tools built in.
Documentation
use bevy::{
    ecs::{entity::Entity, query::With},
    prelude::{Commands, Query, Res, ResMut},
    render::{
        render_asset::RenderAssets,
        renderer::{RenderDevice, RenderQueue},
        texture::{FallbackImage, Image},
    },
    time::Time,
};

use crate::tilemap::{
    despawn::{DespawnedTile, DespawnedTilemap},
    map::TilemapTextures,
};

use super::{
    binding::TilemapBindGroups,
    buffer::{
        PerTilemapBuffersStorage, TilemapAnimationBuffer, TilemapUniformBuffer, UniformBuffer,
    },
    chunk::{TilemapRenderChunk, UnloadRenderChunk},
    extract::{ExtractedTile, TilemapInstance},
    material::TilemapMaterial,
    pipeline::EntiTilesPipeline,
    resources::{ExtractedTilemapMaterials, TilemapInstances},
    texture::TilemapTexturesStorage,
    RenderChunkStorage,
};

#[cfg(feature = "atlas")]
use super::buffer::TilemapTextureDescriptorBuffer;

pub fn prepare_tilemaps_a<M: TilemapMaterial>(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    render_queue: Res<RenderQueue>,
    extracted_tilemaps: Query<Entity, With<TilemapInstance>>,
    mut render_chunks: ResMut<RenderChunkStorage<M>>,
    mut uniform_buffers: ResMut<TilemapUniformBuffer<M>>,
    entitiles_pipeline: Res<EntiTilesPipeline<M>>,
    mut bind_groups: ResMut<TilemapBindGroups<M>>,
    time: Res<Time>,
    tilemap_instances: Res<TilemapInstances<M>>,
    images: Res<RenderAssets<Image>>,
    fallback_image: Res<FallbackImage>,
    extracted_materials: Res<ExtractedTilemapMaterials<M>>,
) {
    uniform_buffers.clear();

    extracted_tilemaps
        .iter()
        .filter_map(|tilemap| tilemap_instances.0.get(&tilemap))
        .for_each(|tilemap| {
            commands
                .entity(tilemap.id)
                .insert(uniform_buffers.insert(&(tilemap, time.elapsed_seconds())));

            render_chunks.prepare_chunks(tilemap, &render_device);
        });

    uniform_buffers.write(&render_device, &render_queue);
    bind_groups.bind_uniform_buffers(&render_device, &mut uniform_buffers, &entitiles_pipeline);
    bind_groups.prepare_material_bind_groups(
        &entitiles_pipeline.material_layout,
        &render_device,
        &images,
        &fallback_image,
        &extracted_materials,
    );
}

pub fn prepare_tilemaps_b<M: TilemapMaterial>(
    render_device: Res<RenderDevice>,
    render_queue: Res<RenderQueue>,
    extracted_tilemaps: Query<Entity, With<TilemapInstance>>,
    mut animation_buffers: ResMut<TilemapAnimationBuffer>,
    mut textures_storage: ResMut<TilemapTexturesStorage>,
    entitiles_pipeline: Res<EntiTilesPipeline<M>>,
    mut bind_groups: ResMut<TilemapBindGroups<M>>,
    tilemap_instances: Res<TilemapInstances<M>>,
    textures_assets: Res<RenderAssets<TilemapTextures>>,
    #[cfg(feature = "atlas")] mut texture_desc_buffers: ResMut<TilemapTextureDescriptorBuffer>,
) {
    animation_buffers.clear();
    #[cfg(feature = "atlas")]
    texture_desc_buffers.clear();

    extracted_tilemaps
        .iter()
        .filter_map(|tilemap| tilemap_instances.0.get(&tilemap))
        .filter(|tilemap| tilemap.texture.is_some())
        .for_each(|tilemap| {
            let textures_handle = tilemap.texture.as_ref().unwrap();
            animation_buffers
                .get_or_insert_buffer(tilemap.id)
                .extend(&tilemap.animations.as_ref().unwrap().0);

            let Some(_textures) = textures_assets.get(textures_handle) else {
                return;
            };

            #[cfg(feature = "atlas")]
            texture_desc_buffers
                .get_or_insert_buffer(tilemap.id)
                .extend(_textures.textures.iter().enumerate().map(|(i, t)| {
                    super::buffer::GpuTilemapTextureDescriptor {
                        tile_count: t.desc.size / t.desc.tile_size,
                        tile_uv_size: t.desc.tile_size.as_vec2() / t.desc.size.as_vec2(),
                        uv_scale: _textures.uv_scales[i],
                    }
                }));

            if !textures_storage.contains(textures_handle) {
                textures_storage.insert(textures_handle.clone());
            }
        });

    #[cfg(feature = "atlas")]
    texture_desc_buffers.write(&render_device, &render_queue);
    animation_buffers.write(&render_device, &render_queue);

    textures_storage.prepare_textures(&render_device, &textures_assets);
    bind_groups.bind_tilemap_storage_buffers(
        &render_device,
        &mut animation_buffers,
        &entitiles_pipeline,
        #[cfg(feature = "atlas")]
        &mut texture_desc_buffers,
    );
}

pub fn prepare_tiles<M: TilemapMaterial>(
    extracted_tiles: Query<&mut ExtractedTile>,
    mut render_chunks: ResMut<RenderChunkStorage<M>>,
    tilemap_instances: Res<TilemapInstances<M>>,
) {
    extracted_tiles.iter().for_each(|tile| {
        let Some(tilemap) = tilemap_instances.0.get(&tile.tilemap_id) else {
            return;
        };

        let chunks = render_chunks.value.entry(tile.tilemap_id).or_default();

        let chunk = chunks
            .entry(tile.chunk_index)
            .or_insert_with(|| TilemapRenderChunk::from_index(tile.chunk_index, tilemap));

        chunk.set_tile(tile.in_chunk_index, Some(tile));
    });
}

pub fn prepare_unloaded_chunks<M: TilemapMaterial>(
    mut render_chunks: ResMut<RenderChunkStorage<M>>,
    extracted_tilemaps: Query<(Entity, &UnloadRenderChunk)>,
) {
    extracted_tilemaps.iter().for_each(|(entity, unloaded)| {
        unloaded.0.iter().for_each(|c| {
            render_chunks.remove_chunk(entity, *c);
        });
    });
}

pub fn prepare_despawned_tilemaps<M: TilemapMaterial>(
    mut render_chunks: ResMut<RenderChunkStorage<M>>,
    mut storage_buffers: ResMut<TilemapAnimationBuffer>,
    mut tilemap_instaces: ResMut<TilemapInstances<M>>,
    tilemaps_query: Query<&DespawnedTilemap>,
) {
    tilemaps_query.iter().for_each(|map| {
        render_chunks.remove_tilemap(map.0);
        storage_buffers.remove(map.0);
        tilemap_instaces.0.remove(&map.0);
    });
}

pub fn prepare_despawned_tiles<M: TilemapMaterial>(
    mut render_chunks: ResMut<RenderChunkStorage<M>>,
    tiles_query: Query<&DespawnedTile>,
) {
    tiles_query.iter().for_each(|tile| {
        if let Some(chunk) = render_chunks
            .get_chunks_mut(tile.tilemap)
            .and_then(|chunks| chunks.get_mut(&tile.chunk_index))
        {
            chunk.set_tile(tile.in_chunk_index, None);
        }
    });
}