Skip to main content

macroquad_ply/experimental/
animation.rs

1//! Animation management
2//!
3//! To create custom animation and use it you will need an image that can be represented as tiles.
4//! Each tile contains specific frame of animation.
5//! Every specific animation should be placed in separate row.
6//!
7//! # Examples
8//! Let's say we have an image of our character where every frame is 15x20 rect and we have this animations:
9//! - Idle animation with 20 frames at 12 fps
10//! - Run animation with 15 frames at 15 fps
11//!
12//! ```no_run
13//! use macroquad::experimental::animation::*;
14//! use macroquad::prelude::*;
15//!
16//! #[macroquad::main("Animation")]
17//! async fn main() {
18//!     // Define animations
19//!     let mut sprite = AnimatedSprite::new(
20//!         15,
21//!         20,
22//!         &[
23//!             Animation {
24//!                 name: "idle".to_string(),
25//!                 row: 0,
26//!                 frames: 20,
27//!                 fps: 12,
28//!             },
29//!             Animation {
30//!                 name: "run".to_string(),
31//!                 row: 1,
32//!                 frames: 15,
33//!                 fps: 15,
34//!             },
35//!         ],
36//!         true,
37//!     );
38//!     let image = load_texture("some_path.png").await.unwrap();
39//!     loop {
40//!         clear_background(WHITE);
41//!         // Now we can draw our character
42//!         draw_texture_ex(
43//!             &image,
44//!             10.,
45//!             10.,
46//!             WHITE,
47//!             DrawTextureParams {
48//!                 source: Some(sprite.frame().source_rect),
49//!                 dest_size: Some(sprite.frame().dest_size),
50//!                 ..Default::default()
51//!             }
52//!         );
53//!         // Update frame
54//!         sprite.update();
55//!         next_frame().await;
56//!     }
57//! }
58
59use crate::{
60    math::{vec2, Rect, Vec2},
61    time::get_frame_time,
62};
63
64/// Specification of animation
65#[derive(Clone, Debug)]
66pub struct Animation {
67    pub name: String,
68    pub row: u32,
69    pub frames: u32,
70    pub fps: u32,
71}
72
73/// Specific animation frame
74pub struct AnimationFrame {
75    /// Area of current frame in source image
76    pub source_rect: Rect,
77    /// Size of frame
78    pub dest_size: Vec2,
79}
80
81/// Main definition of all animations for specific image
82#[derive(Clone)]
83pub struct AnimatedSprite {
84    tile_width: f32,
85    tile_height: f32,
86    animations: Vec<Animation>,
87
88    current_animation: usize,
89    time: f32,
90    frame: u32,
91    /// Controls if frame should be updated on [update][Self::update]
92    pub playing: bool,
93}
94
95impl AnimatedSprite {
96    pub fn new(
97        tile_width: u32,
98        tile_height: u32,
99        animations: &[Animation],
100        playing: bool,
101    ) -> AnimatedSprite {
102        AnimatedSprite {
103            tile_width: tile_width as f32,
104            tile_height: tile_height as f32,
105            animations: animations.to_vec(),
106            current_animation: 0,
107            time: 0.0,
108            frame: 0,
109            playing,
110        }
111    }
112
113    /// Choose animation to display
114    ///
115    /// **Note:** the animations is not reset when switching, for this use [set_frame][Self::set_frame]
116    pub fn set_animation(&mut self, animation: usize) {
117        self.current_animation = animation;
118
119        let animation = &self.animations[self.current_animation];
120        self.frame %= animation.frames;
121    }
122
123    /// Currently chosen animation
124    pub const fn current_animation(&self) -> usize {
125        self.current_animation
126    }
127
128    /// Set specific frame for animation
129    pub fn set_frame(&mut self, frame: u32) {
130        self.frame = frame;
131    }
132
133    /// Returns whether the last frame is being displayed
134    pub fn is_last_frame(&self) -> bool {
135        let animation = &self.animations[self.current_animation];
136        self.frame == animation.frames - 1
137    }
138
139    /// Update current frame
140    ///
141    /// Switches to the next frame every `1. / current_animation.fps` seconds
142    pub fn update(&mut self) {
143        let animation = &self.animations[self.current_animation];
144
145        if self.playing {
146            self.time += get_frame_time();
147            if self.time > 1. / animation.fps as f32 {
148                self.frame += 1;
149                self.time = 0.0;
150            }
151        }
152        self.frame %= animation.frames;
153    }
154
155    /// Get current frame
156    pub fn frame(&self) -> AnimationFrame {
157        let animation = &self.animations[self.current_animation];
158
159        AnimationFrame {
160            source_rect: Rect::new(
161                self.tile_width * self.frame as f32,
162                self.tile_height * animation.row as f32,
163                self.tile_width,
164                self.tile_height,
165            ),
166            dest_size: vec2(self.tile_width, self.tile_height),
167        }
168    }
169}