Skip to content

Commit db5f80b

Browse files
DevinLeamyShaturmockersf
authored
API updates to the AnimationPlayer (#9002)
# Objective Added `AnimationPlayer` API UX improvements. - Succestor to #5912 - Fixes #5848 _(Credits to @asafigan for filing #5848, creating the initial pull request, and the discussion in #5912)_ ## Solution - Created `RepeatAnimation` enum to describe an animation repetition behavior. - Added `is_finished()`, `set_repeat()`, and `is_playback_reversed()` methods to the animation player. - ~~Made the animation clip optional as per the comment from #5912~~ > ~~My problem is that the default handle [used the initialize a `PlayingAnimation`] could actually refer to an actual animation if an AnimationClip is set for the default handle, which leads me to ask, "Should animation_clip should be an Option?"~~ - Added an accessor for the animation clip `animation_clip()` to the animation player. To determine if an animation is finished, we use the number of times the animation has completed and the repetition behavior. If the animation is playing in reverse then `elapsed < 0.0` counts as a completion. Otherwise, `elapsed > animation.duration` counts as a completion. This is what I would expect, personally. If there's any ambiguity, perhaps we could add some `AnimationCompletionBehavior`, to specify that kind of completion behavior to use. Update: Previously `PlayingAnimation::elapsed` was being used as the seek time into the animation clip. This was misleading because if you increased the speed of the animation it would also increase (or decrease) the elapsed time. In other words, the elapsed time was not actually the elapsed time. To solve this, we introduce `PlayingAnimation::seek_time` to serve as the value we manipulate the move between keyframes. Consequently, `elapsed()` now returns the actual elapsed time, and is not effected by the animation speed. Because `set_elapsed` was being used to manipulate the displayed keyframe, we introduce `AnimationPlayer::seek_to` and `AnimationPlayer::replay` to provide this functionality. ## Migration Guide - Removed `set_elapsed`. - Removed `stop_repeating` in favour of `AnimationPlayer::set_repeat(RepeatAnimation::Never)`. - Introduced `seek_to` to seek to a given timestamp inside of the animation. - Introduced `seek_time` accessor for the `PlayingAnimation::seek_to`. - Introduced `AnimationPlayer::replay` to reset the `PlayingAnimation` to a state where no time has elapsed. --------- Co-authored-by: Hennadii Chernyshchyk <[email protected]> Co-authored-by: François <[email protected]>
1 parent 3cf94e7 commit db5f80b

File tree

4 files changed

+162
-40
lines changed

4 files changed

+162
-40
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 131 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,6 @@ impl AnimationClip {
102102
self.duration
103103
}
104104

105-
/// The bone ids mapped by their [`EntityPath`].
106-
#[inline]
107-
pub fn paths(&self) -> &HashMap<EntityPath, usize> {
108-
&self.paths
109-
}
110-
111105
/// Add a [`VariableCurve`] to an [`EntityPath`].
112106
pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) {
113107
// Update the duration of the animation by this curve duration if it's longer
@@ -129,25 +123,94 @@ impl AnimationClip {
129123
}
130124
}
131125

126+
/// Repetition behavior of an animation.
127+
#[derive(Reflect, Copy, Clone, Default)]
128+
pub enum RepeatAnimation {
129+
/// The animation will finish after running once.
130+
#[default]
131+
Never,
132+
/// The animation will finish after running "n" times.
133+
Count(u32),
134+
/// The animation will never finish.
135+
Forever,
136+
}
137+
132138
#[derive(Reflect)]
133139
struct PlayingAnimation {
134-
repeat: bool,
140+
repeat: RepeatAnimation,
135141
speed: f32,
142+
/// Total time the animation has been played.
143+
///
144+
/// Note: Time does not increase when the animation is paused or after it has completed.
136145
elapsed: f32,
146+
/// The timestamp inside of the animation clip.
147+
///
148+
/// Note: This will always be in the range [0.0, animation clip duration]
149+
seek_time: f32,
137150
animation_clip: Handle<AnimationClip>,
138151
path_cache: Vec<Vec<Option<Entity>>>,
152+
/// Number of times the animation has completed.
153+
/// If the animation is playing in reverse, this increments when the animation passes the start.
154+
completions: u32,
139155
}
140156

