bevy_animations/
lib.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Mutex},
4    time::Duration,
5};
6
7use bevy::prelude::*;
8
9mod animations;
10mod plugins;
11mod types;
12
13pub use animations::*;
14pub use plugins::*;
15pub use types::*;
16
17pub mod prelude {
18    pub use crate::animations::{
19        LinearTimedAnimation, LinearTransformAnimation, SingleFrameAnimation, TimedAnimation,
20        TransformAnimation,
21    };
22    pub use crate::plugins::AnimationsPlugin;
23    pub use crate::types::{
24        Animation, AnimationDirection, AnimationDirectionIndexes, AnimationEvent, AnimationName,
25        AnimationType, Animator, FXAnimationEvent, FXBasedDirection, FlipBasedDirection,
26        IndexBasedDirection, NewAnimation, ResetAnimationEvent, YIndex,
27    };
28    pub use crate::{Animations, AnimationsConfig};
29}
30
31#[derive(Component, Clone)]
32struct FXAnimation;
33
34#[derive(Debug, Resource, Default)]
35pub struct AnimationsConfig {
36    pixels_per_meter: f32,
37}
38
39#[derive(Debug, Resource, Default)]
40pub struct EntitesToRemove(Vec<Entity>);
41
42#[derive(Component, Deref, DerefMut, Clone, Debug, Default)]
43pub struct AnimationTimer(pub Timer);
44
45#[derive(Debug)]
46pub struct AnimatingEntity {
47    pub entity: Entity,
48    pub in_blocking_animation: bool,
49    pub animations: HashMap<AnimationName, Arc<Mutex<AnimationType>>>,
50    pub curr_animation: Arc<Mutex<AnimationType>>,
51    pub curr_direction: AnimationDirection,
52    pub last_valid_direction: AnimationDirection,
53    pub curr_animation_called: bool,
54    pub fx_animation: bool,
55}
56
57#[derive(Default, Resource, Debug)]
58pub struct Animations {
59    entities: HashMap<Entity, AnimatingEntity>,
60    animations: HashMap<AnimationName, Animation>,
61    fx_animations: HashMap<AnimationName, Animation>,
62}
63
64impl Animations {
65    /// Adds a new animation to the animation pool.
66    ///
67    /// Can optionally add an entity to the animation.
68    ///
69    /// # Panics
70    /// When an animation with the same name already exists
71    pub fn insert_animation(
72        &mut self,
73        animation: NewAnimation,
74        entity: Option<Entity>,
75    ) -> &mut Self {
76        let name = animation.animation.get_name();
77        let animation = Animation {
78            handles: animation.handles,
79            animation: Arc::new(Mutex::new(animation.animation)),
80        };
81        let new_animation = Arc::clone(&animation.animation);
82        let animation = animation;
83        if self.animations.get_mut(&name).is_none() {
84            self.animations.insert(name, animation);
85        }
86        if let Some(entity) = entity {
87            if let Some(animating_entity) = self.entities.get_mut(&entity) {
88                animating_entity
89                    .animations
90                    .insert(name, Arc::clone(&new_animation));
91            } else {
92                let mut map = HashMap::new();
93                map.insert(name, Arc::clone(&new_animation));
94                self.entities.insert(
95                    entity,
96                    AnimatingEntity {
97                        entity,
98                        animations: map,
99                        curr_animation: new_animation,
100                        curr_direction: AnimationDirection::Still,
101                        last_valid_direction: AnimationDirection::default(),
102                        in_blocking_animation: false,
103                        curr_animation_called: false,
104                        fx_animation: false,
105                    },
106                );
107            }
108        }
109        self
110    }
111
112    /// Add an [Entity] to the pool without a current animation specified
113    ///
114    /// Returns [Result<(), String>] an [Err(String)] if the entity already exists in the pool
115    pub fn insert_entity(&mut self, entity: Entity) -> Result<(), String> {
116        if self.entities.contains_key(&entity) {
117            return Err(format!(
118                "Entity {:?} already exists in `Animations`",
119                entity
120            ));
121        }
122        self.entities.insert(
123            entity,
124            AnimatingEntity {
125                entity,
126                animations: HashMap::new(),
127                curr_animation: Arc::new(Mutex::new(AnimationType::default())),
128                curr_direction: AnimationDirection::Still,
129                last_valid_direction: AnimationDirection::default(),
130                in_blocking_animation: false,
131                curr_animation_called: false,
132                fx_animation: false,
133            },
134        );
135        Ok(())
136    }
137
138    /// Add an animation to an [Entity]
139    ///
140    /// Returns [Result<(), String>] an [Err(String)] if the animation already exists on the entity specified
141    pub fn add_animation_to_entity(
142        &mut self,
143        animation_name: AnimationName,
144        entity: Entity,
145    ) -> Result<(), String> {
146        if let Some(animation) = self.animations.get_mut(&animation_name) {
147            let ac_animation = Arc::new(Mutex::new(animation.animation.lock().unwrap().clone()));
148            if let Some(entity) = self.entities.get_mut(&entity) {
149                if entity.animations.contains_key(&animation_name) {
150                    return Err(format!(
151                        "Animation {:?} already exists on entity {:?}",
152                        animation_name, entity.entity
153                    ));
154                }
155                if entity.curr_animation.lock().unwrap().is_none() {
156                    entity.curr_animation = Arc::clone(&ac_animation);
157                }
158                entity.animations.insert(animation_name, ac_animation);
159            } else {
160                let mut map = HashMap::new();
161                map.insert(animation_name, Arc::clone(&ac_animation));
162                self.entities.insert(
163                    entity,
164                    AnimatingEntity {
165                        entity,
166                        animations: map,
167                        curr_animation: ac_animation,
168                        curr_direction: AnimationDirection::Still,
169                        last_valid_direction: AnimationDirection::default(),
170                        in_blocking_animation: false,
171                        curr_animation_called: false,
172                        fx_animation: false,
173                    },
174                );
175            }
176        }
177        Ok(())
178    }
179
180    /// Gets a clone of the `TextureAtlasLayout` and `Image` handle for the animation specified
181    pub fn get_handles(&self, animation_name: AnimationName) -> Option<Handles> {
182        if let Some(animation) = self.animations.get(&animation_name) {
183            return Some(animation.handles.clone());
184        }
185        None
186    }
187
188    /// Gets a clone of the `TextureAtlasLayout` and `Image` handle for the fx_animation specified
189    ///
190    /// Returns [None] if the animation does not exist
191    pub fn get_fx_handles(
192        &self,
193        animation_name: AnimationName,
194    ) -> Option<Handles> {
195        if let Some(animation) = self.fx_animations.get(&animation_name) {
196            return Some(animation.handles.clone());
197        }
198        None
199    }
200
201    /// Gets the animating entity from the entity specified
202    ///
203    /// Returns [None] if the entity does not exist in the pool
204    pub fn get_entity(&mut self, entity: &Entity) -> Option<&mut AnimatingEntity> {
205        if let Some(animating_entity) = self.entities.get_mut(entity) {
206            return Some(animating_entity);
207        }
208        None
209    }
210
211    /// Checks if the animation specified is not animating on the entity specified currently
212    ///
213    /// Returns [None] if the entity does not exist in the pool
214    pub fn is_new_animation(&self, animation_name: AnimationName, entity: &Entity) -> Option<bool> {
215        if let Some(animating_entity) = self.entities.get(entity) {
216            if animating_entity.curr_animation.lock().unwrap().get_name() != animation_name {
217                return Some(true);
218            }
219            return Some(false);
220        }
221        None
222    }
223
224    /// If the entity specified exists in the pool
225    pub fn has_entity(&self, entity: &Entity) -> bool {
226        if self.entities.contains_key(entity) {
227            return true;
228        }
229        false
230    }
231
232    /// Insert an FX animation this. In order to start the FX animation send it through an [EventWriter(FXAnimationEvent(AnimationName))]
233    pub fn insert_fx_animation(&mut self, value: NewAnimation) -> &mut Self {
234        let key = value.animation.get_name();
235        if self.fx_animations.contains_key(key) {
236            self
237        } else {
238            let animation = Animation {
239                handles: value.handles,
240                animation: Arc::new(Mutex::new(value.animation)),
241            };
242            self.fx_animations.insert(key, animation);
243            self
244        }
245    }
246
247    /// Add an FX animation to a new [AnimatingEntity]. This will start the animation specified.
248    ///
249    /// # Note
250    ///
251    /// This method is used for the backend and shouldn't be called directly. If you need to start an fx animation use [FXAnimationEvent] instead.
252    pub fn start_fx_animation(
253        &mut self,
254        key: Entity,
255        animation: AnimationName,
256        pos: Vec3,
257    ) -> Result<(SpriteBundle, TextureAtlas), ()> {
258        let name = animation;
259        let Some(animation) = self.fx_animations.get(animation) else {
260            return Err(());
261        };
262        let mut animation = animation.animation.lock().unwrap().clone();
263
264        let index = if let Some(timed_animation) = animation.timed_animation() {
265            timed_animation.sprite_index(&AnimationDirection::default())
266        } else if let Some(transform_animation) = animation.transform_animation() {
267            transform_animation.sprite_index(&AnimationDirection::default())
268        } else if let Some(linear_timed_animation) = animation.linear_timed_animation() {
269            linear_timed_animation.sprite_index(&AnimationDirection::default())
270        } else if let Some(linear_transform_animation) = animation.linear_transform_animation() {
271            linear_transform_animation.sprite_index(&AnimationDirection::default())
272        } else if let Some(single_frame_animation) = animation.single_frame_animation() {
273            single_frame_animation.sprite_index(&AnimationDirection::default())
274        } else {
275            panic!("Something Went Terribly Wrong Starting FX Animation");
276        };
277        
278        // Grab the atlas from the animations and spawn a new entity with the atlas at the specified pos
279        let handles: Handles = self
280            .get_fx_handles(name)
281            .unwrap_or_else(|| panic!("There was a problem starting FX animation {}", name))
282            .into();
283        let texture_atlas = TextureAtlas {
284            layout: handles.layout().clone(),
285            index
286        };
287        // Add the animation and entity to a new AnimatingEntity to be animated
288        self.entities.insert(
289            key,
290            AnimatingEntity {
291                entity: key,
292                in_blocking_animation: false,
293                animations: HashMap::new(),
294                curr_animation: Arc::new(Mutex::new(animation)),
295                curr_direction: AnimationDirection::default(),
296                last_valid_direction: AnimationDirection::default(),
297                curr_animation_called: true,
298                fx_animation: true,
299            },
300        );
301        Ok((
302            SpriteBundle {
303                transform: Transform::from_translation(pos),
304                texture: handles.image().clone(),
305                ..Default::default()
306            },
307            texture_atlas
308        ))
309        // Ok(SpriteSheetBundle {
310        //     atlas,
311        //     transform: Transform::from_translation(pos),
312        //     ..Default::default()
313        // })
314    }
315
316    /// if the animation exists in the pool
317    pub fn has_animation(&self, animation_name: AnimationName) -> bool {
318        if self.animations.contains_key(animation_name) {
319            return true;
320        }
321        false
322    }
323
324    /// Returns [Some(())] if the animation already exists on the entity specified
325    ///
326    /// Returns [None] if the entity was not found in the pool
327    ///
328    /// Returns [None] if the animation was not found on the entity specified
329    pub fn entity_has_animation(
330        &self,
331        animation_name: &AnimationName,
332        entity: Entity,
333    ) -> Option<()> {
334        if let Some(animating_entity) = self.entities.get(&entity) {
335            if animating_entity.animations.contains_key(animation_name) {
336                return Some(());
337            }
338            return None;
339        }
340        None
341    }
342
343    /// Returns [Some(bool)] if the entity exists and [Some(true)] if the entity is in a blocking animation
344    ///
345    /// Returns [None] if the entity was not found
346    ///
347    /// usefull to determine for example whether or not to move an entity
348    pub fn in_blocking_animation(&self, entity: Entity) -> Option<bool> {
349        self.entities
350            .get(&entity)
351            .map(|animating_entity| animating_entity.in_blocking_animation)
352    }
353
354    /// Returns [Some(bool)] if the entity exists and [Some(true)] if the entity is in an animation
355    ///
356    /// Returns [None] if the entity was not found
357    ///
358    /// useful for determining for example whether or not to initate another animation
359    pub fn in_animation(&self, entity: Entity) -> Option<bool> {
360        self.entities
361            .get(&entity)
362            .map(|animating_entity| animating_entity.curr_animation_called)
363    }
364
365    /// Returns [Some(bool)] if the entity exists and [Some(true)] if the entity is in the animation specified
366    ///
367    /// Returns [None] if the entity was not found
368    ///
369    /// useful for determining for example whether or not to initate another animation
370    pub fn doing_animation(&self, entity: Entity, animation_name: AnimationName) -> Option<bool> {
371        self.entities.get(&entity).map(|animating_entity| {
372            animating_entity.curr_animation_called
373                && animating_entity.curr_animation.lock().unwrap().get_name() == animation_name
374        })
375    }
376
377    /// Returns `true` if the [Entity] exists in the [Animations] map
378    pub fn is_inserted(&self, key: &Entity) -> bool {
379        if self.entities.contains_key(key) {
380            return true;
381        }
382        false
383    }
384
385    /// Mainly for debug purposes to see the map. Not reccomended to change item.
386    pub fn get_mut_map(&mut self) -> &mut HashMap<Entity, AnimatingEntity> {
387        &mut self.entities
388    }
389
390    /// Mainly for debug purposes to see the map. Not reccomended to change item.
391    pub fn get_map(&self) -> &HashMap<Entity, AnimatingEntity> {
392        &self.entities
393    }
394}