From 454bcd00ef209709b52be8a655bb613e3ba02e7c Mon Sep 17 00:00:00 2001 From: Alex Saveau <saveau.alexandre@gmail.com> Date: Sat, 14 May 2022 12:37:17 -0700 Subject: [PATCH] Add speed modifier (#22) It needs to be built-in because otherwise you have to completely recreate the animation to change the speed. --- CHANGELOG.md | 1 + examples/transform_rotation.rs | 28 +++++++++++++- examples/transform_translation.rs | 28 +++++++++++++- examples/ui_position.rs | 28 +++++++++++++- src/lib.rs | 63 +++++++++++++++++++++++++++++++ src/plugin.rs | 8 +--- 6 files changed, 144 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce467c5..3604dc7 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 6eab861..cd100ff 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 007373f..a1e996b 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 16503cf..1778b58 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 7562438..587e67b 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 4e68d66..ff98803 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); } } } -- GitLab