bevy_animations/
plugins.rs

1use core::panic;
2
3use bevy::prelude::*;
4
5use crate::*;
6
7/// A plugin for Bevy that adds support for animations.
8///
9/// ```
10/// use bevy_animations::AnimationsPlugin;
11/// use bevy::prelude::*;
12///
13/// App::new()
14///     .add_plugins(DefaultPlugins)
15///     .add_plugins(AnimationsPlugin {
16///         pixels_per_meter: 20. // your desired pixels_per_meter
17///     })
18///     .run()
19/// ```
20/// Note that the `pixels_per_meter` field will be used for your [`TransformAnimation`](crate::animations::TimedAnimation)
21#[derive(Debug, Default)]
22pub struct AnimationsPlugin {
23    /// The number of pixels per meter
24    pub pixels_per_meter: f32,
25}
26
27impl Plugin for AnimationsPlugin {
28    /// Builds the plugin
29    fn build(&self, app: &mut App) {
30        app.insert_resource(AnimationsConfig {
31            pixels_per_meter: self.pixels_per_meter,
32        })
33        .add_event::<AnimationEvent>()
34        .add_event::<ResetAnimationEvent>()
35        .add_event::<FXAnimationEvent>()
36        .insert_resource(Animations::default())
37        .insert_resource(EntitesToRemove::default())
38        .add_systems(
39            Update,
40            (
41                catch_fx_animation_events,
42                catch_animation_events,
43                catch_reset_events,
44                remove_entites,
45            )
46                .chain(),
47        );
48    }
49}
50
51/// Main System That Checks for Incoming events
52/// If any incoming events are found they are checked to make sure they are new and if they are the Handle<TextureAtlas> is changed for the entity
53/// After checking the events. All the [`AnimatingEntities`](crate::AnimatingEntities) have their current animations cycled
54fn catch_animation_events(
55    time: Res<Time>,
56    mut query: Query<(
57        &mut TextureAtlas,
58        &mut Handle<Image>,
59        &mut Sprite,
60        &mut Transform,
61        &Animator,
62    )>,
63    mut animations: ResMut<Animations>,
64    mut entities_to_remove: ResMut<EntitesToRemove>,
65    config: Res<AnimationsConfig>,
66    mut animation_events: EventReader<AnimationEvent>,
67    mut commands: Commands,
68) {
69    // Our main event loop
70    for event in animation_events.read() {
71        if !animations.has_entity(&event.1) {
72            panic!("Entity Not Found in Map For {} animation make sure your adding every necessary component to the entity i.e `Animator`", event.0);
73        }
74        if !animations.has_animation(event.0) {
75            panic!("Animation {} not found", event.0);
76        }
77        // Query the texture the sprite and the current direction of the entity
78        let (mut texture_atlas, mut texture, _, _, animator) =
79            match query.get_mut(event.1) {
80                Ok(handle) => handle,
81                Err(_) => {
82                    // If we didn't find the entity from the query it doesn't exist anymore and should be removed via the remove_entites system
83                    entities_to_remove.0.push(event.1);
84                    continue;
85                }
86            };
87        let direction = animator.get_direction();
88        // If incoming event is new
89        if animations
90            .is_new_animation(event.0, &event.1)
91            .unwrap_or_else(|| {
92                panic!(
93                    "Something Went Terribly Wrong Getting New Animation For {}",
94                    event.0
95                )
96            })
97        {
98            let new_animation_handles = animations.get_handles(event.0).unwrap_or_else(|| {
99                panic!(
100                    "Something Went Terribly Wrong Getting Animation For {}",
101                    event.0
102                )
103            });
104            // Get the animating entity from the entity passed in from our event
105            let animating_entity = animations.get_entity(&event.1).unwrap_or_else(|| panic!("Entity Not Found in Map For {} animation make sure your adding every necessary component to the entity i.e `AnimationDirection`", event.0));
106            // info!("{} {:?} {:?}", event.0, event.1, new_animation_handle.id());
107
108            // Get the Arc pointer for the animation
109            let Some(new_animation_arc) = animating_entity.animations.get(event.0) else {
110                panic!("Animation `{}` not found for {:?} make sure the name matches your configuration", event.0, animating_entity.entity);
111            };
112
113            // Unlock the animation if we don't do this we will hit a deadlock whenever we try to unlock the Arc<Mutex<>>
114            let mut new_animation = new_animation_arc.lock().unwrap();
115            let mut blocking = false;
116            let mut new_priority = 0;
117            let mut sprite_index = 0;
118
119            // Get the temp variables above so we don't need to twice
120            if let Some(new_timed_animation) = new_animation.timed_animation() {
121                blocking = new_timed_animation.blocking;
122                new_priority = new_timed_animation.blocking_priority;
123                sprite_index =
124                    new_timed_animation.sprite_index(&animating_entity.last_valid_direction);
125            } else if let Some(new_singe_frame_animation) = new_animation.single_frame_animation() {
126                blocking = new_singe_frame_animation.blocking;
127                new_priority = new_singe_frame_animation.blocking_priority;
128                sprite_index =
129                    new_singe_frame_animation.sprite_index(&animating_entity.last_valid_direction);
130            }
131            // If the new animation isn't a timed or single_frame one we don't care about blocking or priority
132            else if let Some(new_transform_animation) = new_animation.transform_animation() {
133                sprite_index =
134                    new_transform_animation.sprite_index(&animating_entity.last_valid_direction);
135            }
136
137            // If we are in a blocking animation we don't want to changed our animation state
138            // info!("{}", animating_entity.in_blocking_animation);
139            if animating_entity.in_blocking_animation {
140                // Check the new animations priority from the current one
141                let mut curr_animation = animating_entity.curr_animation.lock().unwrap();
142                if let Some(curr_timed_animation) = curr_animation.timed_animation() {
143                    if curr_timed_animation.blocking_priority > new_priority {
144                        // info!("blocking animation");
145                        continue;
146                    }
147                } else if let Some(curr_single_frame_animation) =
148                    curr_animation.single_frame_animation()
149                {
150                    // info!("{} {} {}", curr_single_frame_animation.blocking_priority, new_priority, curr_single_frame_animation.blocking_finished);
151                    if curr_single_frame_animation.blocking_priority > new_priority
152                        && !curr_single_frame_animation.blocking_finished
153                    {
154                        // info!("blocking animation");
155                        continue;
156                    }
157                } else {
158                    // info!("blocking animation");
159                    continue;
160                }
161            }
162
163            animating_entity
164                .curr_animation
165                .lock()
166                .unwrap()
167                .reset_animation();
168            animating_entity.curr_animation = new_animation_arc.clone();
169            animating_entity.in_blocking_animation = blocking;
170
171            texture_atlas.index = sprite_index;
172            texture_atlas.layout = new_animation_handles.layout();
173            *texture = new_animation_handles.image();
174        }
175
176        let animating_entity = animations.get_entity(&event.1).unwrap_or_else(|| panic!("Entity Not Found in Map For {} animation make sure your adding every necessary component to the entity i.e `AnimationDirection`", event.0));
177        animating_entity.curr_animation_called = true;
178
179        // If our direction is changed we can set the current direction
180        if animating_entity.curr_direction != *direction {
181            animating_entity.curr_direction = direction.clone();
182            // We don't want to set a Still direction to our last valid direction field because our animations won't be right
183            if *direction != AnimationDirection::Still {
184                animating_entity.last_valid_direction = direction.clone();
185            }
186        }
187    }
188
189    // Our main animating loop
190    for (entity, animation_entity) in animations.entities.iter_mut() {
191        // Query the sprite and transform from the entity
192        let (texture_atlas, _, sprite, transform, _) =
193            match query.get_mut(*entity) {
194                Ok(query) => query,
195                Err(_) => {
196                    // if we didn't find the entity in the query we should remove it as it doesn't exist anymore
197                    if !animation_entity.fx_animation {
198                        entities_to_remove.0.push(*entity);
199                    }
200                    continue;
201                }
202            };
203        // unlock the current animation once so we don't hit a deadlock
204        let mut curr_animation = animation_entity.curr_animation.lock().unwrap();
205
206        // if the current animation wasn't started via an `AnimationEvent`
207        if !animation_entity.curr_animation_called {
208            continue;
209        }
210
211        // info!("{}", curr_animation.get_name());
212
213        // if the current animation is transform based we should cycle it
214        if let Some(transform_animation) = curr_animation.transform_animation() {
215            if animation_entity.in_blocking_animation {
216                continue;
217            }
218            if transform_animation.cycle_animation(
219                sprite,
220                texture_atlas,
221                &animation_entity.last_valid_direction,
222                transform,
223                config.pixels_per_meter,
224            ).is_none() {
225                if animation_entity.fx_animation {
226                    entities_to_remove.0.push(*entity);
227                    commands.entity(*entity).despawn_recursive();
228                }
229                animation_entity.curr_animation_called = false;
230            }
231        }
232        // if our current animation is timed based we should cycle it
233        else if let Some(timed_animation) = curr_animation.timed_animation() {
234            if timed_animation.cycle_animation(
235                sprite,
236                texture_atlas,
237                &animation_entity.last_valid_direction,
238                time.delta(),
239            ).is_none() {
240                if animation_entity.fx_animation {
241                    entities_to_remove.0.push(*entity);
242                    commands.entity(*entity).despawn_recursive();
243                }
244                animation_entity.in_blocking_animation = false;
245                animation_entity.curr_animation_called = false;
246            }
247        }
248        // if the current animation is linear time based we should cycle it
249        else if let Some(linear_timed_animation) = curr_animation.linear_timed_animation() {
250            if linear_timed_animation.cycle_animation(texture_atlas, time.delta()).is_none() {
251                if animation_entity.fx_animation {
252                    entities_to_remove.0.push(*entity);
253                    commands.entity(*entity).despawn_recursive();
254                }
255                animation_entity.in_blocking_animation = false;
256                animation_entity.curr_animation_called = false;
257            }
258        }
259        // if the current animation is linear transform based we should cycle it
260        else if let Some(linear_transform_animation) = curr_animation.linear_transform_animation()
261        {
262            if linear_transform_animation.cycle_animation(
263                texture_atlas,
264                transform,
265                config.pixels_per_meter,
266            ).is_none() {
267                if animation_entity.fx_animation {
268                    entities_to_remove.0.push(*entity);
269                    commands.entity(*entity).despawn_recursive();
270                }
271                animation_entity.curr_animation_called = false;
272            }
273        }
274        // if the current animation is a single frame animation
275        else if let Some(single_frame_animation) = curr_animation.single_frame_animation() {
276            single_frame_animation.cycle_animation(
277                sprite,
278                texture_atlas,
279                &animation_entity.last_valid_direction,
280                time.delta(),
281            )
282        }
283        // if we get here something bad happened it will most likely never hit as the typing is pretty strong
284        else {
285            panic!(
286                "Something Went Terribly Wrong Animating {} Check Your Configurations",
287                curr_animation.get_name()
288            );
289        }
290    }
291}
292
293fn catch_reset_events(
294    mut query: Query<(&mut Sprite, &mut TextureAtlas, &Animator)>,
295    mut animations: ResMut<Animations>,
296    mut entities_to_remove: ResMut<EntitesToRemove>,
297    mut animation_events: EventReader<ResetAnimationEvent>,
298) {
299    for event in animation_events.read() {
300        // If the entity wasn't found in the query we want to remove it from our data structure
301        let (sprite, texture_atlas, animator) = match query.get_mut(event.0) {
302            Ok(q) => q,
303            Err(_) => {
304                entities_to_remove.0.push(event.0);
305                continue;
306            }
307        };
308        let direction = animator.get_direction();
309        let mut curr_animation = animations
310            .entities
311            .get_mut(&event.0)
312            .expect("Entity Not Found from `ResetAnimationEvent`")
313            .curr_animation
314            .lock()
315            .unwrap();
316        // Try and get the current animation
317        // If it is time based
318        if let Some(timed_animation) = curr_animation.timed_animation() {
319            timed_animation.reset_animation(Some(sprite), Some(texture_atlas), Some(direction));
320        }
321        // If it is transform based
322        else if let Some(transform_animation) = curr_animation.transform_animation() {
323            transform_animation.reset_animation(Some(sprite), Some(texture_atlas), Some(direction));
324        }
325        // If it is linear time based
326        else if let Some(linear_timed_animation) = curr_animation.linear_timed_animation() {
327            linear_timed_animation.reset_animation(Some(texture_atlas));
328        }
329        // If it is linear transform based
330        else if let Some(linear_transform_animation) = curr_animation.linear_transform_animation()
331        {
332            linear_transform_animation.reset_animation(Some(texture_atlas));
333        }
334        // If it is single frame based
335        else if let Some(single_frame_animation) = curr_animation.single_frame_animation() {
336            single_frame_animation.reset_animation(Some(sprite), Some(direction));
337        } else {
338            panic!("Something went terribly wrong resetting the current animation");
339        }
340    }
341}
342
343fn catch_fx_animation_events(
344    mut event_reader: EventReader<FXAnimationEvent>,
345    mut commands: Commands,
346    mut animations: ResMut<Animations>,
347) {
348    for event in event_reader.read() {
349        let entity = commands.spawn(Animator::default()).id();
350        let Ok(sprite_sheet_bundle) = animations.start_fx_animation(entity, event.0, event.1)
351        else {
352            warn!("There was a problem spawning your FXAnimation {}", event.0);
353            continue;
354        };
355
356        commands
357            .entity(entity)
358            .insert(sprite_sheet_bundle)
359            .insert(FXAnimation)
360            .insert(Name::new("FX Animation"));
361    }
362}
363
364/// This system is for any cleanup of despawned entities
365fn remove_entites(
366    mut animations: ResMut<Animations>,
367    mut entities_to_remove: ResMut<EntitesToRemove>,
368) {
369    for entity in entities_to_remove.0.iter() {
370        animations.entities.remove(entity);
371    }
372    entities_to_remove.0.clear();
373}