use std::{collections::VecDeque, ops::Range}; use crate::{loader::Aseprite, NotLoaded, UiTag}; use aseprite_loader::binary::chunks::tags::AnimationDirection as RawDirection; use bevy::prelude::*; pub struct AsepriteAnimationPlugin; impl Plugin for AsepriteAnimationPlugin { fn build(&self, app: &mut App) { app.add_event::(); app.add_systems( Update, (insert_aseprite_animation, update_aseprite_animation).chain(), ); app.register_type::(); app.register_type::(); } } /// The `AsepriteAnimationBundle` bundles the components needed to render an animation. /// ```rust /// // example from examples/animation.rs /// command.spawn(AsepriteAnimationBundle { /// aseprite: server.load("player.aseprite"), /// transform: Transform::from_translation(Vec3::new(15., -20., 0.)), /// animation: Animation::default().with_tag("walk-right"), /// sprite: Sprite { /// flip_x: true, /// ..default() /// }, /// ..default() /// }) /// ``` /// If a tag is present `repeat` and `direction` in `AnimationControl` will be overwritten by the values /// porvided in the aseprite file, but can be interacted with at runtime. #[derive(Bundle, Default)] pub struct AsepriteAnimationBundle { pub aseprite: Handle, pub animation: Animation, pub animation_state: AnimationState, pub sprite: Sprite, pub atlas: TextureAtlas, pub transform: Transform, pub global_transform: GlobalTransform, pub visibility: Visibility, pub inherited_visibility: InheritedVisibility, pub view_visibility: ViewVisibility, pub not_loaded: NotLoaded, } #[derive(Bundle, Default)] pub struct AsepriteAnimationUiBundle { pub aseprite: Handle, pub animation: Animation, pub animation_state: AnimationState, pub atlas: TextureAtlas, pub ui_tag: UiTag, pub not_loaded: NotLoaded, } #[derive(Component, Reflect)] pub struct Animation { pub tag: Option, pub speed: f32, pub playing: bool, pub repeat: AnimationRepeat, pub direction: AnimationDirection, pub queue: VecDeque<(String, AnimationRepeat)>, } impl Default for Animation { fn default() -> Self { Self { tag: None, speed: 1.0, playing: false, repeat: AnimationRepeat::Loop, direction: AnimationDirection::Forward, queue: VecDeque::new(), } } } impl Animation { /// animation speed multiplier, default is 1.0 pub fn with_speed(mut self, speed: f32) -> Self { self.speed = speed; self } /// provide a tag string. Panics at runtime, if animation is not found pub fn with_tag(mut self, tag: &str) -> Self { self.tag = Some(tag.to_string()); self } /// sets a repeat count, defaults is loop pub fn with_repeat(mut self, repeat: AnimationRepeat) -> Self { self.repeat = repeat; self } /// provides an animation direction, maybe overwritten by aseprite tag pub fn with_direction(mut self, direction: AnimationDirection) -> Self { self.direction = direction; self } /// chains an animation after the current one is done pub fn with_then(mut self, tag: &str, repeats: AnimationRepeat) -> Self { self.queue.push_back((tag.to_string(), repeats)); self } /// instanly starts playing a new animation, clearing any item left in the queue. pub fn play(&mut self, tag: &str, repeat: AnimationRepeat) { self.tag = Some(tag.to_string()); self.repeat = repeat; self.queue.clear(); } /// chains an animation after the current one is done pub fn then(&mut self, tag: &str, repeats: AnimationRepeat) { self.queue.push_back((tag.to_string(), repeats)); } /// clears any queued up animations pub fn clear_queue(&mut self) { self.queue.clear() } } impl From<&str> for Animation { fn from(tag: &str) -> Self { Animation { tag: Some(tag.to_string()), speed: 1.0, ..Default::default() } } } #[derive(Component, Default, Reflect)] pub struct AnimationState { current_frame: usize, elapsed: std::time::Duration, current_direction: PlayDirection, } impl AnimationState { pub fn current_frame(&self) -> usize { self.current_frame } } #[derive(Default, Reflect)] enum PlayDirection { #[default] Forward, Backward, } #[derive(Event, Debug, Reflect)] pub enum AnimationEvents { Finished(Entity), LoopCycleFinished(Entity), } #[derive(Default, Reflect)] pub enum AnimationDirection { #[default] Forward, Reverse, PingPong, PingPongReverse, } impl From for AnimationDirection { fn from(direction: RawDirection) -> AnimationDirection { match direction { RawDirection::Forward => AnimationDirection::Forward, RawDirection::Reverse => AnimationDirection::Reverse, RawDirection::PingPong => AnimationDirection::PingPong, RawDirection::PingPongReverse => AnimationDirection::PingPongReverse, _ => panic!("Invalid AnimationDirection"), } } } #[derive(Default, Component, Reflect)] pub enum AnimationRepeat { #[default] Loop, Count(u32), } impl From for AnimationRepeat { fn from(value: u16) -> Self { match value { 0 => AnimationRepeat::Loop, n => AnimationRepeat::Count(u32::from(n)), } } } fn insert_aseprite_animation( mut query: Query< ( Entity, &mut AnimationState, &mut Animation, &mut TextureAtlas, &Handle, Option<&UiTag>, ), With, >, mut cmd: Commands, asesprites: Res>, ) { query.iter_mut().for_each( |(entity, mut state, mut control, mut atlas, aseprite_handle, maybe_ui)| { let Some(aseprite) = asesprites.get(aseprite_handle) else { return; }; let maybe_tag = control .tag .as_ref() .map(|tag| aseprite.tags.get(tag)) .flatten(); let start_frame_index = usize::from(maybe_tag.map(|tag| *tag.range.start()).unwrap_or(0)); let end_frame_index = usize::from( maybe_tag .map(|tag| *tag.range.end()) .unwrap_or(aseprite.frame_durations.len() as u16 - 1), ); state.current_frame = start_frame_index; state.elapsed = std::time::Duration::ZERO; control.playing = true; if let Some(tag) = maybe_tag { control.direction = AnimationDirection::from(tag.direction); state.current_direction = match control.direction { AnimationDirection::Reverse | AnimationDirection::PingPongReverse => { state.current_frame = end_frame_index; PlayDirection::Backward } _ => PlayDirection::Forward, }; } else { match control.direction { AnimationDirection::Reverse | AnimationDirection::PingPongReverse => { state.current_frame = end_frame_index; } _ => (), }; } atlas.layout = aseprite.atlas_layout.clone(); atlas.index = aseprite.get_atlas_index(state.current_frame); if let Some(mut cmd) = cmd.get_entity(entity) { match maybe_ui { Some(_) => { cmd.remove::() .insert(UiImage::new(aseprite.atlas_image.clone())); } None => { cmd.remove::() .insert(aseprite.atlas_image.clone()); } }; }; }, ); } fn update_aseprite_animation( mut query: Query<( Entity, &mut AnimationState, &mut TextureAtlas, &mut Animation, &Handle, )>, mut events: EventWriter, asesprites: Res>, time: Res