micro_games_kit/
animation.rs

1use spitfire_draw::utils::TextureRef;
2use std::{
3    collections::{HashMap, HashSet},
4    ops::Range,
5};
6use vek::Rect;
7
8#[derive(Debug, Clone, PartialEq)]
9pub struct FrameAnimation {
10    frames: Range<usize>,
11    current: Option<usize>,
12    accumulator: f32,
13    events: HashMap<usize, HashSet<String>>,
14    pub fps: f32,
15    pub is_playing: bool,
16    pub looping: bool,
17}
18
19impl FrameAnimation {
20    pub fn new(frames: Range<usize>) -> Self {
21        Self {
22            frames,
23            current: None,
24            accumulator: 0.0,
25            events: Default::default(),
26            fps: 30.0,
27            is_playing: false,
28            looping: false,
29        }
30    }
31
32    pub fn event(mut self, frame: usize, id: impl ToString) -> Self {
33        self.events.entry(frame).or_default().insert(id.to_string());
34        self
35    }
36
37    pub fn fps(mut self, value: f32) -> Self {
38        self.fps = value;
39        self
40    }
41
42    pub fn playing(mut self) -> Self {
43        self.play();
44        self
45    }
46
47    pub fn looping(mut self) -> Self {
48        self.looping = true;
49        self
50    }
51
52    pub fn play(&mut self) {
53        if self.frames.is_empty() {
54            return;
55        }
56        self.is_playing = true;
57        self.accumulator = 0.0;
58        self.current = Some(self.frames.start);
59    }
60
61    pub fn stop(&mut self) {
62        self.is_playing = false;
63        self.accumulator = 0.0;
64        self.current = None;
65    }
66
67    pub fn update(&mut self, delta_time: f32) -> HashSet<&str> {
68        if self.frames.is_empty() || !self.is_playing {
69            return Default::default();
70        }
71        let Some(mut current) = self.current else {
72            return Default::default();
73        };
74        let mut result = HashSet::default();
75        self.accumulator += (delta_time * self.fps).max(0.0);
76        while self.accumulator >= 1.0 {
77            self.accumulator -= 1.0;
78            if let Some(events) = self.events.get(&current) {
79                result.extend(events.iter().map(|id| id.as_str()));
80            }
81            current += 1;
82            if current >= self.frames.end {
83                if self.looping {
84                    current = self.frames.start;
85                } else {
86                    self.is_playing = false;
87                    self.accumulator = 0.0;
88                    self.current = None;
89                    return result;
90                }
91            }
92        }
93        self.current = Some(current);
94        result
95    }
96
97    pub fn current_frame(&self) -> Option<usize> {
98        self.current
99    }
100}
101
102#[derive(Debug, Clone)]
103pub struct NamedAnimation {
104    pub animation: FrameAnimation,
105    pub id: String,
106}
107
108#[derive(Debug, Clone)]
109pub struct SpriteAnimationFrame {
110    pub texture: TextureRef,
111    pub region: Rect<f32, f32>,
112    pub page: f32,
113}
114
115#[derive(Debug, Clone)]
116pub struct SpriteAnimation {
117    pub animation: FrameAnimation,
118    pub frames: HashMap<usize, SpriteAnimationFrame>,
119}
120
121impl SpriteAnimation {
122    pub fn current_frame(&self) -> Option<&SpriteAnimationFrame> {
123        self.frames.get(&self.animation.current_frame()?)
124    }
125}