141157
impl Default for PlayingAnimation {
142158
fn default() -> Self {
143159
Self {
144-
repeat: false,
160+
repeat: RepeatAnimation::default(),
145161
speed: 1.0,
146162
elapsed: 0.0,
163+
seek_time: 0.0,
147164
animation_clip: Default::default(),
148165
path_cache: Vec::new(),
166+
completions: 0,
167+
}
168+
}
169+
}
170+
171+
impl PlayingAnimation {
172+
/// Check if the animation has finished, based on its repetition behavior and the number of times it has repeated.
173+
///
174+
/// Note: An animation with `RepeatAnimation::Forever` will never finish.
175+
#[inline]
176+
pub fn is_finished(&self) -> bool {
177+
match self.repeat {
178+
RepeatAnimation::Forever => false,
179+
RepeatAnimation::Never => self.completions >= 1,
180+
RepeatAnimation::Count(n) => self.completions >= n,
181+
}
182+
}
183+
184+
/// Update the animation given the delta time and the duration of the clip being played.
185+
#[inline]
186+
fn update(&mut self, delta: f32, clip_duration: f32) {
187+
if self.is_finished() {
188+
return;
189+
}
190+
191+
self.elapsed += delta;
192+
self.seek_time += delta * self.speed;
193+
194+
if (self.seek_time > clip_duration && self.speed > 0.0)
195+
|| (self.seek_time < 0.0 && self.speed < 0.0)
196+
{
197+
self.completions += 1;
198+
}
199+
200+
if self.seek_time >= clip_duration {
201+
self.seek_time %= clip_duration;
202+
}
203+
if self.seek_time < 0.0 {
204+
self.seek_time += clip_duration;
149205
}
150206
}
207+
208+
/// Reset back to the initial state as if no time has elapsed.
209+
fn replay(&mut self) {
210+
self.completions = 0;
211+
self.elapsed = 0.0;
212+
self.seek_time = 0.0;
213+
}
151214
}
152215

