bevy_animator/
aseprite.rs

1//=================================================================================
2// Here we are defining the Aseprite Animation AssetFile, and the Aseprite 
3// Animation ID.
4//=================================================================================
5
6use 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
12//=================================================================================
13//    AsepriteAnimationPlugin
14//=================================================================================
15
16pub(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//=================================================================================
28//    Aseprite Asset
29//=================================================================================
30
31/// The Aseprite Asset. Only stores animation frames and tagged animations.
32#[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//=================================================================================
49//    Aseprite Asset Loader
50//=================================================================================
51
52/// Asset Loader for Aseprite Files
53#[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
134//=================================================================================
135//    Aseprite State Animation
136//=================================================================================
137
138/// This trait will allow you to animate a sprite with an aseprite animation file. 
139pub trait AsepriteAnimation : Sized + FromWorld {
140    
141    /// Animations are defined by tags inside of asesprite files. This function will tell bevy what animation to use based
142    /// on the current state of the struct that implements this trait. This is intended to allow Enums to be used to have
143    /// animation states.
144    fn get_tag_name(&self) -> &str;
145    
146    /// This should return the pixel that the sprite should be anchored to. This defaults to the middle of the sprite.
147    fn get_anchor_pixel() -> Vec2 { Vec2::ZERO }
148    
149    /// This is the size of each frame of the animation. With asesprite, all frames are the same size.
150    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}