From 9288b0f89db5ebdcb0a9d68726cd8b3266b2b634 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 29 May 2022 17:28:20 -0700 Subject: [PATCH 01/21] Move towards more flexible animation looping Signed-off-by: Alex Saveau --- examples/colormaterial_color.rs | 5 +- examples/menu.rs | 1 - examples/sequence.rs | 34 +++-- examples/sprite_color.rs | 5 +- examples/text_color.rs | 5 +- examples/transform_rotation.rs | 5 +- examples/transform_translation.rs | 5 +- examples/ui_position.rs | 5 +- src/lib.rs | 61 ++++----- src/tweenable.rs | 201 ++++++++++++++++++++---------- 10 files changed, 204 insertions(+), 123 deletions(-) diff --git a/examples/colormaterial_color.rs b/examples/colormaterial_color.rs index 69cd4bd..1d5ee84 100644 --- a/examples/colormaterial_color.rs +++ b/examples/colormaterial_color.rs @@ -79,13 +79,14 @@ fn setup( let tween = Tween::new( *ease_function, - TweeningType::PingPong, Duration::from_secs(1), ColorMaterialColorLens { start: Color::RED, end: Color::BLUE, }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(MaterialMesh2dBundle { diff --git a/examples/menu.rs b/examples/menu.rs index 5dafd84..eefbcaa 100644 --- a/examples/menu.rs +++ b/examples/menu.rs @@ -52,7 +52,6 @@ fn setup(mut commands: Commands, asset_server: Res) { start_time_ms += 500; let tween_scale = Tween::new( EaseFunction::BounceOut, - TweeningType::Once, Duration::from_secs(2), TransformScaleLens { start: Vec3::splat(0.01), diff --git a/examples/sequence.rs b/examples/sequence.rs index ebbb51f..3f5a804 100644 --- a/examples/sequence.rs +++ b/examples/sequence.rs @@ -111,16 +111,27 @@ fn setup(mut commands: Commands, asset_server: Res) { ]; // Build a sequence from an iterator over a Tweenable (here, a Tween) let seq = Sequence::new(dests.windows(2).enumerate().map(|(index, pair)| { - Tween::new( - EaseFunction::QuadraticInOut, - TweeningType::Once, - Duration::from_secs(1), - TransformPositionLens { - start: pair[0] - center, - end: pair[1] - center, - }, - ) - .with_completed_event(true, index as u64) // Get an event after each segment + Tracks::new([ + Tween::new( + EaseFunction::QuadraticInOut, + Duration::from_millis(250), + TransformRotationLens { + start: Quat::IDENTITY, + end: Quat::from_rotation_z(180_f32.to_radians()), + }, + ) + .with_repeat_count(RepeatCount::Finite(4)) + .with_repeat_strategy(RepeatStrategy::Bounce), + Tween::new( + EaseFunction::QuadraticInOut, + Duration::from_secs(1), + TransformPositionLens { + start: pair[0] - center, + end: pair[1] - center, + }, + ) + .with_completed_event(true, index as u64), // Get an event after each segment + ]) })); commands @@ -139,7 +150,6 @@ fn setup(mut commands: Commands, asset_server: Res) { // size at the same time. let tween_move = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::Once, Duration::from_secs(1), TransformPositionLens { start: Vec3::new(-200., 100., 0.), @@ -149,7 +159,6 @@ fn setup(mut commands: Commands, asset_server: Res) { .with_completed_event(true, 99); // Get an event once move completed let tween_rotate = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::Once, Duration::from_secs(1), TransformRotationLens { start: Quat::IDENTITY, @@ -158,7 +167,6 @@ fn setup(mut commands: Commands, asset_server: Res) { ); let tween_scale = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::Once, Duration::from_secs(1), TransformScaleLens { start: Vec3::ONE, diff --git a/examples/sprite_color.rs b/examples/sprite_color.rs index 44b8a82..6d805f6 100644 --- a/examples/sprite_color.rs +++ b/examples/sprite_color.rs @@ -63,13 +63,14 @@ fn setup(mut commands: Commands) { ] { let tween = Tween::new( *ease_function, - TweeningType::PingPong, std::time::Duration::from_secs(1), SpriteColorLens { start: Color::RED, end: Color::BLUE, }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(SpriteBundle { diff --git a/examples/text_color.rs b/examples/text_color.rs index 29a9dae..2ca414f 100644 --- a/examples/text_color.rs +++ b/examples/text_color.rs @@ -70,14 +70,15 @@ fn setup(mut commands: Commands, asset_server: Res) { ] { let tween = Tween::new( *ease_function, - TweeningType::PingPong, std::time::Duration::from_secs(1), TextColorLens { start: Color::RED, end: Color::BLUE, section: 0, }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(TextBundle { diff --git a/examples/transform_rotation.rs b/examples/transform_rotation.rs index cd100ff..4afe340 100644 --- a/examples/transform_rotation.rs +++ b/examples/transform_rotation.rs @@ -77,13 +77,14 @@ fn setup(mut commands: Commands) { ] { let tween = Tween::new( *ease_function, - TweeningType::PingPong, std::time::Duration::from_secs(1), TransformRotationLens { start: Quat::IDENTITY, end: Quat::from_axis_angle(Vec3::Z, std::f32::consts::PI / 2.), }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(( diff --git a/examples/transform_translation.rs b/examples/transform_translation.rs index a1e996b..d3e0cd0 100644 --- a/examples/transform_translation.rs +++ b/examples/transform_translation.rs @@ -76,13 +76,14 @@ fn setup(mut commands: Commands) { ] { let tween = Tween::new( *ease_function, - TweeningType::PingPong, std::time::Duration::from_secs(1), TransformPositionLens { start: Vec3::new(x, screen_y, 0.), end: Vec3::new(x, -screen_y, 0.), }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(SpriteBundle { diff --git a/examples/ui_position.rs b/examples/ui_position.rs index 1778b58..0c0818f 100644 --- a/examples/ui_position.rs +++ b/examples/ui_position.rs @@ -76,7 +76,6 @@ fn setup(mut commands: Commands) { ] { let tween = Tween::new( *ease_function, - TweeningType::PingPong, std::time::Duration::from_secs(1), UiPositionLens { start: Rect { @@ -92,7 +91,9 @@ fn setup(mut commands: Commands) { bottom: Val::Auto, }, }, - ); + ) + .with_repeat_count(RepeatCount::Infinite) + .with_repeat_strategy(RepeatStrategy::Bounce); commands .spawn_bundle(NodeBundle { diff --git a/src/lib.rs b/src/lib.rs index 51f0f86..f2c0e7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,10 +42,7 @@ //! let tween = Tween::new( //! // Use a quadratic easing on both endpoints. //! EaseFunction::QuadraticInOut, -//! // Loop animation back and forth. -//! TweeningType::PingPong, -//! // Animation time (one way only; for ping-pong it takes 2 seconds -//! // to come back to start). +//! // Animation time. //! Duration::from_secs(1), //! // The lens gives access to the Transform component of the Entity, //! // for the Animator to animate it. It also contains the start and @@ -85,7 +82,6 @@ //! let tween1 = Tween::new( //! // [...] //! # EaseFunction::BounceOut, -//! # TweeningType::Once, //! # Duration::from_secs(2), //! # TransformScaleLens { //! # start: Vec3::ZERO, @@ -95,7 +91,6 @@ //! let tween2 = Tween::new( //! // [...] //! # EaseFunction::QuadraticInOut, -//! # TweeningType::Once, //! # Duration::from_secs(1), //! # TransformPositionLens { //! # start: Vec3::ZERO, @@ -163,22 +158,34 @@ pub use plugin::{ }; pub use tweenable::{Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable}; -/// Type of looping for a tween animation. +/// How many times to repeat a tween animation. See also: [`RepeatMode`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TweeningType { - /// Run the animation once from start to end only. - Once, - /// Loop the animation indefinitely, restarting from the start each time the end is reached. - Loop, - /// Loop the animation back and forth, changing direction each time an endpoint is reached. +pub enum RepeatCount { + /// Run the animation N times. + Finite(u32), + /// Loop the animation indefinitely. + Infinite, +} + +/// What to do when a tween animation needs to be repeated. +/// +/// Only applicable when [`RepeatCount`] is greater than 1. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum RepeatStrategy { + /// Reset the animation back to its starting position. + #[default] + Teleport, + /// Follow a ping-pong pattern, changing the direction each time an endpoint is reached. + /// /// A complete cycle start -> end -> start always counts as 2 loop iterations for the various - /// operations where looping matters. - PingPong, + /// operations where looping matters. That is, a 1 second animation will take 2 seconds to end + /// up back where it started. + Bounce, } -impl Default for TweeningType { +impl Default for RepeatCount { fn default() -> Self { - TweeningType::Once + Self::Finite(1) } } @@ -634,9 +641,15 @@ mod tests { } #[test] - fn tweening_type() { - let tweening_type = TweeningType::default(); - assert_eq!(tweening_type, TweeningType::Once); + fn repeat_count() { + let tweening_type = RepeatCount::default(); + assert_eq!(tweening_type, RepeatCount::Finite(1)); + } + + #[test] + fn repeat_strategy() { + let tweening_type = RepeatStrategy::default(); + assert_eq!(tweening_type, RepeatStrategy::Teleport); } #[test] @@ -686,7 +699,6 @@ mod tests { fn animator_new() { let tween = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -702,7 +714,6 @@ mod tests { for state in [AnimatorState::Playing, AnimatorState::Paused] { let tween = Tween::::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -720,7 +731,6 @@ mod tests { let tween = Tween::::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -734,7 +744,6 @@ mod tests { fn animator_controls() { let tween = Tween::::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -773,7 +782,6 @@ mod tests { fn asset_animator_new() { let tween = Tween::::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -789,7 +797,6 @@ mod tests { for state in [AnimatorState::Playing, AnimatorState::Paused] { let tween = Tween::::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -809,7 +816,6 @@ mod tests { let tween = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); @@ -824,7 +830,6 @@ mod tests { fn asset_animator_controls() { let tween = Tween::new( EaseFunction::QuadraticInOut, - TweeningType::PingPong, std::time::Duration::from_secs(1), DummyLens { start: 0., end: 1. }, ); diff --git a/src/tweenable.rs b/src/tweenable.rs index 5004487..d0f0941 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use std::time::Duration; -use crate::{EaseMethod, Lens, TweeningDirection, TweeningType}; +use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// Playback state of a [`Tweenable`]. /// @@ -42,7 +42,7 @@ pub struct TweenCompleted { pub user_data: u64, } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug)] struct AnimClock { elapsed: Duration, duration: Duration, @@ -221,7 +221,8 @@ pub struct Tween { ease_function: EaseMethod, clock: AnimClock, times_completed: u32, - tweening_type: TweeningType, + count: RepeatCount, + strategy: RepeatStrategy, direction: TweeningDirection, lens: Box + Send + Sync + 'static>, on_completed: Option>>, @@ -238,7 +239,6 @@ impl Tween { /// # use std::time::Duration; /// let tween1 = Tween::new( /// EaseFunction::QuadraticInOut, - /// TweeningType::Once, /// Duration::from_secs_f32(1.0), /// TransformPositionLens { /// start: Vec3::ZERO, @@ -247,7 +247,6 @@ impl Tween { /// ); /// let tween2 = Tween::new( /// EaseFunction::QuadraticInOut, - /// TweeningType::Once, /// Duration::from_secs_f32(1.0), /// TransformRotationLens { /// start: Quat::IDENTITY, @@ -271,7 +270,6 @@ impl Tween { /// # use std::time::Duration; /// let tween = Tween::new( /// EaseFunction::QuadraticInOut, - /// TweeningType::Once, /// Duration::from_secs_f32(1.0), /// TransformPositionLens { /// start: Vec3::ZERO, @@ -279,20 +277,16 @@ impl Tween { /// }, /// ); /// ``` - pub fn new( - ease_function: impl Into, - tweening_type: TweeningType, - duration: Duration, - lens: L, - ) -> Self + pub fn new(ease_function: impl Into, duration: Duration, lens: L) -> Self where L: Lens + Send + Sync + 'static, { Tween { ease_function: ease_function.into(), - clock: AnimClock::new(duration, tweening_type != TweeningType::Once), + clock: AnimClock::new(duration, RepeatCount::default() != RepeatCount::Finite(1)), times_completed: 0, - tweening_type, + count: default(), + strategy: default(), direction: TweeningDirection::Forward, lens: Box::new(lens), on_completed: None, @@ -313,7 +307,6 @@ impl Tween { /// let tween = Tween::new( /// // [...] /// # EaseFunction::QuadraticInOut, - /// # TweeningType::Once, /// # Duration::from_secs_f32(1.0), /// # TransformPositionLens { /// # start: Vec3::ZERO, @@ -359,6 +352,29 @@ impl Tween { self } + /// TODO + pub fn set_repeat_count(&mut self, count: RepeatCount) { + self.count = count; + self.clock.is_looping = count != RepeatCount::Finite(1); + } + + /// TODO + pub fn with_repeat_count(mut self, count: RepeatCount) -> Self { + self.set_repeat_count(count); + self + } + + /// TODO + pub fn set_repeat_strategy(&mut self, strategy: RepeatStrategy) { + self.strategy = strategy; + } + + /// TODO + pub fn with_repeat_strategy(mut self, strategy: RepeatStrategy) -> Self { + self.strategy = strategy; + self + } + /// The current animation direction. /// /// See [`TweeningDirection`] for details. @@ -404,7 +420,11 @@ impl Tweenable for Tween { } fn is_looping(&self) -> bool { - self.tweening_type != TweeningType::Once + match self.count { + RepeatCount::Finite(times) if times == 1 => false, + RepeatCount::Finite(times) => self.times_completed < times, + RepeatCount::Infinite => true, + } } fn set_progress(&mut self, progress: f32) { @@ -429,14 +449,9 @@ impl Tweenable for Tween { // Tick the animation clock let times_completed = self.clock.tick(delta); self.times_completed += times_completed; - if times_completed & 1 != 0 && self.tweening_type == TweeningType::PingPong { + if self.strategy == RepeatStrategy::Bounce && times_completed & 1 != 0 { self.direction = !self.direction; } - let state = if self.is_looping() || times_completed == 0 { - TweenState::Active - } else { - TweenState::Completed - }; let progress = self.clock.progress(); // Apply the lens, even if the animation finished, to ensure the state is consistent @@ -460,7 +475,11 @@ impl Tweenable for Tween { } } - state + if self.is_looping() || self.times_completed == 0 { + TweenState::Active + } else { + TweenState::Completed + } } fn times_completed(&self) -> u32 { @@ -834,27 +853,29 @@ mod tests { #[test] fn tween_tick() { for tweening_direction in &[TweeningDirection::Forward, TweeningDirection::Backward] { - for tweening_type in &[ - TweeningType::Once, - TweeningType::Loop, - TweeningType::PingPong, + for (count, strategy) in &[ + (RepeatCount::Finite(1), RepeatStrategy::default()), + (RepeatCount::Infinite, RepeatStrategy::Teleport), + (RepeatCount::Finite(2), RepeatStrategy::Teleport), + (RepeatCount::Infinite, RepeatStrategy::Bounce), + (RepeatCount::Finite(2), RepeatStrategy::Bounce), ] { println!( - "TweeningType: type={:?} dir={:?}", - tweening_type, tweening_direction + "TweeningType: count={count:?} strategy={strategy:?} dir={tweening_direction:?}", ); // Create a linear tween over 1 second let mut tween = Tween::new( EaseMethod::Linear, - *tweening_type, Duration::from_secs_f32(1.0), TransformPositionLens { start: Vec3::ZERO, end: Vec3::ONE, }, ) - .with_direction(*tweening_direction); + .with_direction(*tweening_direction) + .with_repeat_count(*count) + .with_repeat_strategy(*strategy); assert_eq!(tween.direction(), *tweening_direction); assert!(tween.on_completed.is_none()); assert!(tween.event_data.is_none()); @@ -894,8 +915,8 @@ mod tests { for i in 1..=11 { // Calculate expected values let (progress, times_completed, mut direction, expected_state, just_completed) = - match tweening_type { - TweeningType::Once => { + match count { + RepeatCount::Finite(1) => { let progress = (i as f32 * 0.2).min(1.0); let times_completed = if i >= 5 { 1 } else { 0 }; let state = if i < 5 { @@ -912,36 +933,77 @@ mod tests { just_completed, ) } - TweeningType::Loop => { - let progress = (i as f32 * 0.2).fract(); - let times_completed = i / 5; - let just_completed = i % 5 == 0; - ( - progress, - times_completed, - TweeningDirection::Forward, - TweenState::Active, - just_completed, - ) + RepeatCount::Finite(_) => { + if *strategy == RepeatStrategy::Teleport { + let progress = (i as f32 * 0.2).fract(); + let times_completed = i / 5; + let just_completed = i % 5 == 0; + ( + progress, + times_completed, + TweeningDirection::Forward, + if i < 10 { + TweenState::Active + } else { + TweenState::Completed + }, + just_completed, + ) + } else { + let i5 = i % 5; + let progress = i5 as f32 * 0.2; + let times_completed = i / 5; + let i10 = i % 10; + let direction = if i10 >= 5 { + TweeningDirection::Backward + } else { + TweeningDirection::Forward + }; + let just_completed = i5 == 0; + ( + progress, + times_completed, + direction, + if i < 10 { + TweenState::Active + } else { + TweenState::Completed + }, + just_completed, + ) + } } - TweeningType::PingPong => { - let i5 = i % 5; - let progress = i5 as f32 * 0.2; - let times_completed = i / 5; - let i10 = i % 10; - let direction = if i10 >= 5 { - TweeningDirection::Backward + RepeatCount::Infinite => { + if *strategy == RepeatStrategy::Teleport { + let progress = (i as f32 * 0.2).fract(); + let times_completed = i / 5; + let just_completed = i % 5 == 0; + ( + progress, + times_completed, + TweeningDirection::Forward, + TweenState::Active, + just_completed, + ) } else { - TweeningDirection::Forward - }; - let just_completed = i5 == 0; - ( - progress, - times_completed, - direction, - TweenState::Active, - just_completed, - ) + let i5 = i % 5; + let progress = i5 as f32 * 0.2; + let times_completed = i / 5; + let i10 = i % 10; + let direction = if i10 >= 5 { + TweeningDirection::Backward + } else { + TweeningDirection::Forward + }; + let just_completed = i5 == 0; + ( + progress, + times_completed, + direction, + TweenState::Active, + just_completed, + ) + } } }; let factor = if tweening_direction.is_backward() { @@ -980,7 +1042,14 @@ mod tests { // Check actual values assert_eq!(tween.direction(), direction); - assert_eq!(tween.is_looping(), *tweening_type != TweeningType::Once); + assert_eq!( + tween.is_looping(), + match *count { + RepeatCount::Finite(times) if times == 1 => false, + RepeatCount::Finite(times) => times_completed < times, + RepeatCount::Infinite => true, + } + ); assert_eq!(actual_state, expected_state); assert!(abs_diff_eq(tween.progress(), progress, 1e-5)); assert_eq!(tween.times_completed(), times_completed); @@ -1009,7 +1078,7 @@ mod tests { // Rewind tween.rewind(); assert_eq!(tween.direction(), *tweening_direction); // does not change - assert_eq!(tween.is_looping(), *tweening_type != TweeningType::Once); + assert_eq!(tween.is_looping(), *count != RepeatCount::Finite(1)); assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); assert_eq!(tween.times_completed(), 0); @@ -1045,7 +1114,6 @@ mod tests { fn tween_dir() { let mut tween = Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(1.0), TransformPositionLens { start: Vec3::ZERO, @@ -1103,7 +1171,6 @@ mod tests { fn seq_tick() { let tween1 = Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(1.0), TransformPositionLens { start: Vec3::ZERO, @@ -1112,7 +1179,6 @@ mod tests { ); let tween2 = Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(1.0), TransformRotationLens { start: Quat::IDENTITY, @@ -1163,7 +1229,6 @@ mod tests { let mut seq = Sequence::new((1..5).map(|i| { Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(0.2 * i as f32), TransformPositionLens { start: Vec3::ZERO, @@ -1194,7 +1259,6 @@ mod tests { fn tracks_tick() { let tween1 = Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(1.), TransformPositionLens { start: Vec3::ZERO, @@ -1203,7 +1267,6 @@ mod tests { ); let tween2 = Tween::new( EaseMethod::Linear, - TweeningType::Once, Duration::from_secs_f32(0.8), // shorter TransformRotationLens { start: Quat::IDENTITY, From 30b077ae75c096535e7986dfceb2e251de32c0d3 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Sun, 29 May 2022 22:30:26 -0700 Subject: [PATCH 02/21] Don't use nightly stuff Signed-off-by: Alex Saveau --- src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f2c0e7f..47ca7d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,10 +170,9 @@ pub enum RepeatCount { /// What to do when a tween animation needs to be repeated. /// /// Only applicable when [`RepeatCount`] is greater than 1. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RepeatStrategy { /// Reset the animation back to its starting position. - #[default] Teleport, /// Follow a ping-pong pattern, changing the direction each time an endpoint is reached. /// @@ -189,6 +188,12 @@ impl Default for RepeatCount { } } +impl Default for RepeatStrategy { + fn default() -> Self { + Self::Teleport + } +} + /// Playback state of an animator. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnimatorState { From 3df887008247b21c65a3926d4d12fe637808c031 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 7 Jun 2022 13:22:22 -0700 Subject: [PATCH 03/21] Get rid of is_looping and finish implementing stuff Signed-off-by: Alex Saveau --- src/lib.rs | 13 ++-- src/tweenable.rs | 156 +++++++++++++++++++++-------------------------- 2 files changed, 75 insertions(+), 94 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8246020..635060a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,17 +141,13 @@ //! [`Sprite`]: https://docs.rs/bevy/0.7.0/bevy/sprite/struct.Sprite.html //! [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html -use bevy::{asset::Asset, prelude::*}; use std::time::Duration; +use bevy::{asset::Asset, prelude::*}; use interpolation::Ease as IEase; pub use interpolation::EaseFunction; pub use interpolation::Lerp; -pub mod lens; -mod plugin; -mod tweenable; - pub use lens::Lens; pub use plugin::{ asset_animator_system, component_animator_system, AnimationSystem, TweeningPlugin, @@ -160,6 +156,10 @@ pub use tweenable::{ BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable, }; +pub mod lens; +mod plugin; +mod tweenable; + /// How many times to repeat a tween animation. See also: [`RepeatMode`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RepeatCount { @@ -526,9 +526,10 @@ impl AssetAnimator { #[cfg(test)] mod tests { - use super::{lens::*, *}; use bevy::reflect::TypeUuid; + use super::{lens::*, *}; + struct DummyLens { start: f32, end: f32, diff --git a/src/tweenable.rs b/src/tweenable.rs index 5a6923b..8968c62 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -1,6 +1,7 @@ -use bevy::prelude::*; use std::time::Duration; +use bevy::prelude::*; + use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// The dynamic tweenable type. @@ -26,7 +27,6 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// # struct MyTweenable; /// # impl Tweenable for MyTweenable { /// # fn duration(&self) -> Duration { unimplemented!() } -/// # fn is_looping(&self) -> bool { unimplemented!() } /// # fn set_progress(&mut self, progress: f32) { unimplemented!() } /// # fn progress(&self) -> f32 { unimplemented!() } /// # fn tick(&mut self, delta: Duration, target: &mut Transform, entity: Entity, event_writer: &mut EventWriter) -> TweenState { unimplemented!() } @@ -56,8 +56,9 @@ pub type BoxedTweenable = Box + Send + Sync + 'static>; pub enum TweenState { /// The tweenable is still active, and did not reach its end state yet. Active, - /// Animation reached its end state. The tweenable is idling at its latest time. This can only happen - /// for [`TweeningType::Once`], since other types loop indefinitely. + /// Animation reached its end state. The tweenable is idling at its latest time. + /// + /// Note that [`RepeatCount::Infinite`] tweenables never reach this state. Completed, } @@ -91,15 +92,13 @@ pub struct TweenCompleted { struct AnimClock { elapsed: Duration, duration: Duration, - is_looping: bool, } impl AnimClock { - fn new(duration: Duration, is_looping: bool) -> Self { + fn new(duration: Duration) -> Self { Self { elapsed: Duration::ZERO, duration, - is_looping, } } @@ -108,38 +107,55 @@ impl AnimClock { if self.elapsed < self.duration { 0 - } else if self.is_looping { + } else { let elapsed = self.elapsed.as_nanos(); let duration = self.duration.as_nanos(); self.elapsed = Duration::from_nanos((elapsed % duration) as u64); (elapsed / duration) as u32 - } else { - self.elapsed = self.duration; - 1 } } fn set_progress(&mut self, progress: f32) { - let progress = if self.is_looping { - progress.max(0.).fract() - } else { - progress.clamp(0., 1.) - }; - - self.elapsed = self.duration.mul_f32(progress); + self.elapsed = self.duration.mul_f32(progress.clamp(0., 1.)); } fn progress(&self) -> f32 { self.elapsed.as_secs_f32() / self.duration.as_secs_f32() } - fn completed(&self) -> bool { - self.elapsed >= self.duration + fn reset(&mut self) { + self.elapsed = Duration::ZERO; + } +} + +#[derive(Debug, Default)] +struct AnimCompletion { + times_completed: u32, + count: RepeatCount, + strategy: RepeatStrategy, +} + +impl AnimCompletion { + fn record_completions(&mut self, times_completed: u32) { + self.times_completed = self.times_completed.saturating_add(times_completed); + } + + fn state(&self) -> TweenState { + match self.count { + RepeatCount::Finite(times) => { + if self.times_completed >= times { + TweenState::Completed + } else { + TweenState::Active + } + } + RepeatCount::Infinite => TweenState::Active, + } } fn reset(&mut self) { - self.elapsed = Duration::ZERO; + self.times_completed = 0; } } @@ -156,11 +172,6 @@ pub trait Tweenable: Send + Sync { /// reach back the same state in this case is the double of the returned value. fn duration(&self) -> Duration; - /// Return `true` if the animation is looping. - /// - /// Looping tweenables are of type [`TweeningType::Loop`] or [`TweeningType::PingPong`]. - fn is_looping(&self) -> bool; - /// Set the current animation playback progress. /// /// See [`progress()`] for details on the meaning. @@ -247,9 +258,7 @@ pub type CompletedCallback = dyn Fn(Entity, &Tween) + Send + Sync + 'stati pub struct Tween { ease_function: EaseMethod, clock: AnimClock, - times_completed: u32, - count: RepeatCount, - strategy: RepeatStrategy, + completion: AnimCompletion, direction: TweeningDirection, lens: Box + Send + Sync + 'static>, on_completed: Option>>, @@ -312,10 +321,8 @@ impl Tween { { Self { ease_function: ease_function.into(), - clock: AnimClock::new(duration, RepeatCount::default() != RepeatCount::Finite(1)), - times_completed: 0, - count: default(), - strategy: default(), + clock: AnimClock::new(duration), + completion: default(), direction: TweeningDirection::Forward, lens: Box::new(lens), on_completed: None, @@ -385,24 +392,25 @@ impl Tween { /// TODO pub fn set_repeat_count(&mut self, count: RepeatCount) { - self.count = count; - self.clock.is_looping = count != RepeatCount::Finite(1); + self.completion.count = count; } /// TODO + #[must_use] pub fn with_repeat_count(mut self, count: RepeatCount) -> Self { - self.set_repeat_count(count); + self.completion.count = count; self } /// TODO pub fn set_repeat_strategy(&mut self, strategy: RepeatStrategy) { - self.strategy = strategy; + self.completion.strategy = strategy; } /// TODO + #[must_use] pub fn with_repeat_strategy(mut self, strategy: RepeatStrategy) -> Self { - self.strategy = strategy; + self.completion.strategy = strategy; self } @@ -451,20 +459,16 @@ impl Tweenable for Tween { self.clock.duration } - fn is_looping(&self) -> bool { - match self.count { - RepeatCount::Finite(times) if times == 1 => false, - RepeatCount::Finite(times) => self.times_completed < times, - RepeatCount::Infinite => true, - } - } - fn set_progress(&mut self, progress: f32) { self.clock.set_progress(progress); } fn progress(&self) -> f32 { - self.clock.progress() + if self.completion.state() == TweenState::Completed { + 1. + } else { + self.clock.progress() + } } fn tick( @@ -474,17 +478,17 @@ impl Tweenable for Tween { entity: Entity, event_writer: &mut EventWriter, ) -> TweenState { - if !self.is_looping() && self.clock.completed() { + if self.completion.state() == TweenState::Completed { return TweenState::Completed; } // Tick the animation clock let times_completed = self.clock.tick(delta); - self.times_completed += times_completed; - if self.strategy == RepeatStrategy::Bounce && times_completed & 1 != 0 { + self.completion.record_completions(times_completed); + if self.completion.strategy == RepeatStrategy::Bounce && times_completed & 1 != 0 { self.direction = !self.direction; } - let progress = self.clock.progress(); + let progress = self.progress(); // Apply the lens, even if the animation finished, to ensure the state is consistent let mut factor = progress; @@ -507,20 +511,16 @@ impl Tweenable for Tween { } } - if self.is_looping() || self.times_completed == 0 { - TweenState::Active - } else { - TweenState::Completed - } + self.completion.state() } fn times_completed(&self) -> u32 { - self.times_completed + self.completion.times_completed } fn rewind(&mut self) { self.clock.reset(); - self.times_completed = 0; + self.completion.reset(); } } @@ -607,10 +607,6 @@ impl Tweenable for Sequence { self.duration } - fn is_looping(&self) -> bool { - false // TODO - implement looping sequences... - } - fn set_progress(&mut self, progress: f32) { self.times_completed = if progress >= 1. { 1 } else { 0 }; let progress = progress.clamp(0., 1.); // not looping @@ -716,10 +712,6 @@ impl Tweenable for Tracks { self.duration } - fn is_looping(&self) -> bool { - false // TODO - implement looping tracks... - } - fn set_progress(&mut self, progress: f32) { self.times_completed = if progress >= 1. { 1 } else { 0 }; // not looping let progress = progress.clamp(0., 1.); // not looping @@ -799,10 +791,6 @@ impl Tweenable for Delay { self.timer.duration() } - fn is_looping(&self) -> bool { - false - } - fn set_progress(&mut self, progress: f32) { // need to reset() to clear finished() unfortunately self.timer.reset(); @@ -870,7 +858,7 @@ mod tests { #[test] fn anim_clock_precision() { let duration = Duration::from_millis(1); - let mut clock = AnimClock::new(duration, true); + let mut clock = AnimClock::new(duration); let test_ticks = [ Duration::from_micros(123), @@ -983,7 +971,8 @@ mod tests { } RepeatCount::Finite(_) => { if *strategy == RepeatStrategy::Teleport { - let progress = (i as f32 * 0.2).fract(); + let progress = + if i < 10 { (i as f32 * 0.2).fract() } else { 1. }; let times_completed = i / 5; let just_completed = i % 5 == 0; ( @@ -999,7 +988,7 @@ mod tests { ) } else { let i5 = i % 5; - let progress = i5 as f32 * 0.2; + let progress = if i < 10 { i5 as f32 * 0.2 } else { 1. }; let times_completed = i / 5; let i10 = i % 10; let direction = if i10 >= 5 { @@ -1090,14 +1079,6 @@ mod tests { // Check actual values assert_eq!(tween.direction(), direction); - assert_eq!( - tween.is_looping(), - match *count { - RepeatCount::Finite(times) if times == 1 => false, - RepeatCount::Finite(times) => times_completed < times, - RepeatCount::Infinite => true, - } - ); assert_eq!(actual_state, expected_state); assert!(abs_diff_eq(tween.progress(), progress, 1e-5)); assert_eq!(tween.times_completed(), times_completed); @@ -1126,7 +1107,6 @@ mod tests { // Rewind tween.rewind(); assert_eq!(tween.direction(), *tweening_direction); // does not change - assert_eq!(tween.is_looping(), *count != RepeatCount::Finite(1)); assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); assert_eq!(tween.times_completed(), 0); @@ -1257,13 +1237,13 @@ mod tests { } else if i < 10 { assert_eq!(state, TweenState::Active); let alpha_deg = (18 * (i - 5)) as f32; - assert!(transform.translation.abs_diff_eq(Vec3::splat(1.), 1e-5)); + assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); assert!(transform .rotation .abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5)); } else { assert_eq!(state, TweenState::Completed); - assert!(transform.translation.abs_diff_eq(Vec3::splat(1.), 1e-5)); + assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); assert!(transform .rotation .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5)); @@ -1284,7 +1264,6 @@ mod tests { }, ) })); - assert!(!seq.is_looping()); let mut progress = 0.; for i in 1..5 { @@ -1323,7 +1302,6 @@ mod tests { ); let mut tracks = Tracks::new([tween1, tween2]); assert_eq!(tracks.duration(), Duration::from_secs_f32(1.)); // max(1., 0.8) - assert!(!tracks.is_looping()); let mut transform = Transform::default(); @@ -1355,7 +1333,7 @@ mod tests { assert_eq!(state, TweenState::Completed); assert_eq!(tracks.times_completed(), 1); assert!((tracks.progress() - 1.).abs() < 1e-5); - assert!(transform.translation.abs_diff_eq(Vec3::splat(1.), 1e-5)); + assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); assert!(transform .rotation .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5)); @@ -1366,6 +1344,7 @@ mod tests { assert_eq!(tracks.times_completed(), 0); assert!(tracks.progress().abs() < 1e-5); + tracks.rewind(); tracks.set_progress(0.9); assert!((tracks.progress() - 0.9).abs() < 1e-5); // tick to udpate state (set_progress() does not update state) @@ -1378,6 +1357,7 @@ mod tests { assert_eq!(state, TweenState::Active); assert_eq!(tracks.times_completed(), 0); + tracks.rewind(); tracks.set_progress(3.2); assert!((tracks.progress() - 1.).abs() < 1e-5); // tick to udpate state (set_progress() does not update state) @@ -1390,6 +1370,7 @@ mod tests { assert_eq!(state, TweenState::Completed); assert_eq!(tracks.times_completed(), 1); // no looping + tracks.rewind(); tracks.set_progress(-0.5); assert!(tracks.progress().abs() < 1e-5); // tick to udpate state (set_progress() does not update state) @@ -1411,7 +1392,6 @@ mod tests { { let tweenable: &dyn Tweenable = &delay; assert_eq!(tweenable.duration(), duration); - assert!(!tweenable.is_looping()); assert!(tweenable.progress().abs() < 1e-5); } From 4e1506407b4de1e6fd04e97173e201cfe80cf329 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Tue, 7 Jun 2022 15:00:20 -0700 Subject: [PATCH 04/21] Reformat all comments using rustfmt Signed-off-by: Alex Saveau --- examples/sequence.rs | 14 ++-- src/lens.rs | 100 ++++++++++++++----------- src/lib.rs | 131 +++++++++++++++++++-------------- src/plugin.rs | 27 ++++--- src/tweenable.rs | 170 ++++++++++++++++++++++++++----------------- 5 files changed, 257 insertions(+), 185 deletions(-) diff --git a/examples/sequence.rs b/examples/sequence.rs index 161676b..f09d9a0 100644 --- a/examples/sequence.rs +++ b/examples/sequence.rs @@ -118,7 +118,8 @@ fn setup(mut commands: Commands, asset_server: Res) { end: pair[1] - center, }, ) - .with_completed_event(true, index as u64) // Get an event after each segment + // Get an event after each segment + .with_completed_event(true, index as u64) })); commands @@ -133,8 +134,8 @@ fn setup(mut commands: Commands, asset_server: Res) { .insert(RedSprite) .insert(Animator::new(seq)); - // First move from left to right, then rotate around self 180 degrees while scaling - // size at the same time. + // First move from left to right, then rotate around self 180 degrees while + // scaling size at the same time. let tween_move = Tween::new( EaseFunction::QuadraticInOut, TweeningType::Once, @@ -163,10 +164,11 @@ fn setup(mut commands: Commands, asset_server: Res) { end: Vec3::splat(2.0), }, ); - // Build parallel tracks executing two tweens at the same time : rotate and scale. + // Build parallel tracks executing two tweens at the same time: rotate and + // scale. let tracks = Tracks::new([tween_rotate, tween_scale]); - // Build a sequence from an heterogeneous list of tweenables by casting them manually - // to a boxed Tweenable : first move, then { rotate + scale }. + // Build a sequence from an heterogeneous list of tweenables by casting them + // manually to a BoxedTweenable: first move, then { rotate + scale }. let seq2 = Sequence::new([Box::new(tween_move) as BoxedTweenable<_>, tracks.into()]); commands diff --git a/src/lens.rs b/src/lens.rs index a8eb345..368b740 100644 --- a/src/lens.rs +++ b/src/lens.rs @@ -2,9 +2,10 @@ //! //! # Predefined lenses //! -//! This module contains predefined lenses for common use cases. Those lenses are -//! entirely optional. They can be used if they fit your use case, to save some time, -//! but are not treated any differently from a custom user-provided lens. +//! This module contains predefined lenses for common use cases. Those lenses +//! are entirely optional. They can be used if they fit your use case, to save +//! some time, but are not treated any differently from a custom user-provided +//! lens. //! //! # Rotations //! @@ -12,19 +13,19 @@ //! //! ## Shortest-path rotation //! -//! The [`TransformRotationLens`] animates the [`rotation`] field of a [`Transform`] -//! component using [`Quat::slerp()`]. It inherits the properties of that method, and -//! in particular the fact it always finds the "shortest path" from start to end. This -//! is well suited for animating a rotation between two given directions, but will -//! provide unexpected results if you try to make an entity rotate around a given axis -//! for more than half a turn, as [`Quat::slerp()`] will then try to move "the other -//! way around". +//! The [`TransformRotationLens`] animates the [`rotation`] field of a +//! [`Transform`] component using [`Quat::slerp()`]. It inherits the properties +//! of that method, and in particular the fact it always finds the "shortest +//! path" from start to end. This is well suited for animating a rotation +//! between two given directions, but will provide unexpected results if you try +//! to make an entity rotate around a given axis for more than half a turn, as +//! [`Quat::slerp()`] will then try to move "the other way around". //! //! ## Angle-focused rotations //! -//! Conversely, for cases where the rotation direction is important, like when trying -//! to do a full 360-degree turn, a series of angle-based interpolation lenses is -//! provided: +//! Conversely, for cases where the rotation direction is important, like when +//! trying to do a full 360-degree turn, a series of angle-based interpolation +//! lenses is provided: //! - [`TransformRotateXLens`] //! - [`TransformRotateYLens`] //! - [`TransformRotateZLens`] @@ -38,9 +39,10 @@ use bevy::prelude::*; /// A lens over a subset of a component. /// -/// The lens takes a `target` component or asset from a query, as a mutable reference, -/// and animates (tweens) a subset of the fields of the component/asset based on the -/// linear ratio `ratio` in \[0:1\], already sampled from the easing curve. +/// The lens takes a `target` component or asset from a query, as a mutable +/// reference, and animates (tweens) a subset of the fields of the +/// component/asset based on the linear ratio `ratio` in \[0:1\], already +/// sampled from the easing curve. /// /// # Example /// @@ -63,16 +65,17 @@ use bevy::prelude::*; /// } /// } /// ``` -/// pub trait Lens { - /// Perform a linear interpolation (lerp) over the subset of fields of a component - /// or asset the lens focuses on, based on the linear ratio `ratio`. The `target` - /// component or asset is mutated in place. The implementation decides which fields - /// are interpolated, and performs the animation in-place, overwriting the target. + /// Perform a linear interpolation (lerp) over the subset of fields of a + /// component or asset the lens focuses on, based on the linear ratio + /// `ratio`. The `target` component or asset is mutated in place. The + /// implementation decides which fields are interpolated, and performs + /// the animation in-place, overwriting the target. fn lerp(&mut self, target: &mut T, ratio: f32); } -/// A lens to manipulate the [`color`] field of a section of a [`Text`] component. +/// A lens to manipulate the [`color`] field of a section of a [`Text`] +/// component. /// /// [`color`]: https://docs.rs/bevy/0.7.0/bevy/text/struct.TextStyle.html#structfield.color /// [`Text`]: https://docs.rs/bevy/0.7.0/bevy/text/struct.Text.html @@ -90,7 +93,8 @@ pub struct TextColorLens { #[cfg(feature = "bevy_ui")] impl Lens for TextColorLens { fn lerp(&mut self, target: &mut Text, ratio: f32) { - // Note: Add for Color affects alpha, but not Mul. So use Vec4 for consistency. + // Note: Add for Color affects alpha, but not Mul. So use Vec4 for + // consistency. let start: Vec4 = self.start.into(); let end: Vec4 = self.end.into(); let value = start.lerp(end, ratio); @@ -120,13 +124,15 @@ impl Lens for TransformPositionLens { /// A lens to manipulate the [`rotation`] field of a [`Transform`] component. /// /// This lens interpolates the [`rotation`] field of a [`Transform`] component -/// from a `start` value to an `end` value using the spherical linear interpolation -/// provided by [`Quat::slerp()`]. This means the rotation always uses the shortest -/// path from `start` to `end`. In particular, this means it cannot make entities -/// do a full 360 degrees turn. Instead use [`TransformRotateXLens`] and similar -/// to interpolate the rotation angle around a given axis. +/// from a `start` value to an `end` value using the spherical linear +/// interpolation provided by [`Quat::slerp()`]. This means the rotation always +/// uses the shortest path from `start` to `end`. In particular, this means it +/// cannot make entities do a full 360 degrees turn. Instead use +/// [`TransformRotateXLens`] and similar to interpolate the rotation angle +/// around a given axis. /// -/// See the [top-level `lens` module documentation] for a comparison of rotation lenses. +/// See the [top-level `lens` module documentation] for a comparison of rotation +/// lenses. /// /// [`rotation`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html#structfield.rotation /// [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html @@ -150,10 +156,11 @@ impl Lens for TransformRotationLens { /// /// This lens interpolates the rotation angle of a [`Transform`] component from /// a `start` value to an `end` value, for a rotation around the X axis. Unlike -/// [`TransformRotationLens`], it can produce an animation that rotates the entity -/// any number of turns around its local X axis. +/// [`TransformRotationLens`], it can produce an animation that rotates the +/// entity any number of turns around its local X axis. /// -/// See the [top-level `lens` module documentation] for a comparison of rotation lenses. +/// See the [top-level `lens` module documentation] for a comparison of rotation +/// lenses. /// /// [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html /// [top-level `lens` module documentation]: crate::lens @@ -176,10 +183,11 @@ impl Lens for TransformRotateXLens { /// /// This lens interpolates the rotation angle of a [`Transform`] component from /// a `start` value to an `end` value, for a rotation around the Y axis. Unlike -/// [`TransformRotationLens`], it can produce an animation that rotates the entity -/// any number of turns around its local Y axis. +/// [`TransformRotationLens`], it can produce an animation that rotates the +/// entity any number of turns around its local Y axis. /// -/// See the [top-level `lens` module documentation] for a comparison of rotation lenses. +/// See the [top-level `lens` module documentation] for a comparison of rotation +/// lenses. /// /// [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html /// [top-level `lens` module documentation]: crate::lens @@ -202,10 +210,11 @@ impl Lens for TransformRotateYLens { /// /// This lens interpolates the rotation angle of a [`Transform`] component from /// a `start` value to an `end` value, for a rotation around the Z axis. Unlike -/// [`TransformRotationLens`], it can produce an animation that rotates the entity -/// any number of turns around its local Z axis. +/// [`TransformRotationLens`], it can produce an animation that rotates the +/// entity any number of turns around its local Z axis. /// -/// See the [top-level `lens` module documentation] for a comparison of rotation lenses. +/// See the [top-level `lens` module documentation] for a comparison of rotation +/// lenses. /// /// [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html /// [top-level `lens` module documentation]: crate::lens @@ -227,11 +236,12 @@ impl Lens for TransformRotateZLens { /// A lens to rotate a [`Transform`] component around a given fixed axis. /// /// This lens interpolates the rotation angle of a [`Transform`] component from -/// a `start` value to an `end` value, for a rotation around a given axis. Unlike -/// [`TransformRotationLens`], it can produce an animation that rotates the entity -/// any number of turns around that axis. +/// a `start` value to an `end` value, for a rotation around a given axis. +/// Unlike [`TransformRotationLens`], it can produce an animation that rotates +/// the entity any number of turns around that axis. /// -/// See the [top-level `lens` module documentation] for a comparison of rotation lenses. +/// See the [top-level `lens` module documentation] for a comparison of rotation +/// lenses. /// /// # Panics /// @@ -327,7 +337,8 @@ pub struct ColorMaterialColorLens { #[cfg(feature = "bevy_sprite")] impl Lens for ColorMaterialColorLens { fn lerp(&mut self, target: &mut ColorMaterial, ratio: f32) { - // Note: Add for Color affects alpha, but not Mul. So use Vec4 for consistency. + // Note: Add for Color affects alpha, but not Mul. So use Vec4 for + // consistency. let start: Vec4 = self.start.into(); let end: Vec4 = self.end.into(); let value = start.lerp(end, ratio); @@ -351,7 +362,8 @@ pub struct SpriteColorLens { #[cfg(feature = "bevy_sprite")] impl Lens for SpriteColorLens { fn lerp(&mut self, target: &mut Sprite, ratio: f32) { - // Note: Add for Color affects alpha, but not Mul. So use Vec4 for consistency. + // Note: Add for Color affects alpha, but not Mul. So use Vec4 for + // consistency. let start: Vec4 = self.start.into(); let end: Vec4 = self.end.into(); let value = start.lerp(end, ratio); diff --git a/src/lib.rs b/src/lib.rs index f05d268..55a104c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,9 +12,11 @@ //! Tweening animation plugin for the Bevy game engine //! -//! 🍃 Bevy Tweening provides interpolation-based animation between ("tweening") two values, for Bevy components -//! and assets. Each field of a component or asset can be animated via a collection or predefined easing functions, -//! or providing a custom animation curve. Custom components and assets are also supported. +//! 🍃 Bevy Tweening provides interpolation-based animation between ("tweening") +//! two values, for Bevy components and assets. Each field of a component or +//! asset can be animated via a collection or predefined easing functions, +//! or providing a custom animation curve. Custom components and assets are also +//! supported. //! //! # Example //! @@ -66,17 +68,20 @@ //! //! # Tweenables //! -//! 🍃 Bevy Tweening supports several types of _tweenables_, building blocks that can be combined to form complex -//! animations. A tweenable is a type implementing the [`Tweenable`] trait. +//! 🍃 Bevy Tweening supports several types of _tweenables_, building blocks +//! that can be combined to form complex animations. A tweenable is a type +//! implementing the [`Tweenable`] trait. //! //! - [`Tween`] - A simple tween (easing) animation between two values. -//! - [`Sequence`] - A series of tweenables executing in series, one after the other. +//! - [`Sequence`] - A series of tweenables executing in series, one after the +//! other. //! - [`Tracks`] - A collection of tweenables executing in parallel. //! - [`Delay`] - A time delay. //! //! ## Chaining animations //! -//! Most tweenables can be chained with the `then()` operator to produce a [`Sequence`] tweenable: +//! Most tweenables can be chained with the `then()` operator to produce a +//! [`Sequence`] tweenable: //! //! ``` //! # use bevy::prelude::*; @@ -108,35 +113,40 @@ //! //! # Animators and lenses //! -//! Bevy components and assets are animated with tweening _animator_ components, which take a tweenable and -//! apply it to another component on the same [`Entity`]. Those animators determine that other component and -//! its fields to animate using a _lens_. +//! Bevy components and assets are animated with tweening _animator_ components, +//! which take a tweenable and apply it to another component on the same +//! [`Entity`]. Those animators determine that other component and its fields to +//! animate using a _lens_. //! //! ## Components animation //! -//! Components are animated with the [`Animator`] component, which is generic over the type of component -//! it animates. This is a restriction imposed by Bevy, to access the animated component as a mutable -//! reference via a [`Query`] and comply with the ECS rules. +//! Components are animated with the [`Animator`] component, which is generic +//! over the type of component it animates. This is a restriction imposed by +//! Bevy, to access the animated component as a mutable reference via a +//! [`Query`] and comply with the ECS rules. //! -//! The [`Animator`] itself is not generic over the subset of fields of the components it animates. -//! This limits the proliferation of generic types when animating e.g. both the position and rotation -//! of an entity. +//! The [`Animator`] itself is not generic over the subset of fields of the +//! components it animates. This limits the proliferation of generic types when +//! animating e.g. both the position and rotation of an entity. //! //! ## Assets animation //! -//! Assets are animated in a similar way to component, via the [`AssetAnimator`] component. +//! Assets are animated in a similar way to component, via the [`AssetAnimator`] +//! component. //! -//! Because assets are typically shared, and the animation applies to the asset itself, all users of the asset -//! see the animation. For example, animating the color of a [`ColorMaterial`] will change the color of all the +//! Because assets are typically shared, and the animation applies to the asset +//! itself, all users of the asset see the animation. For example, animating the +//! color of a [`ColorMaterial`] will change the color of all the //! 2D meshes using that material. //! //! ## Lenses //! -//! Both [`Animator`] and [`AssetAnimator`] access the field(s) to animate via a lens, a type that implements -//! the [`Lens`] trait. +//! Both [`Animator`] and [`AssetAnimator`] access the field(s) to animate via a +//! lens, a type that implements the [`Lens`] trait. //! -//! Several predefined lenses are provided in the [`lens`] module for the most commonly animated fields, like the -//! components of a [`Transform`]. A custom lens can also be created by implementing the trait, allowing to animate +//! Several predefined lenses are provided in the [`lens`] module for the most +//! commonly animated fields, like the components of a [`Transform`]. A custom +//! lens can also be created by implementing the trait, allowing to animate //! virtually any field of any Bevy component or asset. //! //! [`Transform::translation`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html#structfield.translation @@ -150,8 +160,7 @@ use bevy::{asset::Asset, prelude::*}; use std::time::Duration; use interpolation::Ease as IEase; -pub use interpolation::EaseFunction; -pub use interpolation::Lerp; +pub use interpolation::{EaseFunction, Lerp}; pub mod lens; mod plugin; @@ -170,11 +179,13 @@ pub use tweenable::{ pub enum TweeningType { /// Run the animation once from start to end only. Once, - /// Loop the animation indefinitely, restarting from the start each time the end is reached. + /// Loop the animation indefinitely, restarting from the start each time the + /// end is reached. Loop, - /// Loop the animation back and forth, changing direction each time an endpoint is reached. - /// A complete cycle start -> end -> start always counts as 2 loop iterations for the various - /// operations where looping matters. + /// Loop the animation back and forth, changing direction each time an + /// endpoint is reached. A complete cycle start -> end -> start always + /// counts as 2 loop iterations for the various operations where looping + /// matters. PingPong, } @@ -256,17 +267,20 @@ impl From for EaseMethod { /// Direction a tweening animation is playing. /// -/// When playing a tweenable forward, the progress values `0` and `1` are respectively mapped to -/// the start and end bounds of the lens(es) being used. Conversely, when playing backward, this -/// mapping is reversed, such that a progress value of `0` corresponds to the state of the target -/// at the end bound of the lens, while a progress value of `1` corresponds to the state of that -/// target at the start bound of the lens, effectively making the animation play backward. +/// When playing a tweenable forward, the progress values `0` and `1` are +/// respectively mapped to the start and end bounds of the lens(es) being used. +/// Conversely, when playing backward, this mapping is reversed, such that a +/// progress value of `0` corresponds to the state of the target at the end +/// bound of the lens, while a progress value of `1` corresponds to the state of +/// that target at the start bound of the lens, effectively making the animation +/// play backward. /// -/// For all but [`TweeningType::PingPong`] this is always [`TweeningDirection::Forward`], unless -/// manually configured with [`Tween::set_direction()`] in which case the value is constant equal -/// to the value set. For the [`TweeningType::PingPong`] tweening type, this is either forward -/// (from start to end; ping) or backward (from end to start; pong), depending on the current -/// iteration of the loop. +/// For all but [`TweeningType::PingPong`] this is always +/// [`TweeningDirection::Forward`], unless manually configured with +/// [`Tween::set_direction()`] in which case the value is constant equal +/// to the value set. For the [`TweeningType::PingPong`] tweening type, this is +/// either forward (from start to end; ping) or backward (from end to start; +/// pong), depending on the current iteration of the loop. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TweeningDirection { /// Animation playing from start to end. @@ -315,7 +329,8 @@ macro_rules! animator_impl { self } - /// Set the initial speed of the animator. See [`Animator::set_speed`] for details. + /// Set the initial speed of the animator. See [`Animator::set_speed`] for + /// details. #[must_use] pub fn with_speed(mut self, speed: f32) -> Self { self.speed = speed; @@ -324,8 +339,8 @@ macro_rules! animator_impl { /// Set the animation speed. Defaults to 1. /// - /// A speed of 2 means the animation will run twice as fast while a speed of 0.1 will result in - /// a 10x slowed animation. + /// A speed of 2 means the animation will run twice as fast while a speed of 0.1 + /// will result in a 10x slowed animation. pub fn set_speed(&mut self, speed: f32) { self.speed = speed; } @@ -366,21 +381,26 @@ macro_rules! animator_impl { } } - /// Get the current progress in \[0:1\] (non-looping) or \[0:1\[ (looping) of the animation. + /// Get the current progress in \[0:1\] (non-looping) or \[0:1\[ (looping) of + /// the animation. /// - /// For looping animations, this reports the progress of the current iteration, in the current direction: - /// - [`TweeningType::Loop`] is 0 at start and 1 at end. The exact value 1.0 is never reached, - /// since the tweenable loops over to 0.0 immediately. - /// - [`TweeningType::PingPong`] is 0 at the source endpoint and 1 and the destination one, - /// which are respectively the start/end for [`TweeningDirection::Forward`], or the end/start - /// for [`TweeningDirection::Backward`]. The exact value 1.0 is never reached, since the tweenable - /// loops over to 0.0 immediately when it changes direction at either endpoint. + /// For looping animations, this reports the progress of the current iteration, + /// in the current direction: + /// - [`TweeningType::Loop`] is 0 at start and 1 at end. The exact value 1.0 is + /// never reached, since the tweenable loops over to 0.0 immediately. + /// - [`TweeningType::PingPong`] is 0 at the source endpoint and 1 and the + /// destination one, which are respectively the start/end for + /// [`TweeningDirection::Forward`], or the end/start for + /// [`TweeningDirection::Backward`]. The exact value 1.0 is never reached, + /// since the tweenable loops over to 0.0 immediately when it changes + /// direction at either endpoint. /// - /// For sequences, the progress is measured over the entire sequence, from 0 at the start of the first - /// child tweenable to 1 at the end of the last one. + /// For sequences, the progress is measured over the entire sequence, from 0 at + /// the start of the first child tweenable to 1 at the end of the last one. /// - /// For tracks (parallel execution), the progress is measured like a sequence over the longest "path" of - /// child tweenables. In other words, this is the current elapsed time over the total tweenable duration. + /// For tracks (parallel execution), the progress is measured like a sequence + /// over the longest "path" of child tweenables. In other words, this is the + /// current elapsed time over the total tweenable duration. #[must_use] pub fn progress(&self) -> f32 { if let Some(tweenable) = &self.tweenable { @@ -407,7 +427,8 @@ macro_rules! animator_impl { /// Stop animation playback and rewind the animation. /// - /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable. + /// This changes the animator state to [`AnimatorState::Paused`] and rewind its + /// tweenable. pub fn stop(&mut self) { self.state = AnimatorState::Paused; self.rewind(); diff --git a/src/plugin.rs b/src/plugin.rs index 10f0c07..70cbe3e 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -4,24 +4,26 @@ use crate::{Animator, AnimatorState, AssetAnimator, TweenCompleted}; /// Plugin to add systems related to tweening of common components and assets. /// -/// This plugin adds systems for a predefined set of components and assets, to allow their -/// respective animators to be updated each frame: +/// This plugin adds systems for a predefined set of components and assets, to +/// allow their respective animators to be updated each frame: /// - [`Transform`] /// - [`Text`] /// - [`Style`] /// - [`Sprite`] /// - [`ColorMaterial`] /// -/// This ensures that all predefined lenses work as intended, as well as any custom lens -/// animating the same component or asset type. +/// This ensures that all predefined lenses work as intended, as well as any +/// custom lens animating the same component or asset type. /// -/// For other components and assets, including custom ones, the relevant system needs to be -/// added manually by the application: -/// - For components, add [`component_animator_system::`] where `T: Component` +/// For other components and assets, including custom ones, the relevant system +/// needs to be added manually by the application: +/// - For components, add [`component_animator_system::`] where `T: +/// Component` /// - For assets, add [`asset_animator_system::`] where `T: Asset` /// -/// This plugin is entirely optional. If you want more control, you can instead add manually -/// the relevant systems for the exact set of components and assets actually animated. +/// This plugin is entirely optional. If you want more control, you can instead +/// add manually the relevant systems for the exact set of components and assets +/// actually animated. /// /// [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html /// [`Text`]: https://docs.rs/bevy/0.7.0/bevy/text/struct.Text.html @@ -58,8 +60,8 @@ pub enum AnimationSystem { /// Animator system for components. /// -/// This system extracts all components of type `T` with an `Animator` attached to the same entity, -/// and tick the animator to animate the component. +/// This system extracts all components of type `T` with an `Animator` +/// attached to the same entity, and tick the animator to animate the component. pub fn component_animator_system( time: Res