bevy_ecs_tilemap icon indicating copy to clipboard operation
bevy_ecs_tilemap copied to clipboard

Question: GPUAnimated Exclusive to LayerBuilder?

Open mbolt35 opened this issue 3 years ago • 2 comments

Thanks for the amazing project - I've had a blast building on this plugin the last few months.

I've noticed that inserting a GPUAnimated component to a tile Entity after spawning the map (it may be as early as the build_layer call) no-ops. I'm assuming that this is the intentional design, and that I should just use a bevy system to advance the tile's animation?

The only downside to that approach is forcing an update of the entire chunk on each animation tick. Is there another approach that I'm missing?

Thanks again!

mbolt35 avatar Jul 19 '22 02:07 mbolt35

I'm not sure why this would noop, can you provide a small example it might help in figuring out what is wrong.

StarArawn avatar Jul 24 '22 13:07 StarArawn

Hey @StarArawn - Thanks for the response. To demonstrate this to myself (why I thought this was intended behavior), I just used the animated_tiles example:

The animations work as expected when I insert the GPUAnimated before calling map_query.build_layer -- ie:

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

    let texture_handle = asset_server.load("tiles.png");

    // Create map entity and component:
    let map_entity = commands.spawn().id();
    let mut map = Map::new(0u16, map_entity);

    // Creates a new layer builder with a layer entity.
    let (mut layer_builder, _) = LayerBuilder::new(
        &mut commands,
        LayerSettings::new(
            MapSize(1, 1),
            ChunkSize(4, 4),
            TileSize(50.0, 50.0),
            TextureSize(350.0, 50.0),
        ),
        0u16,
        0u16,
    );

    layer_builder.set_all(TileBundle::default());

    // Adds an animated tile at (2, 2)
    if let Ok(tile_entity) = layer_builder.get_tile_entity(&mut commands, TilePos(2, 2)) {
        commands.entity(tile_entity).insert(GPUAnimated::new(0, 6, 0.95));
    }

    // Builds the layer.
    // Note: Once this is called you can no longer edit the layer until a hard sync in bevy.
    let layer_entity = map_query.build_layer(&mut commands, layer_builder, texture_handle);

    // Required to keep track of layers for a map internally.
    map.add_layer(&mut commands, 0u16, layer_entity);

    // Spawn Map
    // Required in order to use map_query to retrieve layers/tiles.
    commands
        .entity(map_entity)
        .insert(map)
        .insert(Transform::from_xyz(-100.0, -100.0, 0.0))
        .insert(GlobalTransform::default());
}

The case that was not working is nearly identical except that I insert the GPUAnimated on a keypress:

fn add_animated_tile(commands: &mut Commands, map_query: &mut MapQuery) {
    let position = TilePos(2, 2);

    if let Ok(tile_entity) = map_query.get_tile_entity(position, 0u16, 0u16) {
        commands.entity(tile_entity).insert(GPUAnimated::new(0, 6, 0.95));
    }
}

fn on_key_press(mut commands: Commands, keyboard_input: Res<Input<KeyCode>>, mut map_query: MapQuery) {
    if keyboard_input.just_released(KeyCode::Q) {
        add_animated_tile(&mut commands, &mut map_query);
    }
}

Although, while responding, I think I may have realized the mistake. It looks like the issue is that I would need to call notify_chunk_for_tile in addition to inserting the GPUAnimated. So, the previous add_animated_tile would need to be something like:

fn add_animated_tile(commands: &mut Commands, map_query: &mut MapQuery) {
    let position = TilePos(2, 2);

    if let Ok(tile_entity) = map_query.get_tile_entity(position, 0u16, 0u16) {
        commands.entity(tile_entity).insert(GPUAnimated::new(0, 6, 0.95));
        map_query.notify_chunk_for_tile(position, 0u16, 0u16);
    }
}

Just re-tested with the notify added and it works! This seems obvious now, silly mistake on my part. I have another relevant question (I can create another "issue" if necessary):

The "synchronized" animation behavior I get from manually ticking the animating tiles was actually what I needed, but I would still rather use the more optimized gpu animation. For example, in order for the animations to line up correctly, they must each use the same tile index:

https://user-images.githubusercontent.com/334480/180813208-fb019807-04a3-4391-abbd-740f1de54ee4.mp4

However, because the tiles can be added at different times, if I used the GPUAnimated component, they could become out of sync correct? Any ideas on a good way to synchronize the animation ticks for a specific tile type?

mbolt35 avatar Jul 25 '22 15:07 mbolt35

Closing -- user mistake :)

mbolt35 avatar Aug 11 '22 02:08 mbolt35