Trouv / bevy_ecs_ldtk

ECS-friendly ldtk plugin for bevy, leveraging bevy_ecs_tilemap
Other
627 stars 73 forks source link

Set z transform of each tile in one layer #276

Closed PraxTube closed 7 months ago

PraxTube commented 7 months ago

Is it possible to set the z transform for each tile in a given layer? For example, say we have a layer were we define objects like pots that we would want to include in a Y-sorting method. Could we set the z transform for each pot in this one layer? It doesn't even need to be dynamic, setting it once on creation would also work.

Relates to #95 and #117, but they are both about the layers as a whole not for the tiles inside of the layer. It's quite likely that this doesn't work at all due to the way the tiles are rendered. In that case, is it best to render the objects that need y sorting manually?

Trouv commented 7 months ago

Actually, bevy_ecs_tilemap does support y-sorting. You'll need to depend on bevy_ecs_tilemap separately, but you can enable it using the TilemapRenderSettings resource: https://docs.rs/bevy_ecs_tilemap/latest/bevy_ecs_tilemap/map/struct.TilemapRenderSettings.html#structfield.y_sort

Theoretically you wouldn't need to edit the z-value of the tiles at all. You'd probably need to adjust the player's z-value to be at the same level as the objects you'd like to y-sort it against.

I haven't actually tried doing this, so I'd be curious to hear if you try it and how it goes.

PraxTube commented 7 months ago

Thanks for the pointer. I tried to implement it but there seem to be some issues (or I am just hugely misunderstanding something). I created an issue in the bevy_ecs_tilemap repo.

Though even if that issue would be resolved, there is another problem: LDtk gives each tile a z-value to order them in the same layer, this value is always the same unless you have tiles that stack. In this case it will add 1.0 to each tile on top, even if they are siblings (same y value, different x value). This makes it really annoying to work with y sorting as you would need to account for this as well (or restrict yourself to non overlapping tiles).

There is another issue: If you have multiple layers like the background and collidables, then there is no way to disable the y-sorting for the background but enable it for the collidables (at least as far as I am aware). So you would also have to account for that.

All in all this is much more complex then I initially thought, and I am certainly not smart enough to figure out how to implement this with the built-in y-sort of bevy_ecs_tilemap. The only alternative I see is to implement the sprites you want to y-sort manually and have them completely separate from LDtk (or use some entities or similar in LDtk and then spawn the appropriate sprites later in bevy, but this seems like quite the hassle).

Trouv commented 7 months ago

Hmm yeah my knowledge of bevy_ecs_tilemap's y-sorting is iffy.

If you're doing it w/ sprites spawned by the plugin, I guess the way to go would be to adjust their z value (based on their y value) after they spawn. Trying to do it within LdtkEntity or something like that wouldn't work because that process is designed to add a Transform after the user-defined bundle is added.

Maybe something like

#[derive(Component, Default)]
struct YSort;

#[derive(Component, Default)]
struct YSortOriginalZ(f32);

#[derive(Bundle, Default, LdtkEntity)]
struct PotBundle {
    #[sprite_sheet_bundle]
    sprite: SpriteSheetBundle,
    y_sort: YSort,
}

fn initialize_y_sort_original_z(y_sorts_without_original_z: Query<(Entity, &Transform), (With<YSort>, Without<YSortOriginalZ>)>, mut commands: Commands) {
     for (entity, transform) in y_sorts_without_original_z.iter() {
        commands.entity(entity).insert(YSortOriginalZ(transform.translation.z));
    }
}

fn calculate_z_for_y_sort(mut y_sorted_transforms: Query<(&mut Transform, YSortOriginalZ), Changed<Transform>>) {
     for (mut transform, original_z) in y_sorted_transforms.iter_mut() {
        let new_z = original_z.0 - (transform.translation.y / 100.0);
        if transform.translation.z != new_z { // extra layer of change detection to avoid mutable access maybe idk
            transform.translation.z = new_z;
        }
    }
}

Once again I haven't tried this, but that might be a good starting point

PraxTube commented 7 months ago

That is actually exactly what I ended up trying. I was able to get it to work now. I also used the field instaces as a guiding example. There are some things to be aware of though, like the fact that the entities are children of the level (which will change their Transform) and that the translation.z will be changed according to the order of the layers in LDtk. Overlapping tiles in the same layer is also a problem (which I don't account for in my solution as I don't need to as of now). This is how I implemented.

EDIT: The parent issue can be partially resolved by using Worldly. In my case it's also better to spawn the SpriteSheetBundle separately, see here for these changes.