bones_framework/
animation.rs

1//! Animation utilities and systems.
2
3use crate::prelude::*;
4
5/// Install animation utilities into the given [`SystemStages`].
6pub fn animation_plugin(core: &mut SessionBuilder) {
7    core.stages
8        .add_system_to_stage(CoreStage::Last, update_animation_banks)
9        .add_system_to_stage(CoreStage::Last, animate_sprites);
10}
11
12/// Component that may be added to entities with an [`AtlasSprite`] to animate them.
13#[derive(Clone, HasSchema, Debug)]
14#[repr(C)]
15pub struct AnimatedSprite {
16    /// The current frame in the animation.
17    pub index: u32,
18    /// The frames in the animation.
19    ///
20    /// These are the indexes into the atlas, specified in the order they will be played.
21    // TODO: Put Animation Frames in an `Arc` to Avoid Snapshot Clone Cost.
22    pub frames: SVec<u32>,
23    /// The frames per second to play the animation at.
24    pub fps: f32,
25    /// The amount of time the current frame has been playing
26    pub timer: f32,
27    /// Whether or not to repeat the animation
28    pub repeat: bool,
29}
30
31/// Component that may be added to an [`AtlasSprite`] to control which animation, out of a set of
32/// animations, is playing.
33///
34/// This is great for players or other sprites that will change through different, named animations
35/// at different times.
36#[derive(Clone, HasSchema, Debug, Default)]
37pub struct AnimationBankSprite {
38    /// The current animation.
39    pub current: Ustr,
40    /// The collection of animations in this animation bank.
41    // TODO: Put Animation Frames in an `Arc` to Avoid Snapshot Clone Cost.
42    pub animations: SMap<Ustr, AnimatedSprite>,
43    /// The last animation that was playing.
44    pub last_animation: Ustr,
45}
46
47impl AnimationBankSprite {
48    /// Set the current animation if it exists inside of `animations` in the bank. Else does nothing.
49    pub fn set_current(&mut self, animation_name: impl Into<Ustr>) {
50        let animation_name = animation_name.into();
51        if self.animations.contains_key(&animation_name) {
52            self.current = animation_name;
53        }
54    }
55
56    /// Insert a new animation into the bank
57    pub fn insert_animation(&mut self, name: impl Into<Ustr>, animation: AnimatedSprite) {
58        let name = name.into();
59        self.animations.insert(name, animation);
60    }
61
62    /// Remove an animation from the bank
63    pub fn remove_animation(&mut self, name: &Ustr) -> Option<AnimatedSprite> {
64        self.animations.remove(name)
65    }
66
67    /// Get a reference to an animation in the bank
68    pub fn get_animation(&self, name: &Ustr) -> Option<&AnimatedSprite> {
69        self.animations.get(name)
70    }
71
72    /// Get a mutable reference to an animation in the bank
73    pub fn get_animation_mut(&mut self, name: &Ustr) -> Option<&mut AnimatedSprite> {
74        self.animations.get_mut(name)
75    }
76
77    /// Get the current animation
78    pub fn get_current_animation(&self) -> Option<&AnimatedSprite> {
79        self.animations.get(&self.current)
80    }
81
82    /// Get a mutable reference to the current animation
83    pub fn get_current_animation_mut(&mut self) -> Option<&mut AnimatedSprite> {
84        self.animations.get_mut(&self.current)
85    }
86}
87
88impl Default for AnimatedSprite {
89    fn default() -> Self {
90        Self {
91            index: 0,
92            frames: default(),
93            fps: 0.0,
94            timer: 0.0,
95            repeat: true,
96        }
97    }
98}
99
100/// System for automatically animating sprites with the [`AnimatedSprite`] component.
101pub fn animate_sprites(
102    time: Res<Time>,
103    entities: Res<Entities>,
104    mut atlas_sprites: CompMut<AtlasSprite>,
105    mut animated_sprites: CompMut<AnimatedSprite>,
106) {
107    for (_ent, (atlas_sprite, animated_sprite)) in
108        entities.iter_with((&mut atlas_sprites, &mut animated_sprites))
109    {
110        if animated_sprite.frames.is_empty() {
111            continue;
112        }
113
114        animated_sprite.timer += time.delta_seconds();
115
116        // If we are ready to go to the next frame
117        if (animated_sprite.index != animated_sprite.frames.len() as u32 - 1
118            || animated_sprite.repeat)
119            && animated_sprite.timer > 1.0 / animated_sprite.fps.max(f32::MIN_POSITIVE)
120        {
121            // Restart the timer
122            animated_sprite.timer = 0.0;
123
124            // Increment and loop around the current index
125            animated_sprite.index =
126                (animated_sprite.index + 1) % animated_sprite.frames.len() as u32;
127        }
128
129        // Set the atlas sprite to match the current frame of the animated sprite
130        atlas_sprite.index = animated_sprite.frames[animated_sprite.index as usize];
131    }
132}
133
134/// System for updating [`AnimatedSprite`]s based on thier [`AnimationBankSprite`]
135pub fn update_animation_banks(
136    entities: Res<Entities>,
137    mut animation_bank_sprites: CompMut<AnimationBankSprite>,
138    mut animated_sprites: CompMut<AnimatedSprite>,
139) {
140    for (ent, animation_bank) in entities.iter_with(&mut animation_bank_sprites) {
141        // If the animation has chagned
142        if animation_bank.current != animation_bank.last_animation {
143            // Update the last animation
144            animation_bank.last_animation = animation_bank.current;
145
146            // Get the selected animation from the bank
147            let animated_sprite = animation_bank
148                .get_current_animation()
149                .cloned()
150                .unwrap_or_else(|| {
151                    panic!("Animation `{}` does not exist.", animation_bank.current)
152                });
153
154            // Update the animated sprite with the selected animation
155            animated_sprites.insert(ent, animated_sprite);
156        }
157    }
158}