Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/sprite_animation.rs
6592 views
1
//! Animates a sprite in response to a keyboard event.
2
//!
3
//! See `sprite_sheet.rs` for an example where the sprite animation loops indefinitely.
4
5
use std::time::Duration;
6
7
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
8
9
fn main() {
10
App::new()
11
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites
12
.add_systems(Startup, setup)
13
.add_systems(Update, execute_animations)
14
.add_systems(
15
Update,
16
(
17
// Press the right arrow key to animate the right sprite
18
trigger_animation::<RightSprite>.run_if(input_just_pressed(KeyCode::ArrowRight)),
19
// Press the left arrow key to animate the left sprite
20
trigger_animation::<LeftSprite>.run_if(input_just_pressed(KeyCode::ArrowLeft)),
21
),
22
)
23
.run();
24
}
25
26
// This system runs when the user clicks the left arrow key or right arrow key
27
fn trigger_animation<S: Component>(mut animation: Single<&mut AnimationConfig, With<S>>) {
28
// We create a new timer when the animation is triggered
29
animation.frame_timer = AnimationConfig::timer_from_fps(animation.fps);
30
}
31
32
#[derive(Component)]
33
struct AnimationConfig {
34
first_sprite_index: usize,
35
last_sprite_index: usize,
36
fps: u8,
37
frame_timer: Timer,
38
}
39
40
impl AnimationConfig {
41
fn new(first: usize, last: usize, fps: u8) -> Self {
42
Self {
43
first_sprite_index: first,
44
last_sprite_index: last,
45
fps,
46
frame_timer: Self::timer_from_fps(fps),
47
}
48
}
49
50
fn timer_from_fps(fps: u8) -> Timer {
51
Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once)
52
}
53
}
54
55
// This system loops through all the sprites in the `TextureAtlas`, from `first_sprite_index` to
56
// `last_sprite_index` (both defined in `AnimationConfig`).
57
fn execute_animations(time: Res<Time>, mut query: Query<(&mut AnimationConfig, &mut Sprite)>) {
58
for (mut config, mut sprite) in &mut query {
59
// We track how long the current sprite has been displayed for
60
config.frame_timer.tick(time.delta());
61
62
// If it has been displayed for the user-defined amount of time (fps)...
63
if config.frame_timer.just_finished()
64
&& let Some(atlas) = &mut sprite.texture_atlas
65
{
66
if atlas.index == config.last_sprite_index {
67
// ...and it IS the last frame, then we move back to the first frame and stop.
68
atlas.index = config.first_sprite_index;
69
} else {
70
// ...and it is NOT the last frame, then we move to the next frame...
71
atlas.index += 1;
72
// ...and reset the frame timer to start counting all over again
73
config.frame_timer = AnimationConfig::timer_from_fps(config.fps);
74
}
75
}
76
}
77
}
78
79
#[derive(Component)]
80
struct LeftSprite;
81
82
#[derive(Component)]
83
struct RightSprite;
84
85
fn setup(
86
mut commands: Commands,
87
asset_server: Res<AssetServer>,
88
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
89
) {
90
commands.spawn(Camera2d);
91
92
// Create a minimal UI explaining how to interact with the example
93
commands.spawn((
94
Text::new("Left Arrow: Animate Left Sprite\nRight Arrow: Animate Right Sprite"),
95
Node {
96
position_type: PositionType::Absolute,
97
top: px(12),
98
left: px(12),
99
..default()
100
},
101
));
102
103
// Load the sprite sheet using the `AssetServer`
104
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
105
106
// The sprite sheet has 7 sprites arranged in a row, and they are all 24px x 24px
107
let layout = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None);
108
let texture_atlas_layout = texture_atlas_layouts.add(layout);
109
110
// The first (left-hand) sprite runs at 10 FPS
111
let animation_config_1 = AnimationConfig::new(1, 6, 10);
112
113
// Create the first (left-hand) sprite
114
commands.spawn((
115
Sprite {
116
image: texture.clone(),
117
texture_atlas: Some(TextureAtlas {
118
layout: texture_atlas_layout.clone(),
119
index: animation_config_1.first_sprite_index,
120
}),
121
..default()
122
},
123
Transform::from_scale(Vec3::splat(6.0)).with_translation(Vec3::new(-70.0, 0.0, 0.0)),
124
LeftSprite,
125
animation_config_1,
126
));
127
128
// The second (right-hand) sprite runs at 20 FPS
129
let animation_config_2 = AnimationConfig::new(1, 6, 20);
130
131
// Create the second (right-hand) sprite
132
commands.spawn((
133
Sprite {
134
image: texture.clone(),
135
texture_atlas: Some(TextureAtlas {
136
layout: texture_atlas_layout.clone(),
137
index: animation_config_2.first_sprite_index,
138
}),
139
..Default::default()
140
},
141
Transform::from_scale(Vec3::splat(6.0)).with_translation(Vec3::new(70.0, 0.0, 0.0)),
142
RightSprite,
143
animation_config_2,
144
));
145
}
146
147