bevy_kenney_assets/
lib.rs1use 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
12pub 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#[non_exhaustive]
51#[derive(Debug, Error)]
52pub enum KenneySpriteSheetAssetLoaderError {
53 #[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 reader: &'a mut Reader<'_>,
76 _settings: &'a (),
77 load_context: &'a mut LoadContext<'_>,
78 ) -> Result<Self::Asset, Self::Error> {
79 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}