bevy_animator/
aseprite.rs1use asefile::AsepriteFile;
7use bevy::{asset::{AssetLoader, AsyncReadExt}, ecs::query::WorldQuery, prelude::{Vec2, *}, render::{render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::ImageSampler}, sprite::Anchor, utils::HashMap};
8use btree_range_map::RangeMap;
9
10use crate::animation::{Animation, Animator};
11
12pub(crate) struct AsepriteAnimationPlugin;
17
18impl Plugin for AsepriteAnimationPlugin {
19 fn build(&self, app: &mut App) {
20 app
21 .init_asset_loader::<AsepriteLoader>()
22 .init_asset::<Aseprite>()
23 ;
24 }
25}
26
27#[allow(dead_code)]
33#[derive(Asset, TypePath)]
34pub struct Aseprite {
35 layout : Handle<TextureAtlasLayout>,
36 image : Handle<Image>,
37 duration : Vec<u32>,
38 anims : HashMap<String, Anim>,
39 dimensions : UVec2
40}
41
42#[derive(Clone, Debug, Default, PartialEq)]
43struct Anim {
44 pub frame_map : RangeMap<f32, usize>,
45 duration : f32
46}
47
48#[derive(Default)]
54pub struct AsepriteLoader;
55
56impl AssetLoader for AsepriteLoader {
57 type Asset = Aseprite;
58
59 type Settings = ();
60
61 type Error = asefile::AsepriteParseError;
62
63 fn load<'a>(
64 &'a self,
65 reader: &'a mut bevy::asset::io::Reader,
66 _: &'a Self::Settings,
67 load_context: &'a mut bevy::asset::LoadContext,
68 ) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
69 Box::pin(async move {
70 let mut bytes = Vec::new();
71 reader.read_to_end(&mut bytes).await?;
72 let aseprite = AsepriteFile::read(bytes.as_slice())?;
73
74 let image_loader = load_context.begin_labeled_asset();
75 let layout_loader = load_context.begin_labeled_asset();
76 let mut atlas = TextureAtlasBuilder::default();
77 let mut frames = Vec::new();
78 let mut durations = Vec::new();
79 for frame_index in 0..aseprite.num_frames() {
80 let frame = aseprite.frame(frame_index);
81 let mut image = Image::new(
82 Extent3d {
83 width: aseprite.width() as u32,
84 height: aseprite.height() as u32,
85 depth_or_array_layers: 1,
86 },
87 TextureDimension::D2,
88 frame.image().into_vec(),
89 TextureFormat::Rgba8UnormSrgb,
90 RenderAssetUsages::all()
91 );
92 image.sampler = ImageSampler::nearest();
93 durations.push(frame.duration());
94 frames.push(image);
95 }
96 for image in frames.iter() { atlas.add_texture(None, image); }
97 let (layout, image) = atlas.finish().expect("Failed to build texture atlas.");
98
99 let loaded_image = image_loader.finish(image, None);
100 let loaded_layout = layout_loader.finish(layout, None);
101
102 let image_handle = load_context.add_loaded_labeled_asset("atlas", loaded_image);
103 let layout_handle = load_context.add_loaded_labeled_asset("layout", loaded_layout);
104
105 let mut anims = HashMap::default();
106 for tag_index in 0..aseprite.num_tags() {
107 let tag = aseprite.tag(tag_index);
108 let duration = (tag.from_frame()..=tag.to_frame())
109 .map(|index| durations[index as usize])
110 .sum::<u32>();
111 let mut frame_map = RangeMap::new();
112 let mut last : f32 = 0.0;
113 for frame_index in tag.from_frame()..=tag.to_frame() {
114 let current_duration = durations[frame_index as usize] as f32 / duration as f32;
115 frame_map.insert(last..last + current_duration, frame_index as usize);
116 last += current_duration;
117 }
118 let anim = Anim { frame_map, duration: duration as f32 / 1000.0 };
119 anims.insert(tag.name().to_string(), anim);
120 }
121
122 let mut frame_map = RangeMap::new();
123 frame_map.insert(0.0..1.0, "test");
124
125 Ok(Aseprite { layout: layout_handle, duration: durations, image: image_handle, anims, dimensions: UVec2::new(aseprite.width() as u32, aseprite.height() as u32) })
126 })
127 }
128
129 fn extensions(&self) -> &[&str] {
130 &["ase", "aseprite"]
131 }
132}
133
134pub trait AsepriteAnimation : Sized + FromWorld {
140
141 fn get_tag_name(&self) -> &str;
145
146 fn get_anchor_pixel() -> Vec2 { Vec2::ZERO }
148
149 fn get_dimensions() -> UVec2;
151}
152
153impl <A : AsepriteAnimation + Send + Sync + 'static> Animation for A {
154 type AsociatedAsset = Aseprite;
155
156 type Query<'w, 's> = &'w mut TextureAtlas;
157
158 fn apply(
159 animator : &Animator<Self>,
160 items : &mut <Self::Query<'_, '_> as WorldQuery>::Item<'_>,
161 asset : &Self::AsociatedAsset,
162 ) {
163 items.layout = asset.layout.clone();
164
165 let tag = animator.animation.get_tag_name();
166 if let Some(anim) = asset.anims.get(tag) {
167 let frame = anim.frame_map.get(animator.progress()).unwrap_or(&0);
168 items.index = *frame;
169 }
170 }
171
172 fn duration(&self, asset : &Self::AsociatedAsset) -> f32 {
173 asset.anims.get(self.get_tag_name()).unwrap().duration
174 }
175
176 fn spawn(animation : Option<Self>, world : &mut World, path : String, entity : Entity) {
177 let animation_comp = animation.unwrap_or(Self::from_world(world));
178 let asset_server = world.get_resource::<AssetServer>().unwrap();
179 let animation : Handle<Self::AsociatedAsset> = asset_server.load(&path);
180 let image : Handle<Image> = asset_server.load(format!("{}#atlas", path));
181 let layout : Handle<TextureAtlasLayout> = asset_server.load(format!("{}#layout", path));
182
183 let anchor_origin = Vec2::new(-0.5, 0.5);
184 let anchor_pixel = Self::get_anchor_pixel();
185 let dimensions = Self::get_dimensions();
186 let anchor_x = anchor_pixel.x / dimensions.x as f32;
187 let anchor_y = anchor_pixel.y / dimensions.y as f32;
188 let anchor_offset = Vec2::new(anchor_x, -anchor_y);
189 let anchor = Anchor::Custom(anchor_origin + anchor_offset);
190
191 world.get_or_spawn(entity).unwrap()
192 .insert(Animator::new(animation_comp))
193 .insert(animation)
194 .insert(SpriteSheetBundle {
195 texture : image,
196 atlas : TextureAtlas { layout, index: 0 },
197 sprite : Sprite {
198 anchor,
199 ..Default::default()
200 },
201 ..Default::default()
202 })
203 ;
204 }
205}