diff --git a/CHANGELOG.md b/CHANGELOG.md index ce467c56fd61253a0b5575f578fcd579318dbe77..3604dc7521e46740603483497b70ab621585c1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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` ## [0.4.0] - 2022-04-16 diff --git a/examples/transform_rotation.rs b/examples/transform_rotation.rs index 6eab8618d1202279a1ebc8e8d713bf3b174877f5..cd100ff868ab0928ae237ca8ec0b257e61e2aee0 100644 --- a/examples/transform_rotation.rs +++ b/examples/transform_rotation.rs @@ -1,7 +1,9 @@ use bevy::prelude::*; +use bevy_inspector_egui::{Inspectable, InspectorPlugin}; + use bevy_tweening::{lens::*, *}; -fn main() -> Result<(), Box<dyn std::error::Error>> { +fn main() { App::default() .insert_resource(WindowDescriptor { title: "TransformRotationLens".to_string(), @@ -12,10 +14,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { }) .add_plugins(DefaultPlugins) .add_plugin(TweeningPlugin) + .add_plugin(InspectorPlugin::<Options>::new()) .add_startup_system(setup) + .add_system(update_animation_speed) .run(); +} - Ok(()) +#[derive(Copy, Clone, PartialEq, Inspectable)] +struct Options { + #[inspectable(min = 0.01, max = 100.)] + speed: f32, +} + +impl Default for Options { + fn default() -> Self { + Self { speed: 1. } + } } fn setup(mut commands: Commands) { @@ -96,3 +110,13 @@ fn setup(mut commands: Commands) { } } } + +fn update_animation_speed(options: Res<Options>, mut animators: Query<&mut Animator<Transform>>) { + if !options.is_changed() { + return; + } + + for mut animator in animators.iter_mut() { + animator.set_speed(options.speed); + } +} diff --git a/examples/transform_translation.rs b/examples/transform_translation.rs index 007373f4e7aaf939e7bbaf83ced98f9116ac2f37..a1e996b7228adb1744f92f4272fd5fa4edda1a91 100644 --- a/examples/transform_translation.rs +++ b/examples/transform_translation.rs @@ -1,7 +1,9 @@ use bevy::prelude::*; +use bevy_inspector_egui::{Inspectable, InspectorPlugin}; + use bevy_tweening::{lens::*, *}; -fn main() -> Result<(), Box<dyn std::error::Error>> { +fn main() { App::default() .insert_resource(WindowDescriptor { title: "TransformPositionLens".to_string(), @@ -12,10 +14,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { }) .add_plugins(DefaultPlugins) .add_plugin(TweeningPlugin) + .add_plugin(InspectorPlugin::<Options>::new()) .add_startup_system(setup) + .add_system(update_animation_speed) .run(); +} - Ok(()) +#[derive(Copy, Clone, PartialEq, Inspectable)] +struct Options { + #[inspectable(min = 0.01, max = 100.)] + speed: f32, +} + +impl Default for Options { + fn default() -> Self { + Self { speed: 1. } + } } fn setup(mut commands: Commands) { @@ -84,3 +98,13 @@ fn setup(mut commands: Commands) { x += size * spacing; } } + +fn update_animation_speed(options: Res<Options>, mut animators: Query<&mut Animator<Transform>>) { + if !options.is_changed() { + return; + } + + for mut animator in animators.iter_mut() { + animator.set_speed(options.speed); + } +} diff --git a/examples/ui_position.rs b/examples/ui_position.rs index 16503cf658fe0d5ea88cd9a6994450b52c0a983c..1778b58eec16463283de0618da4b0a4edeaa7dc3 100644 --- a/examples/ui_position.rs +++ b/examples/ui_position.rs @@ -1,7 +1,9 @@ use bevy::prelude::*; +use bevy_inspector_egui::{Inspectable, InspectorPlugin}; + use bevy_tweening::{lens::*, *}; -fn main() -> Result<(), Box<dyn std::error::Error>> { +fn main() { App::default() .insert_resource(WindowDescriptor { title: "UiPositionLens".to_string(), @@ -12,10 +14,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { }) .add_plugins(DefaultPlugins) .add_plugin(TweeningPlugin) + .add_plugin(InspectorPlugin::<Options>::new()) .add_startup_system(setup) + .add_system(update_animation_speed) .run(); +} - Ok(()) +#[derive(Copy, Clone, PartialEq, Inspectable)] +struct Options { + #[inspectable(min = 0.01, max = 100.)] + speed: f32, +} + +impl Default for Options { + fn default() -> Self { + Self { speed: 1. } + } } fn setup(mut commands: Commands) { @@ -105,3 +119,13 @@ fn setup(mut commands: Commands) { x += offset_x; } } + +fn update_animation_speed(options: Res<Options>, mut animators: Query<&mut Animator<Style>>) { + if !options.is_changed() { + return; + } + + for mut animator in animators.iter_mut() { + animator.set_speed(options.speed); + } +} diff --git a/src/lib.rs b/src/lib.rs index 75624386088df1d65903310cecde4dff2f0bb057..587e67ba8300f4a6b1f24c361b6b86df2927a651 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,7 @@ //! [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html use bevy::{asset::Asset, prelude::*}; +use std::time::Duration; use interpolation::Ease as IEase; pub use interpolation::EaseFunction; @@ -304,6 +305,7 @@ pub struct Animator<T: Component> { /// Control if this animation is played or not. pub state: AnimatorState, tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>, + speed: f32, } impl<T: Component + std::fmt::Debug> std::fmt::Debug for Animator<T> { @@ -319,6 +321,7 @@ impl<T: Component> Default for Animator<T> { Animator { state: Default::default(), tweenable: None, + speed: 1., } } } @@ -338,6 +341,20 @@ impl<T: Component> Animator<T> { self } + /// Set the initial speed of the animator. See [`Animator::set_speed`] for details. + pub fn with_speed(mut self, speed: f32) -> Self { + self.speed = speed; + self + } + + /// 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. + pub fn set_speed(&mut self, speed: f32) { + self.speed = speed; + } + /// Set the top-level tweenable item this animator controls. pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + Send + Sync + 'static) { self.tweenable = Some(Box::new(tween)); @@ -395,6 +412,21 @@ impl<T: Component> Animator<T> { } } + /// Ticks the tween, if present. See [`Tweenable::tick`] for details. + pub fn tick( + &mut self, + delta: Duration, + target: &mut T, + entity: Entity, + event_writer: &mut EventWriter<TweenCompleted>, + ) -> Option<TweenState> { + if let Some(tweenable) = &mut self.tweenable { + Some(tweenable.tick(delta.mul_f32(self.speed), target, entity, event_writer)) + } else { + None + } + } + /// Stop animation playback and rewind the animation. /// /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable. @@ -420,6 +452,7 @@ pub struct AssetAnimator<T: Asset> { pub state: AnimatorState, tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>, handle: Handle<T>, + speed: f32, } impl<T: Asset + std::fmt::Debug> std::fmt::Debug for AssetAnimator<T> { @@ -436,6 +469,7 @@ impl<T: Asset> Default for AssetAnimator<T> { state: Default::default(), tweenable: None, handle: Default::default(), + speed: 1., } } } @@ -456,6 +490,20 @@ impl<T: Asset> AssetAnimator<T> { self } + /// Set the initial speed of the animator. See [`Animator::set_speed`] for details. + pub fn with_speed(mut self, speed: f32) -> Self { + self.speed = speed; + self + } + + /// 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. + pub fn set_speed(&mut self, speed: f32) { + self.speed = speed; + } + /// Set the top-level tweenable item this animator controls. pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + Send + Sync + 'static) { self.tweenable = Some(Box::new(tween)); @@ -513,6 +561,21 @@ impl<T: Asset> AssetAnimator<T> { } } + /// Ticks the tween, if present. See [`Tweenable::tick`] for details. + pub fn tick( + &mut self, + delta: Duration, + target: &mut T, + entity: Entity, + event_writer: &mut EventWriter<TweenCompleted>, + ) -> Option<TweenState> { + if let Some(tweenable) = &mut self.tweenable { + Some(tweenable.tick(delta.mul_f32(self.speed), target, entity, event_writer)) + } else { + None + } + } + /// Stop animation playback and rewind the animation. /// /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable. diff --git a/src/plugin.rs b/src/plugin.rs index 4e68d66fcbbbef8e692dbdb465d254e10601248d..ff98803a91f601e297e5f4015b4b26e91ffb29dc 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -57,9 +57,7 @@ pub fn component_animator_system<T: Component>( ) { for (entity, ref mut target, ref mut animator) in query.iter_mut() { if animator.state != AnimatorState::Paused { - if let Some(tweenable) = animator.tweenable_mut() { - tweenable.tick(time.delta(), target, entity, &mut event_writer); - } + animator.tick(time.delta(), target, entity, &mut event_writer); } } } @@ -76,9 +74,7 @@ pub fn asset_animator_system<T: Asset>( for (entity, ref mut animator) in query.iter_mut() { if animator.state != AnimatorState::Paused { if let Some(target) = assets.get_mut(animator.handle()) { - if let Some(tweenable) = animator.tweenable_mut() { - tweenable.tick(time.delta(), target, entity, &mut event_writer); - } + animator.tick(time.delta(), target, entity, &mut event_writer); } } }