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}