StarArawn / bevy_ecs_tilemap

A tilemap rendering crate for bevy which is more ECS friendly.
MIT License
913 stars 196 forks source link

Cannot have multiple layers with different sizes but the same textures #563

Open BigBadE opened 1 week ago

BigBadE commented 1 week ago

If you have two layers, each with the same texture but different tile sizes, the tile size of the first one spawned will override the tile size of the second.

For example:

use bevy::prelude::*;
use bevy_ecs_tilemap::prelude::*;
use bevy_ecs_tilemap::TilemapPlugin;

fn startup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
) {
    commands.spawn(Camera2dBundle::default());

    let texture_handle: Handle<Image> = asset_server.load("Forest.png");

    spawn(&mut commands, &texture_handle, Vec2::new(32.0, 16.0));
    spawn(&mut commands, &texture_handle, Vec2::new(16.0, 32.0));
}

fn spawn(commands: &mut Commands, texture_handle: &Handle<Image>, dimension: Vec2) {
    let map_size = TilemapSize::from(dimension.as_uvec2());

    // Create a tilemap entity a little early.
    // We want this entity early because we need to tell each tile which tilemap entity
    // it is associated with. This is done with the TilemapId component on each tile.
    // Eventually, we will insert the `TilemapBundle` bundle on the entity, which
    // will contain various necessary components, such as `TileStorage`.
    let tilemap_entity = commands.spawn_empty().id();

    // To begin creating the map we will need a `TileStorage` component.
    // This component is a grid of tile entities and is used to help keep track of individual
    // tiles in the world. If you have multiple layers of tiles you would have a tilemap entity
    // per layer, each with their own `TileStorage` component.
    let mut tile_storage = TileStorage::empty(map_size);

    // Spawn the elements of the tilemap.
    // Alternatively, you can use helpers::filling::fill_tilemap.
    for x in 0..map_size.x {
        for y in 0..map_size.y {
            if x % 2 == 1 || y % 2 == 1 {
                continue;
            }
            let tile_pos = TilePos { x, y };
            let tile_entity = commands
                .spawn(TileBundle {
                    position: tile_pos,
                    tilemap_id: TilemapId(tilemap_entity),
                    ..Default::default()
                })
                .id();
            tile_storage.set(&tile_pos, tile_entity);
        }
    }

    let flipped_dim = Vec2::new(dimension.y, dimension.x);
    let tile_size = flipped_dim.into();
    let grid_size = flipped_dim.into();
    let map_type = TilemapType::default();

    commands.entity(tilemap_entity).insert(TilemapBundle {
        grid_size,
        map_type,
        size: map_size,
        storage: tile_storage,
        texture: TilemapTexture::Single(texture_handle.clone()),
        tile_size,
        transform: get_tilemap_center_transform(&map_size, &grid_size, &map_type, 0.0),
        ..Default::default()
    });
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin{
            primary_window: Some(Window {
                title: String::from(
                    "Bug example",
                ),
                ..Default::default()
            }),
            ..default()
        }).set(ImagePlugin::default_nearest()))
        .add_plugins(TilemapPlugin)
        .add_systems(Startup, startup)
        .run();
}

This uses an example texture called Forest.png with a bunch of tiles that are visibly different enough to show.

To see the bug, swap the order of spawn calls, though it should be obvious only two texture tiles are shown instead of three because only the top size is applied to both.

This seems to be caused by texture_array_cache only using the texture as a key and not the tile size, and seems like a niche issue, but a fix would be appreciated (or some sort of panic if multiple tile sizes are added for the same texture).