Trouv / bevy_ecs_ldtk

ECS-friendly ldtk plugin for bevy, leveraging bevy_ecs_tilemap
Other
630 stars 74 forks source link

Loading custom sprite assets #236

Open musjj opened 9 months ago

musjj commented 9 months ago

I'm trying to use bevy_aseprite to load my player sprite. It looks like that this crate heavily relies on macros in order to load entity assets, so I'm not sure how to integrate it.

The bevy_aseprite library requires the user to have the following bundle in the entity:

pub struct AsepriteBundle {
    pub transform: Transform,
    pub global_transform: GlobalTransform,
    pub animation: AsepriteAnimation,
    pub aseprite: Handle<Aseprite>, // <- The asset
}

Since you're not supposed to spawn the entities manually, how do I pass the asset into the bundle? Do I need to make a custom macro for this?

Trouv commented 9 months ago

You can actually use a custom implementation of LdtkEntity for this. Since AsepriteBundle is a foreign type, you'll need to wrap it in a local type to implement a foreign trait like LdtkEntity on it, but it is doable. It does give you access to the asset server:

#[derive(Default, Bundle)]
pub struct PlayerBundle {
    aseprite_bundle: AsepriteBundle
    // other player components..
}

impl LdtkEntity for PlayerBundle {
    fn bundle_entity(
        entity_instance: &EntityInstance,
        layer_instance: &LayerInstance,
        tileset: Option<&Handle<Image>>,
        tileset_definition: Option<&TilesetDefinition>,
        asset_server: &AssetServer,
        texture_atlases: &mut Assets<TextureAtlas>
    ) -> Self {
        // build your PlayerBundle here
    }
}

You also have other options than the macros when spawning entities, and it's definitely not ideal when you need more access to the world than the macros/LdtkEntity trait provides. A common pattern is the "blueprint pattern", where you have the plugin spawn a smaller or marker component on your entity, and then you do some post-processing on it in a normal bevy system.

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

// register this to the app via one of the LdtkEntityAppExt methods 
#[derive(Default, Bundle, LdtkEntity)]
struct PlayerBundle {
    marker: PlayerMarker,
}

fn add_aseprite_to_player(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    player_query: Query<(Entity, &Transform), Added<PlayerMarker>>
) {
    // add any component/bundle you want to the player, with access to the asset server
}

Personally I prefer doing the latter since implementing LdtkEntity manually is a bit verbose and doesn't give you much access to the world if you need it, but for the time being it is more convenient for getting access to most relevant LDtk data.

musjj commented 9 months ago

Thanks, I'll go with the second method. There's this issue: https://github.com/Trouv/bevy_ecs_ldtk/issues/177, but will the workflow for this kind of stuff improve in the future? I feel that expecting the user to always use SpriteSheetBundle is a bit too restrictive.

Trouv commented 9 months ago

It will. However, it might not be in the form #177 is proposing. Right now I'm gradually working on improving the "blueprint pattern" (in particular, making it easier to access LDtk asset data from normal systems). I don't really expect users to always use a SpriteSheetBundle, it's just a common use case so the plugin provides an attribute macro for it.