Trouv / bevy_ecs_ldtk

ECS-friendly ldtk plugin for bevy, leveraging bevy_ecs_tilemap
Other
682 stars 78 forks source link

Tilemap shows wrong tiles when using spacing and atlas #94

Open svenallers opened 2 years ago

svenallers commented 2 years ago

Hi, I currently have the issue that the rendered tilemap does not look at all like the one I created.

Here is how it looks in LDtk:

ldtk

And here is how it is rendered when starting my game:

bevy

The tileset is using a spacing of 2px and therefore I enabled the atlas feature. My code currently looks like this:

main.rs

use bevy::prelude::*;
use bevy_ecs_ldtk::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugin(LdtkPlugin)
        .add_startup_system(setup)
        .insert_resource(LevelSelection::Index(0))
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());

    commands.spawn_bundle(LdtkWorldBundle {
        ldtk_handle: asset_server.load("racing-track.ldtk"),
        ..Default::default()
    });
}

cargo.toml

[package]
edition = "2021"
name = "bevy-racer"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = "0.7"
bevy_ecs_ldtk = {version = "0.3", features = ["atlas"]}

A link to the full codebase, having the issue including the ldtk file and resources: https://github.com/svenallers/bevy-racer/commit/1e7e76d63d7247ea3a73b31679bfa75717afdd16

Could also be that the issue is on me, but I have no clue how to solve it.

svenallers commented 2 years ago

I just dug into the calculation of the position of the tiles in the tilesets and it seems that LDtk is handling indices differently, thanbevy_ecs_tilemap when the size of the sprite sheet does not align perfectly with the tile size and their spacing (meaning the width is not n * (tile_size + spacing) - spacing but sth. different).

Seems that LDtk adds an index for the "not complete" tile. See:

LDtk_ceiling

Which implies that one would need to ceil when calculating the number of columns based on the sheet size, but bevy_ecs_tilemap floors the result. See https://github.com/StarArawn/bevy_ecs_tilemap/blob/9e431db7aad111a0fadeff4aff92524439912a57/src/render/shaders/tilemap.wgsl#L55:

var columns: u32 = u32((tilemap_data.texture_size.x + tilemap_data.spacing.x) / (tilemap_data.tile_size.x + tilemap_data.spacing.x));

That explains why the shown tiles were off by 9 (the grass tile intended was in the 10th row (row index = 9) and with having one column less in each row you always have an offset that is the row index).

I did use a sprite sheet that was from an external source, thus I was not aware that it is not perfectly aligned when the space is included. After I adopted the sheet everything was fine. (The width was 32 * 128px = 4096px before, which did not take the spacing of 2px into account (4096px / 130px ~ 31,5). After increasing the width to 32 * 130px - 2 = 4158px everything was fine)

But still, I think it could make sense to translate the LDtk indices to fit the bevy_ecs_tilemap calculation in those cases, to not step into the trap that easily.

Trouv commented 2 years ago

Thanks for the issue, and also thanks for looking into it. Tile spacing has been a bit touchy from the beginning, and this explanation makes a lot of sense.

The upcoming release for bevy_ecs_tilemap will have a new API, and it might allow providing custom TextureAtlass to its maps. So, I'm hoping that we'll be able to more easily replicate LDtk's indexing techniques when that time comes. If not, we can also go back to using the LDtk-provided "src rect" for the tiles instead of the LDtk-provided atlas indices.

For now, the best workaround is, as you mentioned, adapting the sheet itself. Sorry about that.

svenallers commented 2 years ago

Yes, I was already thinking about solving it by either recalculating indices and using the translated ones for the bevy_ecs_tilemap::tile::Tile or by faking a bigger sheet by using a calculated size for bevy_ecs_tilemap::LayerSettings.texture_size instead of the actual size, which then would be columns * (tile_size + spacing) - spacing. But both feel suboptimal, as the first does not properly emulate the behavior of LDtk and the second one is basically faking wrong data to work around the issue. And both would probably decrease maintainability as they would make the code harder to understand, plus the first solution would probably make it even very hard to debug as one would be wondering why the indices are different, as long as one is not aware of the circumstances.

Having a custom TextureAtlas sounds way more elegant. Hope that works out.