-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimation.rs
178 lines (160 loc) · 5.3 KB
/
animation.rs
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Player sprite animation.
//! This is based on multiple examples and may be very different for your game.
//! - [Sprite flipping](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_flipping.rs)
//! - [Sprite animation](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
//! - [Timers](https://github.com/bevyengine/bevy/blob/latest/examples/time/timers.rs)
use bevy::prelude::*;
use rand::prelude::*;
use std::time::Duration;
use crate::{
audio::SoundEffect,
demo::{movement::MovementController, player::PlayerAssets},
AppSet,
};
pub(super) fn plugin(app: &mut App) {
// Animate and play sound effects based on controls.
app.register_type::<PlayerAnimation>();
app.add_systems(
Update,
(
update_animation_timer.in_set(AppSet::TickTimers),
(
update_animation_movement,
update_animation_atlas,
trigger_step_sound_effect,
)
.chain()
.run_if(resource_exists::<PlayerAssets>)
.in_set(AppSet::Update),
),
);
}
/// Update the sprite direction and animation state (idling/walking).
fn update_animation_movement(
mut player_query: Query<(&MovementController, &mut Sprite, &mut PlayerAnimation)>,
) {
for (controller, mut sprite, mut animation) in &mut player_query {
let dx = controller.intent.x;
if dx != 0.0 {
sprite.flip_x = dx < 0.0;
}
let animation_state = if controller.intent == Vec2::ZERO {
PlayerAnimationState::Idling
} else {
PlayerAnimationState::Walking
};
animation.update_state(animation_state);
}
}
/// Update the animation timer.
fn update_animation_timer(time: Res<Time>, mut query: Query<&mut PlayerAnimation>) {
for mut animation in &mut query {
animation.update_timer(time.delta());
}
}
/// Update the texture atlas to reflect changes in the animation.
fn update_animation_atlas(mut query: Query<(&PlayerAnimation, &mut Sprite)>) {
for (animation, mut sprite) in &mut query {
let Some(atlas) = sprite.texture_atlas.as_mut() else {
continue;
};
if animation.changed() {
atlas.index = animation.get_atlas_index();
}
}
}
/// If the player is moving, play a step sound effect synchronized with the
/// animation.
fn trigger_step_sound_effect(
mut commands: Commands,
player_assets: Res<PlayerAssets>,
mut step_query: Query<&PlayerAnimation>,
) {
for animation in &mut step_query {
if animation.state == PlayerAnimationState::Walking
&& animation.changed()
&& (animation.frame == 2 || animation.frame == 5)
{
let rng = &mut rand::thread_rng();
let random_step = player_assets.steps.choose(rng).unwrap();
commands.spawn((
AudioPlayer(random_step.clone()),
PlaybackSettings::DESPAWN,
SoundEffect,
));
}
}
}
/// Component that tracks player's animation state.
/// It is tightly bound to the texture atlas we use.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct PlayerAnimation {
timer: Timer,
frame: usize,
state: PlayerAnimationState,
}
#[derive(Reflect, PartialEq)]
pub enum PlayerAnimationState {
Idling,
Walking,
}
impl PlayerAnimation {
/// The number of idle frames.
const IDLE_FRAMES: usize = 2;
/// The duration of each idle frame.
const IDLE_INTERVAL: Duration = Duration::from_millis(500);
/// The number of walking frames.
const WALKING_FRAMES: usize = 6;
/// The duration of each walking frame.
const WALKING_INTERVAL: Duration = Duration::from_millis(50);
fn idling() -> Self {
Self {
timer: Timer::new(Self::IDLE_INTERVAL, TimerMode::Repeating),
frame: 0,
state: PlayerAnimationState::Idling,
}
}
fn walking() -> Self {
Self {
timer: Timer::new(Self::WALKING_INTERVAL, TimerMode::Repeating),
frame: 0,
state: PlayerAnimationState::Walking,
}
}
pub fn new() -> Self {
Self::idling()
}
/// Update animation timers.
pub fn update_timer(&mut self, delta: Duration) {
self.timer.tick(delta);
if !self.timer.finished() {
return;
}
self.frame = (self.frame + 1)
% match self.state {
PlayerAnimationState::Idling => Self::IDLE_FRAMES,
PlayerAnimationState::Walking => Self::WALKING_FRAMES,
};
}
/// Update animation state if it changes.
pub fn update_state(&mut self, state: PlayerAnimationState) {
if self.state != state {
match state {
PlayerAnimationState::Idling => *self = Self::idling(),
PlayerAnimationState::Walking => *self = Self::walking(),
}
}
}
/// Whether animation changed this tick.
pub fn changed(&self) -> bool {
self.timer.finished()
}
/// Return sprite index in the atlas.
pub fn get_atlas_index(&self) -> usize {
match self.state {
PlayerAnimationState::Idling => self.frame,
PlayerAnimationState::Walking => 6 + self.frame,
}
}
}