153216
/// An animation that is being faded out as part of a transition
@@ -222,7 +285,7 @@ impl AnimationPlayer {
222285
/// If `transition_duration` is set, this will use a linear blending
223286
/// between the previous and the new animation to make a smooth transition
224287
pub fn play(&mut self, handle: Handle<AnimationClip>) -> &mut Self {
225-
if self.animation.animation_clip != handle || self.is_paused() {
288+
if !self.is_playing_clip(&handle) || self.is_paused() {
226289
self.start(handle);
227290
}
228291
self
@@ -235,24 +298,56 @@ impl AnimationPlayer {
235298
handle: Handle<AnimationClip>,
236299
transition_duration: Duration,
237300
) -> &mut Self {
238-
if self.animation.animation_clip != handle || self.is_paused() {
301+
if !self.is_playing_clip(&handle) || self.is_paused() {
239302
self.start_with_transition(handle, transition_duration);
240303
}
241304
self
242305
}
243306

244-
/// Set the animation to repeat
307+
/// Handle to the animation clip being played.
308+
pub fn animation_clip(&self) -> &Handle<AnimationClip> {
309+
&self.animation.animation_clip
310+
}
311+
312+
/// Check if the given animation clip is being played.
313+
pub fn is_playing_clip(&self, handle: &Handle<AnimationClip>) -> bool {
314+
self.animation_clip() == handle
315+
}
316+
317+
/// Check if the playing animation has finished, according to the repetition behavior.
318+
pub fn is_finished(&self) -> bool {
319+
self.animation.is_finished()
320+
}
321+
322+
/// Sets repeat to [`RepeatAnimation::Forever`].
323+
///
324+
/// See also [`Self::set_repeat`].
245325
pub fn repeat(&mut self) -> &mut Self {
246-
self.animation.repeat = true;
326+
self.animation.repeat = RepeatAnimation::Forever;
247327
self
248328
}
249329

250-
/// Stop the animation from repeating
251-
pub fn stop_repeating(&mut self) -> &mut Self {
252-
self.animation.repeat = false;
330+
/// Set the repetition behaviour of the animation.
331+
pub fn set_repeat(&mut self, repeat: RepeatAnimation) -> &mut Self {
332+
self.animation.repeat = repeat;
253333
self
254334
}
255335

336+
/// Repetition behavior of the animation.
337+
pub fn repeat_mode(&self) -> RepeatAnimation {
338+
self.animation.repeat
339+
}
340+
341+
/// Number of times the animation has completed.
342+
pub fn completions(&self) -> u32 {
343+
self.animation.completions
344+
}
345+
346+
/// Check if the animation is playing in reverse.
347+
pub fn is_playback_reversed(&self) -> bool {
348+
self.animation.speed < 0.0
349+
}
350+
256351
/// Pause the animation
257352
pub fn pause(&mut self) {
258353
self.paused = true;
@@ -284,11 +379,21 @@ impl AnimationPlayer {
284379
self.animation.elapsed
285380
}
286381

287-
/// Seek to a specific time in the animation
288-
pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self {
289-
self.animation.elapsed = elapsed;
382+
/// Seek time inside of the animation. Always within the range [0.0, clip duration].
383+
pub fn seek_time(&self) -> f32 {
384+
self.animation.seek_time
385+
}
386+
387+
/// Seek to a specific time in the animation.
388+
pub fn seek_to(&mut self, seek_time: f32) -> &mut Self {
389+
self.animation.seek_time = seek_time;
290390
self
291391
}
392+
393+
/// Reset the animation to its initial state, as if no time has elapsed.
394+
pub fn replay(&mut self) {
395+
self.animation.replay();
396+
}
292397
}
293398

294399
fn entity_from_path(
@@ -489,16 +594,12 @@ fn apply_animation(
489594
children: &Query<&Children>,
490595
) {
491596
if let Some(animation_clip) = animations.get(&animation.animation_clip) {
492-
if !paused {
493-
animation.elapsed += time.delta_seconds() * animation.speed;
494-
}
495-
let mut elapsed = animation.elapsed;
496-
if animation.repeat {
497-
elapsed %= animation_clip.duration;
498-
}
499-
if elapsed < 0.0 {
500-
elapsed += animation_clip.duration;
501-
}
597+
// We don't return early because seek_to() may have been called on the animation player.
598+
animation.update(
599+
if paused { 0.0 } else { time.delta_seconds() },
600+
animation_clip.duration,
601+
);
602+
502603
if animation.path_cache.len() != animation_clip.paths.len() {
503604
animation.path_cache = vec![Vec::new(); animation_clip.paths.len()];
504605
}
@@ -556,7 +657,7 @@ fn apply_animation(
556657
// PERF: finding the current keyframe can be optimised
557658
let step_start = match curve
558659
.keyframe_timestamps
559-
.binary_search_by(|probe| probe.partial_cmp(&elapsed).unwrap())
660+
.binary_search_by(|probe| probe.partial_cmp(&animation.seek_time).unwrap())
560661
{
561662
Ok(n) if n >= curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished
562663
Ok(i) => i,
@@ -566,7 +667,7 @@ fn apply_animation(
566667
};
567668
let ts_start = curve.keyframe_timestamps[step_start];
568669
let ts_end = curve.keyframe_timestamps[step_start + 1];
569-
let lerp = (elapsed - ts_start) / (ts_end - ts_start);
670+
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
570671

571672
// Apply the keyframe
572673
match &curve.keyframes {
@@ -620,7 +721,6 @@ impl Plugin for AnimationPlugin {
620721
app.add_asset::<AnimationClip>()
621722
.register_asset_reflect::<AnimationClip>()
622723
.register_type::<AnimationPlayer>()
623-
.register_type::<PlayingAnimation>()
624724
.add_systems(
625725
PostUpdate,
626726
animation_player.before(TransformSystem::TransformPropagate),

examples/animation/animated_fox.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::time::Duration;
55

66
use bevy::pbr::CascadeShadowConfigBuilder;
77
use bevy::prelude::*;
8+
use bevy_internal::animation::RepeatAnimation;
89

910
fn main() {
1011
App::new()
@@ -77,6 +78,8 @@ fn setup(
7778
println!(" - spacebar: play / pause");
7879
println!(" - arrow up / down: speed up / slow down animation playback");
7980
println!(" - arrow left / right: seek backward / forward");
81+
println!(" - digit 1 / 3 / 5: play the animation <digit> times");
82+
println!(" - L: loop the animation forever");
8083
println!(" - return: change animation");
8184
}
8285

@@ -116,13 +119,13 @@ fn keyboard_animation_control(
116119
}
117120

118121
if keyboard_input.just_pressed(KeyCode::Left) {
119-
let elapsed = player.elapsed();
120-
player.set_elapsed(elapsed - 0.1);
122+
let elapsed = player.seek_time();
123+
player.seek_to(elapsed - 0.1);
121124
}
122125

123126
if keyboard_input.just_pressed(KeyCode::Right) {
124-
let elapsed = player.elapsed();
125-
player.set_elapsed(elapsed + 0.1);
127+
let elapsed = player.seek_time();
128+
player.seek_to(elapsed + 0.1);
126129
}
127130

128131
if keyboard_input.just_pressed(KeyCode::Return) {
@@ -134,5 +137,24 @@ fn keyboard_animation_control(
134137
)
135138
.repeat();
136139
}
140+
141+
if keyboard_input.just_pressed(KeyCode::Key1) {
142+
player.set_repeat(RepeatAnimation::Count(1));
143+
player.replay();
144+
}
145+
146+
if keyboard_input.just_pressed(KeyCode::Key3) {
147+
player.set_repeat(RepeatAnimation::Count(3));
148+
player.replay();
149+
}
150+
151+
if keyboard_input.just_pressed(KeyCode::Key5) {
152+
player.set_repeat(RepeatAnimation::Count(5));
153+
player.replay();
154+
}
155+
156+
if keyboard_input.just_pressed(KeyCode::L) {
157+
player.set_repeat(RepeatAnimation::Forever);
158+
}
137159
}
138160
}

examples/stress_tests/many_foxes.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,13 @@ fn keyboard_animation_control(
270270
}
271271

272272
if keyboard_input.just_pressed(KeyCode::Left) {
273-
let elapsed = player.elapsed();
274-
player.set_elapsed(elapsed - 0.1);
273+
let elapsed = player.seek_time();
274+
player.seek_to(elapsed - 0.1);
275275
}
276276

277277
if keyboard_input.just_pressed(KeyCode::Right) {
278-
let elapsed = player.elapsed();
279-
player.set_elapsed(elapsed + 0.1);
278+
let elapsed = player.seek_time();
279+
player.seek_to(elapsed + 0.1);
280280
}
281281

282282
if keyboard_input.just_pressed(KeyCode::Return) {

examples/tools/scene_viewer/animation_plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ fn handle_inputs(
9191

9292
let resume = !player.is_paused();
9393
// set the current animation to its start and pause it to reset to its starting state
94-
player.set_elapsed(0.0).pause();
94+
player.seek_to(0.0).pause();
9595

9696
clips.advance_to_next();
9797
let current_clip = clips.current();

0 commit comments

Comments
 (0)