mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
API updates to the AnimationPlayer (#9002)
# Objective Added `AnimationPlayer` API UX improvements. - Succestor to https://github.com/bevyengine/bevy/pull/5912 - Fixes https://github.com/bevyengine/bevy/issues/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 <genaloner@gmail.com> Co-authored-by: François <mockersf@gmail.com>
This commit is contained in:
parent
3cf94e7c9d
commit
db5f80b2be
4 changed files with 162 additions and 40 deletions
|
@ -102,12 +102,6 @@ impl AnimationClip {
|
|||
self.duration
|
||||
}
|
||||
|
||||
/// The bone ids mapped by their [`EntityPath`].
|
||||
#[inline]
|
||||
pub fn paths(&self) -> &HashMap<EntityPath, usize> {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
/// Add a [`VariableCurve`] to an [`EntityPath`].
|
||||
pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) {
|
||||
// Update the duration of the animation by this curve duration if it's longer
|
||||
|
@ -129,27 +123,96 @@ impl AnimationClip {
|
|||
}
|
||||
}
|
||||
|
||||
/// Repetition behavior of an animation.
|
||||
#[derive(Reflect, Copy, Clone, Default)]
|
||||
pub enum RepeatAnimation {
|
||||
/// The animation will finish after running once.
|
||||
#[default]
|
||||
Never,
|
||||
/// The animation will finish after running "n" times.
|
||||
Count(u32),
|
||||
/// The animation will never finish.
|
||||
Forever,
|
||||
}
|
||||
|
||||
#[derive(Reflect)]
|
||||
struct PlayingAnimation {
|
||||
repeat: bool,
|
||||
repeat: RepeatAnimation,
|
||||
speed: f32,
|
||||
/// Total time the animation has been played.
|
||||
///
|
||||
/// Note: Time does not increase when the animation is paused or after it has completed.
|
||||
elapsed: f32,
|
||||
/// The timestamp inside of the animation clip.
|
||||
///
|
||||
/// Note: This will always be in the range [0.0, animation clip duration]
|
||||
seek_time: f32,
|
||||
animation_clip: Handle<AnimationClip>,
|
||||
path_cache: Vec<Vec<Option<Entity>>>,
|
||||
/// Number of times the animation has completed.
|
||||
/// If the animation is playing in reverse, this increments when the animation passes the start.
|
||||
completions: u32,
|
||||
}
|
||||
|
||||
impl Default for PlayingAnimation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
repeat: false,
|
||||
repeat: RepeatAnimation::default(),
|
||||
speed: 1.0,
|
||||
elapsed: 0.0,
|
||||
seek_time: 0.0,
|
||||
animation_clip: Default::default(),
|
||||
path_cache: Vec::new(),
|
||||
completions: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayingAnimation {
|
||||
/// Check if the animation has finished, based on its repetition behavior and the number of times it has repeated.
|
||||
///
|
||||
/// Note: An animation with `RepeatAnimation::Forever` will never finish.
|
||||
#[inline]
|
||||
pub fn is_finished(&self) -> bool {
|
||||
match self.repeat {
|
||||
RepeatAnimation::Forever => false,
|
||||
RepeatAnimation::Never => self.completions >= 1,
|
||||
RepeatAnimation::Count(n) => self.completions >= n,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the animation given the delta time and the duration of the clip being played.
|
||||
#[inline]
|
||||
fn update(&mut self, delta: f32, clip_duration: f32) {
|
||||
if self.is_finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.elapsed += delta;
|
||||
self.seek_time += delta * self.speed;
|
||||
|
||||
if (self.seek_time > clip_duration && self.speed > 0.0)
|
||||
|| (self.seek_time < 0.0 && self.speed < 0.0)
|
||||
{
|
||||
self.completions += 1;
|
||||
}
|
||||
|
||||
if self.seek_time >= clip_duration {
|
||||
self.seek_time %= clip_duration;
|
||||
}
|
||||
if self.seek_time < 0.0 {
|
||||
self.seek_time += clip_duration;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset back to the initial state as if no time has elapsed.
|
||||
fn replay(&mut self) {
|
||||
self.completions = 0;
|
||||
self.elapsed = 0.0;
|
||||
self.seek_time = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// An animation that is being faded out as part of a transition
|
||||
struct AnimationTransition {
|
||||
/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
|
||||
|
@ -222,7 +285,7 @@ impl AnimationPlayer {
|
|||
/// If `transition_duration` is set, this will use a linear blending
|
||||
/// between the previous and the new animation to make a smooth transition
|
||||
pub fn play(&mut self, handle: Handle<AnimationClip>) -> &mut Self {
|
||||
if self.animation.animation_clip != handle || self.is_paused() {
|
||||
if !self.is_playing_clip(&handle) || self.is_paused() {
|
||||
self.start(handle);
|
||||
}
|
||||
self
|
||||
|
@ -235,24 +298,56 @@ impl AnimationPlayer {
|
|||
handle: Handle<AnimationClip>,
|
||||
transition_duration: Duration,
|
||||
) -> &mut Self {
|
||||
if self.animation.animation_clip != handle || self.is_paused() {
|
||||
if !self.is_playing_clip(&handle) || self.is_paused() {
|
||||
self.start_with_transition(handle, transition_duration);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the animation to repeat
|
||||
/// Handle to the animation clip being played.
|
||||
pub fn animation_clip(&self) -> &Handle<AnimationClip> {
|
||||
&self.animation.animation_clip
|
||||
}
|
||||
|
||||
/// Check if the given animation clip is being played.
|
||||
pub fn is_playing_clip(&self, handle: &Handle<AnimationClip>) -> bool {
|
||||
self.animation_clip() == handle
|
||||
}
|
||||
|
||||
/// Check if the playing animation has finished, according to the repetition behavior.
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.animation.is_finished()
|
||||
}
|
||||
|
||||
/// Sets repeat to [`RepeatAnimation::Forever`].
|
||||
///
|
||||
/// See also [`Self::set_repeat`].
|
||||
pub fn repeat(&mut self) -> &mut Self {
|
||||
self.animation.repeat = true;
|
||||
self.animation.repeat = RepeatAnimation::Forever;
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop the animation from repeating
|
||||
pub fn stop_repeating(&mut self) -> &mut Self {
|
||||
self.animation.repeat = false;
|
||||
/// Set the repetition behaviour of the animation.
|
||||
pub fn set_repeat(&mut self, repeat: RepeatAnimation) -> &mut Self {
|
||||
self.animation.repeat = repeat;
|
||||
self
|
||||
}
|
||||
|
||||
/// Repetition behavior of the animation.
|
||||
pub fn repeat_mode(&self) -> RepeatAnimation {
|
||||
self.animation.repeat
|
||||
}
|
||||
|
||||
/// Number of times the animation has completed.
|
||||
pub fn completions(&self) -> u32 {
|
||||
self.animation.completions
|
||||
}
|
||||
|
||||
/// Check if the animation is playing in reverse.
|
||||
pub fn is_playback_reversed(&self) -> bool {
|
||||
self.animation.speed < 0.0
|
||||
}
|
||||
|
||||
/// Pause the animation
|
||||
pub fn pause(&mut self) {
|
||||
self.paused = true;
|
||||
|
@ -284,11 +379,21 @@ impl AnimationPlayer {
|
|||
self.animation.elapsed
|
||||
}
|
||||
|
||||
/// Seek to a specific time in the animation
|
||||
pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self {
|
||||
self.animation.elapsed = elapsed;
|
||||
/// Seek time inside of the animation. Always within the range [0.0, clip duration].
|
||||
pub fn seek_time(&self) -> f32 {
|
||||
self.animation.seek_time
|
||||
}
|
||||
|
||||
/// Seek to a specific time in the animation.
|
||||
pub fn seek_to(&mut self, seek_time: f32) -> &mut Self {
|
||||
self.animation.seek_time = seek_time;
|
||||
self
|
||||
}
|
||||
|
||||
/// Reset the animation to its initial state, as if no time has elapsed.
|
||||
pub fn replay(&mut self) {
|
||||
self.animation.replay();
|
||||
}
|
||||
}
|
||||
|
||||
fn entity_from_path(
|
||||
|
@ -489,16 +594,12 @@ fn apply_animation(
|
|||
children: &Query<&Children>,
|
||||
) {
|
||||
if let Some(animation_clip) = animations.get(&animation.animation_clip) {
|
||||
if !paused {
|
||||
animation.elapsed += time.delta_seconds() * animation.speed;
|
||||
}
|
||||
let mut elapsed = animation.elapsed;
|
||||
if animation.repeat {
|
||||
elapsed %= animation_clip.duration;
|
||||
}
|
||||
if elapsed < 0.0 {
|
||||
elapsed += animation_clip.duration;
|
||||
}
|
||||
// We don't return early because seek_to() may have been called on the animation player.
|
||||
animation.update(
|
||||
if paused { 0.0 } else { time.delta_seconds() },
|
||||
animation_clip.duration,
|
||||
);
|
||||
|
||||
if animation.path_cache.len() != animation_clip.paths.len() {
|
||||
animation.path_cache = vec![Vec::new(); animation_clip.paths.len()];
|
||||
}
|
||||
|
@ -556,7 +657,7 @@ fn apply_animation(
|
|||
// PERF: finding the current keyframe can be optimised
|
||||
let step_start = match curve
|
||||
.keyframe_timestamps
|
||||
.binary_search_by(|probe| probe.partial_cmp(&elapsed).unwrap())
|
||||
.binary_search_by(|probe| probe.partial_cmp(&animation.seek_time).unwrap())
|
||||
{
|
||||
Ok(n) if n >= curve.keyframe_timestamps.len() - 1 => continue, // this curve is finished
|
||||
Ok(i) => i,
|
||||
|
@ -566,7 +667,7 @@ fn apply_animation(
|
|||
};
|
||||
let ts_start = curve.keyframe_timestamps[step_start];
|
||||
let ts_end = curve.keyframe_timestamps[step_start + 1];
|
||||
let lerp = (elapsed - ts_start) / (ts_end - ts_start);
|
||||
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
|
||||
|
||||
// Apply the keyframe
|
||||
match &curve.keyframes {
|
||||
|
@ -620,7 +721,6 @@ impl Plugin for AnimationPlugin {
|
|||
app.add_asset::<AnimationClip>()
|
||||
.register_asset_reflect::<AnimationClip>()
|
||||
.register_type::<AnimationPlayer>()
|
||||
.register_type::<PlayingAnimation>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
animation_player.before(TransformSystem::TransformPropagate),
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::time::Duration;
|
|||
|
||||
use bevy::pbr::CascadeShadowConfigBuilder;
|
||||
use bevy::prelude::*;
|
||||
use bevy_internal::animation::RepeatAnimation;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
|
@ -77,6 +78,8 @@ fn setup(
|
|||
println!(" - spacebar: play / pause");
|
||||
println!(" - arrow up / down: speed up / slow down animation playback");
|
||||
println!(" - arrow left / right: seek backward / forward");
|
||||
println!(" - digit 1 / 3 / 5: play the animation <digit> times");
|
||||
println!(" - L: loop the animation forever");
|
||||
println!(" - return: change animation");
|
||||
}
|
||||
|
||||
|
@ -116,13 +119,13 @@ fn keyboard_animation_control(
|
|||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Left) {
|
||||
let elapsed = player.elapsed();
|
||||
player.set_elapsed(elapsed - 0.1);
|
||||
let elapsed = player.seek_time();
|
||||
player.seek_to(elapsed - 0.1);
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Right) {
|
||||
let elapsed = player.elapsed();
|
||||
player.set_elapsed(elapsed + 0.1);
|
||||
let elapsed = player.seek_time();
|
||||
player.seek_to(elapsed + 0.1);
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Return) {
|
||||
|
@ -134,5 +137,24 @@ fn keyboard_animation_control(
|
|||
)
|
||||
.repeat();
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Key1) {
|
||||
player.set_repeat(RepeatAnimation::Count(1));
|
||||
player.replay();
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Key3) {
|
||||
player.set_repeat(RepeatAnimation::Count(3));
|
||||
player.replay();
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Key5) {
|
||||
player.set_repeat(RepeatAnimation::Count(5));
|
||||
player.replay();
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::L) {
|
||||
player.set_repeat(RepeatAnimation::Forever);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,13 +270,13 @@ fn keyboard_animation_control(
|
|||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Left) {
|
||||
let elapsed = player.elapsed();
|
||||
player.set_elapsed(elapsed - 0.1);
|
||||
let elapsed = player.seek_time();
|
||||
player.seek_to(elapsed - 0.1);
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Right) {
|
||||
let elapsed = player.elapsed();
|
||||
player.set_elapsed(elapsed + 0.1);
|
||||
let elapsed = player.seek_time();
|
||||
player.seek_to(elapsed + 0.1);
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Return) {
|
||||
|
|
|
@ -91,7 +91,7 @@ fn handle_inputs(
|
|||
|
||||
let resume = !player.is_paused();
|
||||
// set the current animation to its start and pause it to reset to its starting state
|
||||
player.set_elapsed(0.0).pause();
|
||||
player.seek_to(0.0).pause();
|
||||
|
||||
clips.advance_to_next();
|
||||
let current_clip = clips.current();
|
||||
|
|
Loading…
Reference in a new issue