Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API updates to the AnimationPlayer #9002

Merged
merged 31 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e663e51
RepeatAnimation, is_playback_reversed(), is_finished(), animation upd…
DevinLeamy Jun 30, 2023
a6488df
Apply modulo to elapsed time
DevinLeamy Jun 30, 2023
169a94c
Greater than or equals
DevinLeamy Jun 30, 2023
1400461
lint
DevinLeamy Jun 30, 2023
6e3b15c
Updated names and comments
DevinLeamy Jun 30, 2023
eec1240
Typo
DevinLeamy Jun 30, 2023
a9f82a3
Added animation_clip() accessor and made animation_clip optional
DevinLeamy Jul 1, 2023
a920e90
lint
DevinLeamy Jul 1, 2023
57df0ac
Updated comment
DevinLeamy Jul 1, 2023
5af3277
Conditionally update the elapsed time
DevinLeamy Jul 1, 2023
1d829db
repeat_mode() and completions() public accessors
DevinLeamy Jul 1, 2023
3ad8d27
Updated comment
DevinLeamy Jul 1, 2023
62c67e5
Implement default for RepeatAnimation, with Never as the default
DevinLeamy Aug 16, 2023
d36d5bc
Renamed PlayingAnimation::finished to PlayingAnimation::is_finished t…
DevinLeamy Aug 16, 2023
a3a7eb4
animation_clip: Handle<AnimationClip>, rather than Option<Handle<Anim…
DevinLeamy Aug 16, 2023
60fc68c
Introduced seek_time and PlayingAnimation::update()
DevinLeamy Aug 16, 2023
63a71b6
Introduced AnimationPlayer::replay() to reset the PlayingAnimation's …
DevinLeamy Aug 17, 2023
88fd036
Removed AnimationPlayer::stop_repeating() and added a comment to Anim…
DevinLeamy Aug 17, 2023
764ae7e
Updated examples
DevinLeamy Aug 17, 2023
e028c17
Remove unused import
DevinLeamy Aug 17, 2023
f09ce88
Merge branch 'main' of https://github.com/bevyengine/bevy into animat…
DevinLeamy Aug 17, 2023
9670723
nit: fix comment
DevinLeamy Aug 17, 2023
1d2a9b9
Nit: improved comment
DevinLeamy Aug 17, 2023
d59789b
Replaced deprecated for_each_mut with for_each
DevinLeamy Aug 18, 2023
011d13f
Updated animated_fox example to showcase the repeat modes
DevinLeamy Aug 18, 2023
5b8da3f
Merge branch 'animation-api' of https://github.com/DevinLeamy/bevy in…
DevinLeamy Aug 18, 2023
f52c991
Merge branch 'main' of https://github.com/bevyengine/bevy into animat…
DevinLeamy Aug 18, 2023
7910f05
nit: format
DevinLeamy Aug 18, 2023
6527d72
Reformat comments + re-run CI, post moving run-examples over to windows
DevinLeamy Aug 22, 2023
f50c8c4
nit: cargo fmt --all
DevinLeamy Aug 22, 2023
8d3b642
Applied suggestions (updates to comments and inlines)
DevinLeamy Aug 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 131 additions & 31 deletions crates/bevy_animation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -129,25 +123,94 @@ impl AnimationClip {
}
}

/// Repetition behavior of an animation.
#[derive(Reflect, Copy, Clone, Default)]
pub enum RepeatAnimation {
/// The animation will finish after running once.
#[default]
Never,
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
/// 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,
DevinLeamy marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
DevinLeamy marked this conversation as resolved.
Show resolved Hide resolved
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) {
DevinLeamy marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -485,16 +590,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()];
}
Expand Down Expand Up @@ -548,7 +649,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,
Expand All @@ -558,7 +659,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 {
Expand Down Expand Up @@ -612,7 +713,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),
Expand Down
30 changes: 26 additions & 4 deletions examples/animation/animated_fox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::time::Duration;

use bevy::pbr::CascadeShadowConfigBuilder;
use bevy::prelude::*;
use bevy_internal::animation::RepeatAnimation;

fn main() {
App::new()
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
}
8 changes: 4 additions & 4 deletions examples/stress_tests/many_foxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion examples/tools/scene_viewer/animation_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down