1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Animation utilities and systems.

use crate::prelude::*;

/// Install animation utilities into the given [`SystemStages`].
pub fn animation_plugin(core: &mut Session) {
    core.stages
        .add_system_to_stage(CoreStage::Last, update_animation_banks)
        .add_system_to_stage(CoreStage::Last, animate_sprites);
}

/// Component that may be added to entities with an [`AtlasSprite`] to animate them.
#[derive(Clone, HasSchema, Debug)]
#[repr(C)]
pub struct AnimatedSprite {
    /// The current frame in the animation.
    pub index: u32,
    /// The frames in the animation.
    ///
    /// These are the indexes into the atlas, specified in the order they will be played.
    // TODO: Put Animation Frames in an `Arc` to Avoid Snapshot Clone Cost.
    pub frames: SVec<u32>,
    /// The frames per second to play the animation at.
    pub fps: f32,
    /// The amount of time the current frame has been playing
    pub timer: f32,
    /// Whether or not to repeat the animation
    pub repeat: bool,
}

/// Component that may be added to an [`AtlasSprite`] to control which animation, out of a set of
/// animations, is playing.
///
/// This is great for players or other sprites that will change through different, named animations
/// at different times.
#[derive(Clone, HasSchema, Debug, Default)]
pub struct AnimationBankSprite {
    /// The current animation.
    pub current: Ustr,
    /// The collection of animations in this animation bank.
    // TODO: Put Animation Frames in an `Arc` to Avoid Snapshot Clone Cost.
    pub animations: SMap<Ustr, AnimatedSprite>,
    /// The last animation that was playing.
    pub last_animation: Ustr,
}

impl AnimationBankSprite {
    /// Set the current animation if it exists inside of `animations` in the bank. Else does nothing.
    pub fn set_current(&mut self, animation_name: impl Into<Ustr>) {
        let animation_name = animation_name.into();
        if self.animations.contains_key(&animation_name) {
            self.current = animation_name;
        }
    }

    /// Insert a new animation into the bank
    pub fn insert_animation(&mut self, name: impl Into<Ustr>, animation: AnimatedSprite) {
        let name = name.into();
        self.animations.insert(name, animation);
    }

    /// Remove an animation from the bank
    pub fn remove_animation(&mut self, name: &Ustr) -> Option<AnimatedSprite> {
        self.animations.remove(name)
    }

    /// Get a reference to an animation in the bank
    pub fn get_animation(&self, name: &Ustr) -> Option<&AnimatedSprite> {
        self.animations.get(name)
    }

    /// Get a mutable reference to an animation in the bank
    pub fn get_animation_mut(&mut self, name: &Ustr) -> Option<&mut AnimatedSprite> {
        self.animations.get_mut(name)
    }

    /// Get the current animation
    pub fn get_current_animation(&self) -> Option<&AnimatedSprite> {
        self.animations.get(&self.current)
    }

    /// Get a mutable reference to the current animation
    pub fn get_current_animation_mut(&mut self) -> Option<&mut AnimatedSprite> {
        self.animations.get_mut(&self.current)
    }
}

impl Default for AnimatedSprite {
    fn default() -> Self {
        Self {
            index: 0,
            frames: default(),
            fps: 0.0,
            timer: 0.0,
            repeat: true,
        }
    }
}

/// System for automatically animating sprites with the [`AnimatedSprite`] component.
pub fn animate_sprites(
    time: Res<Time>,
    entities: Res<Entities>,
    mut atlas_sprites: CompMut<AtlasSprite>,
    mut animated_sprites: CompMut<AnimatedSprite>,
) {
    for (_ent, (atlas_sprite, animated_sprite)) in
        entities.iter_with((&mut atlas_sprites, &mut animated_sprites))
    {
        if animated_sprite.frames.is_empty() {
            continue;
        }

        animated_sprite.timer += time.delta_seconds();

        // If we are ready to go to the next frame
        if (animated_sprite.index != animated_sprite.frames.len() as u32 - 1
            || animated_sprite.repeat)
            && animated_sprite.timer > 1.0 / animated_sprite.fps.max(f32::MIN_POSITIVE)
        {
            // Restart the timer
            animated_sprite.timer = 0.0;

            // Increment and loop around the current index
            animated_sprite.index =
                (animated_sprite.index + 1) % animated_sprite.frames.len() as u32;
        }

        // Set the atlas sprite to match the current frame of the animated sprite
        atlas_sprite.index = animated_sprite.frames[animated_sprite.index as usize];
    }
}

/// System for updating [`AnimatedSprite`]s based on thier [`AnimationBankSprite`]
pub fn update_animation_banks(
    entities: Res<Entities>,
    mut animation_bank_sprites: CompMut<AnimationBankSprite>,
    mut animated_sprites: CompMut<AnimatedSprite>,
) {
    for (ent, animation_bank) in entities.iter_with(&mut animation_bank_sprites) {
        // If the animation has chagned
        if animation_bank.current != animation_bank.last_animation {
            // Update the last animation
            animation_bank.last_animation = animation_bank.current;

            // Get the selected animation from the bank
            let animated_sprite = animation_bank
                .get_current_animation()
                .cloned()
                .unwrap_or_else(|| {
                    panic!("Animation `{}` does not exist.", animation_bank.current)
                });

            // Update the animated sprite with the selected animation
            animated_sprites.insert(ent, animated_sprite);
        }
    }
}