bevy_kenney_assets/
lib.rs

1use bevy_app::{App, Plugin};
2use bevy_asset::{
3    io::Reader, Asset, AssetApp, AssetLoader, AsyncReadExt,
4    Handle, LoadContext, LoadedAsset,
5};
6use bevy_math::{URect, UVec2};
7use bevy_reflect::TypePath;
8use bevy_render::texture::Image;
9use bevy_sprite::TextureAtlasLayout;
10use thiserror::Error;
11
12/// Kenney makes [amazing assets](https://kenney.nl/).
13///
14/// Often 2d assets come with a spritesheet and
15/// an xml file describing said spritesheet.
16///
17/// This Plugin contains a loader for said
18/// spritesheets.
19pub struct KenneyAssetPlugin;
20
21impl Plugin for KenneyAssetPlugin {
22    fn build(&self, app: &mut App) {
23        app
24          .init_asset::<KenneySpriteSheetAsset>()
25          .init_asset_loader::<KenneySpriteSheetAssetLoader>();
26    }
27}
28
29#[derive(Debug)]
30pub struct SubTexture {
31    pub name: String,
32    pub x: u32,
33    pub y: u32,
34    pub width: u32,
35    pub height: u32,
36}
37
38#[derive(Asset, TypePath, Debug)]
39pub struct KenneySpriteSheetAsset {
40    pub textures: Vec<SubTexture>,
41    pub sheet: Handle<Image>,
42    pub texture_atlas_layout: Handle<TextureAtlasLayout>,
43}
44
45#[derive(Default)]
46pub struct KenneySpriteSheetAssetLoader;
47
48/// Possible errors that can be produced by
49/// [`KenneySpriteSheetAssetLoader`]
50#[non_exhaustive]
51#[derive(Debug, Error)]
52pub enum KenneySpriteSheetAssetLoaderError {
53    /// An [IO](std::io) Error
54    #[error("Could not load asset: {0}")]
55    Io(#[from] std::io::Error),
56
57    #[error("unable to load dependency")]
58    LoadDirect(#[from] bevy_asset::LoadDirectError),
59
60    #[error("xml parse error")]
61    XmlParse(#[from] roxmltree::Error),
62
63    #[error("Elements in .xml file must have: x, y, width, height, and name")]
64    InvalidSubTexture,
65}
66
67impl AssetLoader for KenneySpriteSheetAssetLoader {
68    type Asset = KenneySpriteSheetAsset;
69    type Settings = ();
70    type Error = KenneySpriteSheetAssetLoaderError;
71    async fn load<'a>(
72        &'a self,
73        // TODO: 0.15
74        // reader: &'a mut dyn Reader,
75        reader: &'a mut Reader<'_>,
76        _settings: &'a (),
77        load_context: &'a mut LoadContext<'_>,
78    ) -> Result<Self::Asset, Self::Error> {
79        // original path must be the xml file
80        let original_path =
81            load_context.asset_path().path();
82        let image_path =
83            original_path.with_extension("png");
84        let image = load_context
85            .loader()
86            .direct()
87            .load::<Image>(image_path.clone())
88            .await?;
89        let sheet_handle: Handle<Image> =
90            load_context.load(image_path);
91
92        let spritesheet_image = image.get();
93        let spritesheet_size = spritesheet_image.size();
94
95        let mut xml_string = String::new();
96        reader.read_to_string(&mut xml_string).await?;
97
98        let doc = roxmltree::Document::parse(&xml_string)?;
99
100        let sheet_dimensions = UVec2::new(
101            spritesheet_size.x,
102            spritesheet_size.y,
103        );
104
105        let mut layout =
106            TextureAtlasLayout::new_empty(sheet_dimensions);
107        let sub_textures = doc
108            .descendants()
109            .filter(|element| {
110                element.tag_name() == "SubTexture".into()
111            })
112            .map(|tex| {
113                let x: u32 = tex.attribute("x")?.parse().ok()?;
114                let y: u32 = tex.attribute("y")?.parse().ok()?;
115                let width: u32 =
116                    tex.attribute("width")?.parse().ok()?;
117                let height: u32 =
118                    tex.attribute("height")?.parse().ok()?;
119
120                layout.add_texture(URect::from_corners(
121                    UVec2::new(x, y),
122                    UVec2::new(
123                        x + width,
124                        y + height,
125                    ),
126                ));
127                Some(SubTexture {
128                    name: tex
129                        .attribute("name")?
130                        .to_string(),
131                    x,
132                    y,
133                    width,
134                    height,
135                })
136            })
137            .collect::<Option<Vec<SubTexture>>>().ok_or(
138                KenneySpriteSheetAssetLoaderError::InvalidSubTexture
139            )?;
140        let texture_atlas_layout =
141            LoadedAsset::from(layout);
142        let layout_handle = load_context
143            .add_loaded_labeled_asset(
144                "texture_atlas_layout",
145                texture_atlas_layout,
146            );
147        Ok(KenneySpriteSheetAsset {
148            textures: sub_textures,
149            sheet: sheet_handle,
150            texture_atlas_layout: layout_handle,
151        })
152    }
153
154    fn extensions(&self) -> &[&str] {
155        &["xml"]
156    }
157}