Skip to content
Snippets Groups Projects
Unverified Commit 6a871576 authored by Alex Saveau's avatar Alex Saveau Committed by GitHub
Browse files

Support running an animation N times (#19)

Remove `TweeningType` and split its functionalities between a new `RepeatCount`
controlling the number of repeats of an animation on one hand, and
`RepeatStrategy` controlling the way an animation restarts after a loop ended
on the other hand. This allows more granular control on the type of playback.

Remove the `tweening_type` parameter from `Tween<T>::new()` and replace it with
builder methods `with_repeat_count()` and `with_repeat_strategy()`.

Remove `is_looping()` from all tweenables, which was not implemented for most
of them anyway.
parent fd86ec51
No related branches found
No related tags found
No related merge requests found
......@@ -7,16 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add `is_forward()` and `is_backward()` convenience helpers to `TweeningDirection`.
- Add `Tween::set_direction()` and `Tween::with_direction()` which allow configuring the playback direction of a tween, allowing to play it backward from end to start.
- Support dynamically changing an animation's speed with `Animator::set_speed`
- Add `AnimationSystem` label to tweening tick systems
- A `BoxedTweenable` type to make working with `Box<dyn Tweenable + ...>` easier
- Added `is_forward()` and `is_backward()` convenience helpers to `TweeningDirection`.
- Added `Tween::set_direction()` and `Tween::with_direction()` which allow configuring the playback direction of a tween, allowing to play it backward from end to start.
- Added support for dynamically changing an animation's speed with `Animator::set_speed`.
- Added `AnimationSystem` label to tweening tick systems.
- Added `BoxedTweenable` type to make working with `Box<dyn Tweenable + ...>` easier.
- Added `RepeatCount` and `RepeatStrategy` for more granular control over animation looping.
- Added `with_repeat_count()` and `with_repeat_strategy()` builder methods to `Tween<T>`.
### Changed
- Double boxing in `Sequence` and `Tracks` was fixed. As a result, any custom tweenables
- Double boxing in `Sequence` and `Tracks` was fixed. As a result, any custom tweenables.
should implement `From` for `BoxedTweenable` to make those APIs easier to use.
- Removed the `tweening_type` parameter from the signature of `Tween<T>::new()`; use `with_repeat_count()` and `with_repeat_strategy()` instead.
### Removed
- Removed `Tweenable::is_looping()`, which was not implemented for most tweenables.
- Removed `TweeningType` in favor of `RepeatCount` and `RepeatStrategy`.
## [0.4.0] - 2022-04-16
......
......@@ -77,13 +77,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::MirroredRepeat);
commands
.spawn_bundle(MaterialMesh2dBundle {
......
......@@ -50,7 +50,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
start_time_ms += 500;
let tween_scale = Tween::new(
EaseFunction::BounceOut,
TweeningType::Once,
Duration::from_secs(2),
TransformScaleLens {
start: Vec3::splat(0.01),
......
use std::time::Duration;
use bevy::prelude::*;
use bevy_tweening::{lens::*, *};
use std::time::Duration;
fn main() {
App::default()
......@@ -107,19 +109,31 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Vec3::new(margin, screen_y - margin, 0.),
Vec3::new(margin, margin, 0.),
];
// Build a sequence from an iterator over a Tweenable (here, a Tween<Transform>)
// Build a sequence from an iterator over a Tweenable (here, a
// Tracks<Transform>)
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,
},
)
// Get an event after each segment
.with_completed_event(index as u64)
Tracks::new([
Tween::new(
EaseFunction::QuadraticInOut,
Duration::from_millis(250),
TransformRotateZLens {
start: 0.,
end: 180_f32.to_radians(),
},
)
.with_repeat_count(RepeatCount::Finite(4))
.with_repeat_strategy(RepeatStrategy::MirroredRepeat),
Tween::new(
EaseFunction::QuadraticInOut,
Duration::from_secs(1),
TransformPositionLens {
start: pair[0] - center,
end: pair[1] - center,
},
)
// Get an event after each segment
.with_completed_event(index as u64),
])
}));
commands
......@@ -138,7 +152,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// scaling 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.),
......@@ -148,7 +161,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_completed_event(99); // Get an event once move completed
let tween_rotate = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_secs(1),
TransformRotationLens {
start: Quat::IDENTITY,
......@@ -157,7 +169,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
);
let tween_scale = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_secs(1),
TransformScaleLens {
start: Vec3::ONE,
......
......@@ -61,13 +61,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::MirroredRepeat);
commands
.spawn_bundle(SpriteBundle {
......
......@@ -68,14 +68,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
] {
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::MirroredRepeat);
commands
.spawn_bundle(TextBundle {
......
......@@ -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::MirroredRepeat);
commands
.spawn_bundle((
......
......@@ -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::MirroredRepeat);
commands
.spawn_bundle(SpriteBundle {
......
......@@ -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::MirroredRepeat);
commands
.spawn_bundle(NodeBundle {
......
......@@ -44,10 +44,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
......@@ -90,7 +87,6 @@
//! let tween1 = Tween::new(
//! // [...]
//! # EaseFunction::BounceOut,
//! # TweeningType::Once,
//! # Duration::from_secs(2),
//! # TransformScaleLens {
//! # start: Vec3::ZERO,
......@@ -100,7 +96,6 @@
//! let tween2 = Tween::new(
//! // [...]
//! # EaseFunction::QuadraticInOut,
//! # TweeningType::Once,
//! # Duration::from_secs(1),
//! # TransformPositionLens {
//! # start: Vec3::ZERO,
......@@ -156,16 +151,12 @@
//! [`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, Lerp};
pub mod lens;
mod plugin;
mod tweenable;
pub use lens::Lens;
pub use plugin::{
asset_animator_system, component_animator_system, AnimationSystem, TweeningPlugin,
......@@ -174,24 +165,47 @@ pub use tweenable::{
BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable,
};
/// Type of looping for a tween animation.
pub mod lens;
mod plugin;
mod tweenable;
/// How many times to repeat a tween animation. See also: [`RepeatStrategy`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepeatCount {
/// Run the animation N times.
Finite(u32),
/// Run the animation for some amount of time.
For(Duration),
/// Loop the animation indefinitely.
Infinite,
}
/// What to do when a tween animation needs to be repeated.
///
/// Only applicable when [`RepeatCount`] is greater than the animation duration.
#[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. A complete cycle start -> end -> start always
/// counts as 2 loop iterations for the various operations where looping
/// matters.
PingPong,
pub enum RepeatStrategy {
/// Reset the animation back to its starting position.
Repeat,
/// 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. That
/// is, a 1 second animation will take 2 seconds to end up back where it
/// started.
MirroredRepeat,
}
impl Default for TweeningType {
impl Default for RepeatCount {
fn default() -> Self {
Self::Once
Self::Finite(1)
}
}
impl Default for RepeatStrategy {
fn default() -> Self {
Self::Repeat
}
}
......@@ -275,12 +289,12 @@ impl From<EaseFunction> for EaseMethod {
/// that target at the start bound of the lens, effectively making the animation
/// play backward.
///
/// For all but [`TweeningType::PingPong`] this is always
/// For all but [`RepeatStrategy::MirroredRepeat`] 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.
/// [`Tween::set_direction()`] in which case the value is constant equal to the
/// value set. When using [`RepeatStrategy::MirroredRepeat`], 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.
......@@ -381,19 +395,8 @@ macro_rules! animator_impl {
}
}
/// 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.
/// Get the current progress of the tweenable. See [`Tweenable::progress`] for
/// details.
///
/// 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.
......@@ -535,9 +538,10 @@ impl<T: Asset> AssetAnimator<T> {
#[cfg(test)]
mod tests {
use super::{lens::*, *};
use bevy::reflect::TypeUuid;
use super::{lens::*, *};
struct DummyLens {
start: f32,
end: f32,
......@@ -567,9 +571,15 @@ mod tests {
}
#[test]
fn tweening_type() {
let tweening_type = TweeningType::default();
assert_eq!(tweening_type, TweeningType::Once);
fn repeat_count() {
let count = RepeatCount::default();
assert_eq!(count, RepeatCount::Finite(1));
}
#[test]
fn repeat_strategy() {
let strategy = RepeatStrategy::default();
assert_eq!(strategy, RepeatStrategy::Repeat);
}
#[test]
......@@ -619,7 +629,6 @@ mod tests {
fn animator_new() {
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -635,7 +644,6 @@ mod tests {
for state in [AnimatorState::Playing, AnimatorState::Paused] {
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -653,7 +661,6 @@ mod tests {
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -667,7 +674,6 @@ mod tests {
fn animator_controls() {
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -706,7 +712,6 @@ mod tests {
fn asset_animator_new() {
let tween = Tween::<DummyAsset>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -722,7 +727,6 @@ mod tests {
for state in [AnimatorState::Playing, AnimatorState::Paused] {
let tween = Tween::<DummyAsset>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -742,7 +746,6 @@ mod tests {
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......@@ -757,7 +760,6 @@ mod tests {
fn asset_animator_controls() {
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment