bevy_tweening/lib.rs
1#![deny(
2 warnings,
3 missing_copy_implementations,
4 trivial_casts,
5 trivial_numeric_casts,
6 unsafe_code,
7 unstable_features,
8 unused_import_braces,
9 unused_qualifications,
10 missing_docs
11)]
12
13//! Tweening animation plugin for the Bevy game engine.
14//!
15//! 🍃 Bevy Tweening provides interpolation-based animation between ("tweening")
16//! two values, to animate any field of any component, resource, or asset,
17//! including both built-in Bevy ones and custom user-defined ones. Each field
18//! of a component, resource, or asset, can be animated via a collection of
19//! predefined easing functions, or providing a custom animation curve. The
20//! library supports any number of animations queued in parallel, even on the
21//! same component, resource, or asset type, and allows runtime control over
22//! playback and animation speed.
23//!
24//! # Quick start
25//!
26//! Look at the documentation for:
27//! - [`Tween`] -- the description of a tweening animation and the explanation
28//! of some core concepts
29//! - [`TweenAnim`] -- the component representing the runtime animation
30//! - [`AnimTarget`] -- the component defining the target that the animation
31//! mutates
32//! - [`TweeningPlugin`] -- the plugin to add to your app
33//! - [`EntityCommandsTweeningExtensions`] -- the simplest way to spawn
34//! animations; this is an extension trait for [`EntityCommands`], the type
35//! [`Commands::spawn()`] returns.
36//!
37//! # Example
38//!
39//! Add the [`TweeningPlugin`] to your app:
40//!
41//! ```no_run
42//! use bevy::prelude::*;
43//! use bevy_tweening::*;
44//!
45//! App::default()
46//! .add_plugins(DefaultPlugins)
47//! .add_plugins(TweeningPlugin)
48//! .run();
49//! ```
50//!
51//! Animate the position ([`Transform::translation`]) of an [`Entity`]:
52//!
53//! ```
54//! # use bevy::prelude::*;
55//! # use bevy_tweening::{lens::*, *};
56//! # use std::time::Duration;
57//! # fn system(mut commands: Commands) {
58//! // Create a single animation (tween) to move an entity.
59//! let tween = Tween::new(
60//! // Use a quadratic easing on both endpoints.
61//! EaseFunction::QuadraticInOut,
62//! // It takes 1 second to go from start to end points.
63//! Duration::from_secs(1),
64//! // The lens gives access to the Transform component of the Entity,
65//! // for the TweenAnimator to animate it. It also contains the start and
66//! // end values respectively associated with the progress ratios 0. and 1.
67//! TransformPositionLens {
68//! start: Vec3::ZERO,
69//! end: Vec3::new(1., 2., -4.),
70//! },
71//! );
72//!
73//! // Spawn an entity to animate the position of.
74//! commands.spawn((
75//! Transform::default(),
76//! // Create a tweenable animation targetting the current entity. Without AnimTarget,
77//! // the target is implicitly a component on this same entity. The exact component
78//! // type is derived from the type of the Lens used by the Tweenable itself.
79//! TweenAnim::new(tween),
80//! ));
81//! # }
82//! ```
83//!
84//! If the target of the animation is not a component on the current entity,
85//! then an [`AnimTarget`] component is necessary to specify that target. Note
86//! that **[`AnimTarget`] is always mandatory for resource and asset
87//! animations**.
88//!
89//! ```
90//! # use bevy::prelude::*;
91//! # use bevy_tweening::{lens::*, *};
92//! # use std::time::Duration;
93//! # fn make_tween<R: Resource>() -> Tween { unimplemented!() }
94//! #[derive(Resource)]
95//! struct MyResource;
96//!
97//! # fn system(mut commands: Commands) {
98//! // Create a single animation (tween) to animate a resource.
99//! let tween = make_tween::<MyResource>();
100//!
101//! // Spawn an entity to own the resource animation.
102//! commands.spawn((
103//! TweenAnim::new(tween),
104//! // The AnimTarget is necessary here:
105//! AnimTarget::resource::<MyResource>(),
106//! ));
107//! # }
108//! ```
109//!
110//! This example shows the general pattern to add animations for any component,
111//! resource, or asset. Since moving the position of an object is a very common
112//! task, 🍃 Bevy Tweening provides a shortcut for it. The above example can be
113//! rewritten more concicely as:
114//!
115//! ```
116//! # use bevy::prelude::*;
117//! # use bevy_tweening::{lens::*, *};
118//! # use std::time::Duration;
119//! # fn system(mut commands: Commands) {
120//! commands
121//! // Spawn an entity to animate the position of.
122//! .spawn((Transform::default(),))
123//! // Create a new Transform::translation animation
124//! .move_to(
125//! Vec3::new(1., 2., -4.),
126//! Duration::from_secs(1),
127//! EaseFunction::QuadraticInOut,
128//! );
129//! # }
130//! ```
131//!
132//! The [`move_to()`] extension is convenient helper for animations, which
133//! creates a [`Tween`] that animates the [`Transform::translation`]. It has the
134//! added benefit that the starting point is automatically read from the
135//! component itself; you only need to specify the end position. See the
136//! [`EntityCommandsTweeningExtensions`] extension trait defining helpers for
137//! other common animations.
138//!
139//! # Ready to animate
140//!
141//! Unlike previous versions of 🍃 Bevy Tweening, **you don't need any
142//! particular system setup** aside from adding the [`TweeningPlugin`] to your
143//! [`App`]. In particular, per-component-type and per-asset-type systems are
144//! gone. Instead, the plugin adds a _single_ system executing during the
145//! [`Update`] schedule, which calls [`TweenAnim::step_all()`]. Each
146//! [`TweenAnim`] acts as a controller for one animation, and mutates its
147//! target.
148//!
149//! # Tweenables
150//!
151//! 🍃 Bevy Tweening supports several types of _tweenables_, building blocks
152//! that can be combined to form complex animations. A tweenable is a type
153//! implementing the [`Tweenable`] trait.
154//!
155//! - [`Tween`] - A simple tween (easing) animation between two values.
156//! - [`Sequence`] - A series of tweenables executing in series, one after the
157//! other.
158//! - [`Delay`] - A time delay. This doesn't animate anything.
159//!
160//! To execute multiple animations in parallel (like the `Tracks` tweenable used
161//! to do in older versions of 🍃 Bevy Tweening; it's now removed), simply
162//! enqueue each animation independently. This require careful selection of
163//! individual timings though if you want to synchronize those animations.
164//!
165//! ## Chaining animations
166//!
167//! Most tweenables can be chained with the `then()` operator to produce a
168//! [`Sequence`] tweenable:
169//!
170//! ```
171//! # use bevy::prelude::*;
172//! # use bevy_tweening::{lens::*, *};
173//! # use std::time::Duration;
174//! let tween1 = Tween::new(
175//! // [...]
176//! # EaseFunction::BounceOut,
177//! # Duration::from_secs(2),
178//! # TransformScaleLens {
179//! # start: Vec3::ZERO,
180//! # end: Vec3::ONE,
181//! # },
182//! );
183//! let tween2 = Tween::new(
184//! // [...]
185//! # EaseFunction::QuadraticInOut,
186//! # Duration::from_secs(1),
187//! # TransformPositionLens {
188//! # start: Vec3::ZERO,
189//! # end: Vec3::new(3.5, 0., 0.),
190//! # },
191//! );
192//! // Produce a Sequence executing first 'tween1' then 'tween2'
193//! let seq = tween1.then(tween2);
194//! ```
195//!
196//! Note that some tweenable animations can be of infinite duration; this is the
197//! case for example when using [`RepeatCount::Infinite`]. If you add such an
198//! infinite animation in a sequence, and append more tweenables after it,
199//! **those tweenables will never play** because playback will be stuck forever
200//! repeating the first animation. You're responsible for creating sequences
201//! that make sense. In general, only use infinite tweenable animations alone or
202//! as the last element of a sequence (for example, move to position and then
203//! rotate forever on self).
204//!
205//! # `TweenAnim`
206//!
207//! Bevy components, resources, and assets, are animated with the [`TweenAnim`]
208//! component. This component acts as a controller for a single animation. It
209//! determines the target component, resource, or asset, to animate, via an
210//! [`AnimTarget`], and accesses the field(s) of that target using a [`Lens`].
211//!
212//! - Components are animated via the [`AnimTargetKind::Component`], which
213//! identifies a component instance on an entity via the [`Entity`] itself. If
214//! that target entity is the same as the one owning the [`TweenAnim`], then
215//! the [`AnimTarget`] can be omitted, for convenience.
216//! - Resources are animated via the [`AnimTargetKind::Resource`].
217//! - Assets are animated via the [`AnimTargetKind::Asset`] which identifies an
218//! asset via the type of its [`Assets`] collection (and so indirectly the
219//! type of asset itself) and the [`AssetId`] referencing that asset inside
220//! that collection.
221//!
222//! Because assets are typically shared, and the animation applies to the asset
223//! itself, all users of the asset see the animation. For example, animating the
224//! color of a [`ColorMaterial`] will change the color of all the 2D meshes
225//! using that material. If you want to animate the color of a single mesh, you
226//! need to duplicate the asset and assign a unique copy to that mesh,
227//! then animate that copy alone.
228//!
229//! After that, you can use the [`TweenAnim`] component to control the animation
230//! playback:
231//!
232//! ```no_run
233//! # use bevy::prelude::Single;
234//! # use bevy_tweening::*;
235//! fn my_system(mut anim: Single<&mut TweenAnim>) {
236//! anim.speed = 0.8; // 80% playback speed
237//! }
238//! ```
239//!
240//! ## Lenses
241//!
242//! The [`AnimTarget`] references the target (component, resource, or asset)
243//! being animated. However, only a part of that target is generally animated.
244//! To that end, the [`TweenAnim`] (or, more exactly, the [`Tweenable`] it uses)
245//! accesses the field(s) to animate via a _lens_, a type that implements the
246//! [`Lens`] trait and allows mapping a target to the actual value(s) animated.
247//!
248//! For example, the [`TransformPositionLens`] uses a [`Transform`] component as
249//! input, and animates its [`Transform::translation`] field only, leaving the
250//! rotation and scale unchanged.
251//!
252//! ```no_run
253//! # use bevy::{prelude::{Transform, Vec3}, ecs::change_detection::Mut};
254//! # use bevy_tweening::Lens;
255//! # struct TransformPositionLens { start: Vec3, end: Vec3 };
256//! impl Lens<Transform> for TransformPositionLens {
257//! fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
258//! target.translation = self.start.lerp(self.end, ratio);
259//! }
260//! }
261//! ```
262//!
263//! Several built-in lenses are provided in the [`lens`] module for the most
264//! commonly animated fields, like the components of a [`Transform`]. Those are
265//! provided for convenience and mainly as examples. 🍃 Bevy Tweening expects
266//! you to write your own lenses by implementing the [`Lens`] trait, which as
267//! you can see above is very simple. This allows animating virtually any field
268//! of any component, resource, or asset, whether shipped with Bevy or defined
269//! by the user.
270//!
271//! # Tweening vs. keyframed animation
272//!
273//! 🍃 Bevy Tweening is a "tweening" animation library. It focuses on simple
274//! animations often used in applications and games to breathe life into a user
275//! interface or the objects of a game world. The API design favors simplicity,
276//! often for quick one-shot animations created from code. This type of
277//! animation is inherently simpler than a full-blown animation solution, like
278//! `bevy_animation`, which typically works with complex keyframe-based
279//! animation curves authored via Digital Content Creation (DCC) tools like 3D
280//! modellers and exported as assets, and whose most common usage is skeletal
281//! animation of characters. There's a grey area between those two approaches,
282//! and you can use both to achieve most animations, but 🍃 Bevy Tweening will
283//! shine for simpler animations while `bevy_animation` while offer a more
284//! extensive support for larger, more complex ones.
285//!
286//! [`Transform::translation`]: https://docs.rs/bevy/0.18/bevy/transform/components/struct.Transform.html#structfield.translation
287//! [`Entity`]: https://docs.rs/bevy/0.18/bevy/ecs/entity/struct.Entity.html
288//! [`ColorMaterial`]: https://docs.rs/bevy/0.18/bevy/sprite/struct.ColorMaterial.html
289//! [`Transform`]: https://docs.rs/bevy/0.18/bevy/transform/components/struct.Transform.html
290//! [`TransformPositionLens`]: crate::lens::TransformPositionLens
291//! [`move_to()`]: crate::EntityCommandsTweeningExtensions::move_to
292
293use std::{
294 any::TypeId,
295 ops::{Deref, DerefMut},
296 time::Duration,
297};
298
299use bevy::{
300 asset::UntypedAssetId,
301 ecs::{
302 change_detection::MutUntyped,
303 component::{ComponentId, Components, Mutable},
304 },
305 platform::collections::HashMap,
306 prelude::*,
307};
308pub use lens::Lens;
309use lens::{
310 TransformRotateAdditiveXLens, TransformRotateAdditiveYLens, TransformRotateAdditiveZLens,
311};
312pub use plugin::{AnimationSystem, TweeningPlugin};
313use thiserror::Error;
314pub use tweenable::{
315 BoxedTweenable, CycleCompletedEvent, Delay, IntoBoxedTweenable, Sequence, TotalDuration, Tween,
316 TweenState, Tweenable,
317};
318
319use crate::{
320 lens::{TransformPositionLens, TransformScaleLens},
321 tweenable::TweenConfig,
322};
323
324pub mod lens;
325mod plugin;
326mod tweenable;
327
328#[cfg(test)]
329mod test_utils;
330
331/// How many times to repeat a tweenable animation.
332///
333/// See also [`RepeatStrategy`].
334///
335/// Default: `Finite(1)`
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub enum RepeatCount {
338 /// Run the animation an exact number of times.
339 ///
340 /// The total playback duration is the tweenable animation duration times
341 /// this number of iterations.
342 Finite(u32),
343 /// Run the animation for some duration.
344 ///
345 /// If this duration is not a multiple of the tweenable animation duration,
346 /// then the animation will get stopped midway through its playback,
347 /// possibly even before finishing a single loop.
348 For(Duration),
349 /// Loop the animation indefinitely.
350 Infinite,
351}
352
353impl Default for RepeatCount {
354 fn default() -> Self {
355 Self::Finite(1)
356 }
357}
358
359impl From<u32> for RepeatCount {
360 fn from(value: u32) -> Self {
361 Self::Finite(value)
362 }
363}
364
365impl From<Duration> for RepeatCount {
366 fn from(value: Duration) -> Self {
367 Self::For(value)
368 }
369}
370
371impl RepeatCount {
372 /// Calculate the total duration for this repeat count.
373 pub fn total_duration(&self, cycle_duration: Duration) -> TotalDuration {
374 match self {
375 RepeatCount::Finite(count) => TotalDuration::Finite(cycle_duration * *count),
376 RepeatCount::For(duration) => TotalDuration::Finite(*duration),
377 RepeatCount::Infinite => TotalDuration::Infinite,
378 }
379 }
380}
381
382/// Repeat strategy for animation cycles.
383///
384/// Only applicable when [`RepeatCount`] is greater than the total duration of
385/// the tweenable animation.
386///
387/// Default: `Repeat`.
388#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
389pub enum RepeatStrategy {
390 /// Reset the cycle back to its starting position.
391 ///
392 /// When playback reaches the end of the animation cycle, it jumps directly
393 /// back to the cycle start. This can create discontinuities if the
394 /// animation is not authored to be looping.
395 #[default]
396 Repeat,
397
398 /// Follow a ping-pong pattern, changing the cycle direction each time an
399 /// endpoint is reached.
400 ///
401 /// A complete loop start -> end -> start always counts as 2 cycles for the
402 /// various operations where cycle count matters. That is, an animation with
403 /// a 1-second cycle and a mirrored repeat strategy will take 2 seconds
404 /// to end up back in the state where it started.
405 ///
406 /// This strategy ensures that there's no discontinuity in the animation,
407 /// since there's no jump.
408 MirroredRepeat,
409}
410
411/// Playback state of a [`TweenAnim`].
412///
413/// Default: `Playing`.
414#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
415pub enum PlaybackState {
416 /// The animation is playing. This is the default state.
417 #[default]
418 Playing,
419 /// The animation is paused in its current state.
420 Paused,
421}
422
423impl std::ops::Not for PlaybackState {
424 type Output = Self;
425
426 fn not(self) -> Self::Output {
427 match self {
428 Self::Paused => Self::Playing,
429 Self::Playing => Self::Paused,
430 }
431 }
432}
433
434/// Describe how eased value should be computed.
435///
436/// This function is applied to the cycle fraction `t` representing the playback
437/// position over the cycle duration. The result is used to interpolate the
438/// animation target.
439///
440/// In general a [`Lens`] should perform a linear interpolation over its target,
441/// and the non-linear behavior (for example, bounciness, etc.) comes from this
442/// function. This ensures the same [`Lens`] can be reused in multiple contexts,
443/// while the "shape" of the animation is controlled independently.
444///
445/// Default: `EaseFunction::Linear`.
446#[derive(Debug, Clone, Copy)]
447pub enum EaseMethod {
448 /// Follow [`EaseFunction`].
449 EaseFunction(EaseFunction),
450 /// Discrete interpolation. The eased value will jump from start to end when
451 /// stepping over the discrete limit, which must be value between 0 and 1.
452 Discrete(f32),
453 /// Use a custom function to interpolate the value. The function is called
454 /// with the cycle ratio, in `[0:1]`, as parameter, and must return the
455 /// easing factor, typically also in `[0:1]`. Note that values outside this
456 /// unit range may not work well with some animations; for example if
457 /// animating a color, a negative red values have no meaning.
458 CustomFunction(fn(f32) -> f32),
459}
460
461impl EaseMethod {
462 #[must_use]
463 fn sample(self, x: f32) -> f32 {
464 match self {
465 Self::EaseFunction(function) => EasingCurve::new(0.0, 1.0, function).sample(x).unwrap(),
466 Self::Discrete(limit) => {
467 if x > limit {
468 1.
469 } else {
470 0.
471 }
472 }
473 Self::CustomFunction(function) => function(x),
474 }
475 }
476}
477
478impl Default for EaseMethod {
479 fn default() -> Self {
480 Self::EaseFunction(EaseFunction::Linear)
481 }
482}
483
484impl From<EaseFunction> for EaseMethod {
485 fn from(ease_function: EaseFunction) -> Self {
486 Self::EaseFunction(ease_function)
487 }
488}
489
490/// Direction a tweening animation is playing.
491///
492/// The playback direction determines if the delta animation time passed to
493/// [`Tweenable::step()`] is added or subtracted to the current time position on
494/// the animation's timeline.
495/// - In `Forward` direction, time passes forward from `t=0` to the total
496/// duration of the animation.
497/// - Conversely, in `Backward` direction, time passes backward from the total
498/// duration back to `t=0`.
499///
500/// Note that backward playback is supported for infinite animations (when the
501/// repeat count is [`RepeatCount::Infinite`]), but [`Tweenable::rewind()`] is
502/// not supported and will panic.
503///
504/// Default: `Forward`.
505#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
506pub enum PlaybackDirection {
507 /// Animation playing from start to end.
508 #[default]
509 Forward,
510 /// Animation playing from end to start, in reverse.
511 Backward,
512}
513
514impl PlaybackDirection {
515 /// Is the direction equal to [`PlaybackDirection::Forward`]?
516 #[must_use]
517 pub fn is_forward(&self) -> bool {
518 *self == Self::Forward
519 }
520
521 /// Is the direction equal to [`PlaybackDirection::Backward`]?
522 #[must_use]
523 pub fn is_backward(&self) -> bool {
524 *self == Self::Backward
525 }
526}
527
528impl std::ops::Not for PlaybackDirection {
529 type Output = Self;
530
531 fn not(self) -> Self::Output {
532 match self {
533 Self::Forward => Self::Backward,
534 Self::Backward => Self::Forward,
535 }
536 }
537}
538
539/// Extension trait for [`EntityCommands`], adding animation functionalities for
540/// commonly used tweening animations.
541///
542/// This trait extends [`EntityCommands`] to provide convenience helpers to
543/// common tweening animations like moving the position of an entity by
544/// animating its [`Transform::translation`].
545///
546/// One of the major source of convenience provided by these helpers is the fact
547/// that some of the data necessary to create the tween animation is
548/// automatically derived from the current value of the component at the time
549/// when the command is processed. For example, the [`move_to()`] helper only
550/// requires specifying the end position, and will automatically read the start
551/// position from the current [`Transform::translation`] value. This avoids
552/// having to explicitly access that component to read that value and manually
553/// store it into a [`Lens`].
554///
555/// [`move_to()`]: Self::move_to
556pub trait EntityCommandsTweeningExtensions<'a> {
557 /// Queue a new tween animation to move the current entity.
558 ///
559 /// The entity must have a [`Transform`] component. The tween animation will
560 /// be initialized with the current [`Transform::translation`] as its
561 /// starting point, and the given endpoint, duration, and ease method.
562 ///
563 /// Note that the starting point position is saved when the command is
564 /// applied, generally after the current system when [`apply_deferred()`]
565 /// runs. So any change to [`Transform::translation`] between this call and
566 /// [`apply_deferred()`] will be taken into account.
567 ///
568 /// This function is a fire-and-forget convenience helper, and doesn't give
569 /// access to the [`Entity`] created. To retrieve the entity and control
570 /// the animation playback, you should spawn a [`TweenAnim`] component
571 /// manually.
572 ///
573 /// # Example
574 ///
575 /// ```
576 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
577 /// # use bevy_tweening::*;
578 /// # use std::time::Duration;
579 /// # let mut queue = CommandQueue::default();
580 /// # let mut world = World::default();
581 /// # let mut commands = Commands::new(&mut queue, &mut world);
582 /// commands.spawn(Transform::default()).move_to(
583 /// Vec3::new(3.5, 0., 0.),
584 /// Duration::from_secs(1),
585 /// EaseFunction::QuadraticIn,
586 /// );
587 /// ```
588 ///
589 /// [`apply_deferred()`]: bevy::ecs::system::System::apply_deferred
590 fn move_to(
591 self,
592 end: Vec3,
593 duration: Duration,
594 ease_method: impl Into<EaseMethod>,
595 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
596
597 /// Queue a new tween animation to move the current entity.
598 ///
599 /// The entity must have a [`Transform`] component. The tween animation will
600 /// be initialized with the current [`Transform::translation`] as its
601 /// ending point, and the given starting point, duration, and ease method.
602 ///
603 /// Note that the ending point position is saved when the command is
604 /// applied, generally after the current system when [`apply_deferred()`]
605 /// runs. So any change to [`Transform::translation`] between this call and
606 /// [`apply_deferred()`] will be taken into account.
607 ///
608 /// This function is a fire-and-forget convenience helper, and doesn't give
609 /// access to the [`Entity`] created. To retrieve the entity and control
610 /// the animation playback, you should spawn a [`TweenAnim`] component
611 /// manually.
612 ///
613 /// # Example
614 ///
615 /// ```
616 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
617 /// # use bevy_tweening::*;
618 /// # use std::time::Duration;
619 /// # let mut queue = CommandQueue::default();
620 /// # let mut world = World::default();
621 /// # let mut commands = Commands::new(&mut queue, &mut world);
622 /// commands.spawn(Transform::default()).move_from(
623 /// Vec3::new(3.5, 0., 0.),
624 /// Duration::from_secs(1),
625 /// EaseFunction::QuadraticIn,
626 /// );
627 /// ```
628 ///
629 /// [`apply_deferred()`]: bevy::ecs::system::System::apply_deferred
630 fn move_from(
631 self,
632 start: Vec3,
633 duration: Duration,
634 ease_method: impl Into<EaseMethod>,
635 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
636
637 /// Queue a new tween animation to scale the current entity.
638 ///
639 /// The entity must have a [`Transform`] component. The tween animation will
640 /// be initialized with the current [`Transform::scale`] as its starting
641 /// point, and the given endpoint, duration, and ease method.
642 ///
643 /// Note that the starting point scale is saved when the command is applied,
644 /// generally after the current system when [`apply_deferred()`]
645 /// runs. So any change to [`Transform::scale`] between this call and
646 /// [`apply_deferred()`] will be taken into account.
647 ///
648 /// This function is a fire-and-forget convenience helper, and doesn't give
649 /// access to the [`Entity`] created. To retrieve the entity and control
650 /// the animation playback, you should spawn a [`TweenAnim`] component
651 /// manually.
652 ///
653 /// # Example
654 ///
655 /// ```
656 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
657 /// # use bevy_tweening::*;
658 /// # use std::time::Duration;
659 /// # let mut queue = CommandQueue::default();
660 /// # let mut world = World::default();
661 /// # let mut commands = Commands::new(&mut queue, &mut world);
662 /// commands.spawn(Transform::default()).scale_to(
663 /// Vec3::splat(2.), // 200% size
664 /// Duration::from_secs(1),
665 /// EaseFunction::QuadraticIn,
666 /// );
667 /// ```
668 ///
669 /// [`apply_deferred()`]: bevy::ecs::system::System::apply_deferred
670 fn scale_to(
671 self,
672 end: Vec3,
673 duration: Duration,
674 ease_method: impl Into<EaseMethod>,
675 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
676
677 /// Queue a new tween animation to scale the current entity.
678 ///
679 /// The entity must have a [`Transform`] component. The tween animation will
680 /// be initialized with the current [`Transform::scale`] as its ending
681 /// point, and the given start scale, duration, and ease method.
682 ///
683 /// Note that the ending point scale is saved when the command is applied,
684 /// generally after the current system when [`apply_deferred()`]
685 /// runs. So any change to [`Transform::scale`] between this call and
686 /// [`apply_deferred()`] will be taken into account.
687 ///
688 /// This function is a fire-and-forget convenience helper, and doesn't give
689 /// access to the [`Entity`] created. To retrieve the entity and control
690 /// the animation playback, you should spawn a [`TweenAnim`] component
691 /// manually.
692 ///
693 /// # Example
694 ///
695 /// ```
696 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
697 /// # use bevy_tweening::*;
698 /// # use std::time::Duration;
699 /// # let mut queue = CommandQueue::default();
700 /// # let mut world = World::default();
701 /// # let mut commands = Commands::new(&mut queue, &mut world);
702 /// commands.spawn(Transform::default()).scale_from(
703 /// Vec3::splat(0.8), // 80% size
704 /// Duration::from_secs(1),
705 /// EaseFunction::QuadraticIn,
706 /// );
707 /// ```
708 ///
709 /// [`apply_deferred()`]: bevy::ecs::system::System::apply_deferred
710 fn scale_from(
711 self,
712 start: Vec3,
713 duration: Duration,
714 ease_method: impl Into<EaseMethod>,
715 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
716
717 /// Queue a new tween animation to rotate the current entity around its X
718 /// axis continuously (repeats forever, linearly).
719 ///
720 /// The entity must have a [`Transform`] component.
721 ///
722 /// This function is a fire-and-forget convenience helper, and doesn't give
723 /// access to the [`Entity`] created. To retrieve the entity and control
724 /// the animation playback, you should spawn a [`TweenAnim`] component
725 /// manually.
726 ///
727 /// # Example
728 ///
729 /// ```
730 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
731 /// # use bevy_tweening::*;
732 /// # use std::time::Duration;
733 /// # let mut queue = CommandQueue::default();
734 /// # let mut world = World::default();
735 /// # let mut commands = Commands::new(&mut queue, &mut world);
736 /// commands
737 /// .spawn(Transform::default())
738 /// .rotate_x(Duration::from_secs(1));
739 /// ```
740 fn rotate_x(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand>;
741
742 /// Queue a new tween animation to rotate the current entity around its Y
743 /// axis continuously (repeats forever, linearly).
744 ///
745 /// The entity must have a [`Transform`] component.
746 ///
747 /// This function is a fire-and-forget convenience helper, and doesn't give
748 /// access to the [`Entity`] created. To retrieve the entity and control
749 /// the animation playback, you should spawn a [`TweenAnim`] component
750 /// manually.
751 ///
752 /// # Example
753 ///
754 /// ```
755 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
756 /// # use bevy_tweening::*;
757 /// # use std::time::Duration;
758 /// # let mut queue = CommandQueue::default();
759 /// # let mut world = World::default();
760 /// # let mut commands = Commands::new(&mut queue, &mut world);
761 /// commands
762 /// .spawn(Transform::default())
763 /// .rotate_y(Duration::from_secs(1));
764 /// ```
765 fn rotate_y(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand>;
766
767 /// Queue a new tween animation to rotate the current entity around its Z
768 /// axis continuously (repeats forever, linearly).
769 ///
770 /// The entity must have a [`Transform`] component.
771 ///
772 /// This function is a fire-and-forget convenience helper, and doesn't give
773 /// access to the [`Entity`] created. To retrieve the entity and control
774 /// the animation playback, you should spawn a [`TweenAnim`] component
775 /// manually.
776 ///
777 /// # Example
778 ///
779 /// ```
780 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
781 /// # use bevy_tweening::*;
782 /// # use std::time::Duration;
783 /// # let mut queue = CommandQueue::default();
784 /// # let mut world = World::default();
785 /// # let mut commands = Commands::new(&mut queue, &mut world);
786 /// commands
787 /// .spawn(Transform::default())
788 /// .rotate_z(Duration::from_secs(1));
789 /// ```
790 fn rotate_z(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand>;
791
792 /// Queue a new tween animation to rotate the current entity around its X
793 /// axis by a given angle.
794 ///
795 /// The entity must have a [`Transform`] component. The animation applies a
796 /// rotation on top of the value of the [`Transform`] at the time the
797 /// animation is queued.
798 ///
799 /// This function is a fire-and-forget convenience helper, and doesn't give
800 /// access to the [`Entity`] created. To retrieve the entity and control
801 /// the animation playback, you should spawn a [`TweenAnim`] component
802 /// manually.
803 ///
804 /// # Example
805 ///
806 /// ```
807 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
808 /// # use bevy_tweening::*;
809 /// # use std::time::Duration;
810 /// # let mut queue = CommandQueue::default();
811 /// # let mut world = World::default();
812 /// # let mut commands = Commands::new(&mut queue, &mut world);
813 /// commands.spawn(Transform::default()).rotate_x_by(
814 /// std::f32::consts::FRAC_PI_4,
815 /// Duration::from_secs(1),
816 /// EaseFunction::QuadraticIn,
817 /// );
818 /// ```
819 fn rotate_x_by(
820 self,
821 angle: f32,
822 duration: Duration,
823 ease_method: impl Into<EaseMethod>,
824 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
825
826 /// Queue a new tween animation to rotate the current entity around its Y
827 /// axis by a given angle.
828 ///
829 /// The entity must have a [`Transform`] component. The animation applies a
830 /// rotation on top of the value of the [`Transform`] at the time the
831 /// animation is queued.
832 ///
833 /// This function is a fire-and-forget convenience helper, and doesn't give
834 /// access to the [`Entity`] created. To retrieve the entity and control
835 /// the animation playback, you should spawn a [`TweenAnim`] component
836 /// manually.
837 ///
838 /// # Example
839 ///
840 /// ```
841 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
842 /// # use bevy_tweening::*;
843 /// # use std::time::Duration;
844 /// # let mut queue = CommandQueue::default();
845 /// # let mut world = World::default();
846 /// # let mut commands = Commands::new(&mut queue, &mut world);
847 /// commands.spawn(Transform::default()).rotate_y_by(
848 /// std::f32::consts::FRAC_PI_4,
849 /// Duration::from_secs(1),
850 /// EaseFunction::QuadraticIn,
851 /// );
852 /// ```
853 fn rotate_y_by(
854 self,
855 angle: f32,
856 duration: Duration,
857 ease_method: impl Into<EaseMethod>,
858 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
859
860 /// Queue a new tween animation to rotate the current entity around its Z
861 /// axis by a given angle.
862 ///
863 /// The entity must have a [`Transform`] component. The animation applies a
864 /// rotation on top of the value of the [`Transform`] at the time the
865 /// animation is queued.
866 ///
867 /// This function is a fire-and-forget convenience helper, and doesn't give
868 /// access to the [`Entity`] created. To retrieve the entity and control
869 /// the animation playback, you should spawn a [`TweenAnim`] component
870 /// manually.
871 ///
872 /// # Example
873 ///
874 /// ```
875 /// # use bevy::{prelude::*, ecs::world::CommandQueue};
876 /// # use bevy_tweening::*;
877 /// # use std::time::Duration;
878 /// # let mut queue = CommandQueue::default();
879 /// # let mut world = World::default();
880 /// # let mut commands = Commands::new(&mut queue, &mut world);
881 /// commands.spawn(Transform::default()).rotate_z_by(
882 /// std::f32::consts::FRAC_PI_4,
883 /// Duration::from_secs(1),
884 /// EaseFunction::QuadraticIn,
885 /// );
886 /// ```
887 fn rotate_z_by(
888 self,
889 angle: f32,
890 duration: Duration,
891 ease_method: impl Into<EaseMethod>,
892 ) -> AnimatedEntityCommands<'a, impl TweenCommand>;
893}
894
895/// Helper trait to abstract a tweening animation command.
896///
897/// This is mostly used internally by the [`AnimatedEntityCommands`] to tweak
898/// the current animation, while also abstracting the various commands used to
899/// implement the [`EntityCommandsTweeningExtensions`]. In general, you probably
900/// don't have any use for that trait.
901pub trait TweenCommand: EntityCommand {
902 /// Get read-only access to the tween configuration of the command.
903 #[allow(unused)]
904 fn config(&self) -> &TweenConfig;
905
906 /// Get mutable access to the tween configuration of the command.
907 fn config_mut(&mut self) -> &mut TweenConfig;
908}
909
910/// Animation command to move an entity to a target position.
911#[derive(Clone, Copy)]
912pub(crate) struct MoveToCommand {
913 end: Vec3,
914 config: TweenConfig,
915}
916
917impl EntityCommand for MoveToCommand {
918 fn apply(self, mut entity: EntityWorldMut) {
919 if let Some(start) = entity.get::<Transform>().map(|tr| tr.translation) {
920 let lens = TransformPositionLens {
921 start,
922 end: self.end,
923 };
924 let tween = Tween::from_config(self.config, lens);
925 let anim_target = AnimTarget::component::<Transform>(entity.id());
926 entity.world_scope(|world| {
927 world.spawn((TweenAnim::new(tween), anim_target));
928 });
929 }
930 }
931}
932
933impl TweenCommand for MoveToCommand {
934 #[inline]
935 fn config(&self) -> &TweenConfig {
936 &self.config
937 }
938
939 #[inline]
940 fn config_mut(&mut self) -> &mut TweenConfig {
941 &mut self.config
942 }
943}
944
945/// Animation command to move an entity from a source position.
946#[derive(Clone, Copy)]
947pub(crate) struct MoveFromCommand {
948 start: Vec3,
949 config: TweenConfig,
950}
951
952impl EntityCommand for MoveFromCommand {
953 fn apply(self, mut entity: EntityWorldMut) {
954 if let Some(end) = entity.get::<Transform>().map(|tr| tr.translation) {
955 let lens = TransformPositionLens {
956 start: self.start,
957 end,
958 };
959 let tween = Tween::from_config(self.config, lens);
960 let anim_target = AnimTarget::component::<Transform>(entity.id());
961 entity.world_scope(|world| {
962 world.spawn((TweenAnim::new(tween), anim_target));
963 });
964 }
965 }
966}
967
968impl TweenCommand for MoveFromCommand {
969 #[inline]
970 fn config(&self) -> &TweenConfig {
971 &self.config
972 }
973
974 #[inline]
975 fn config_mut(&mut self) -> &mut TweenConfig {
976 &mut self.config
977 }
978}
979
980/// Animation command to scale an entity to a target size.
981#[derive(Clone, Copy)]
982pub(crate) struct ScaleToCommand {
983 end: Vec3,
984 config: TweenConfig,
985}
986
987impl EntityCommand for ScaleToCommand {
988 fn apply(self, mut entity: EntityWorldMut) {
989 if let Some(start) = entity.get::<Transform>().map(|tr| tr.scale) {
990 let lens = TransformScaleLens {
991 start,
992 end: self.end,
993 };
994 let tween = Tween::from_config(self.config, lens);
995 let anim_target = AnimTarget::component::<Transform>(entity.id());
996 entity.world_scope(|world| {
997 world.spawn((TweenAnim::new(tween), anim_target));
998 });
999 }
1000 }
1001}
1002
1003impl TweenCommand for ScaleToCommand {
1004 #[inline]
1005 fn config(&self) -> &TweenConfig {
1006 &self.config
1007 }
1008
1009 #[inline]
1010 fn config_mut(&mut self) -> &mut TweenConfig {
1011 &mut self.config
1012 }
1013}
1014
1015/// Animation command to scale an entity from a source size.
1016#[derive(Clone, Copy)]
1017pub(crate) struct ScaleFromCommand {
1018 start: Vec3,
1019 config: TweenConfig,
1020}
1021
1022impl EntityCommand for ScaleFromCommand {
1023 fn apply(self, mut entity: EntityWorldMut) {
1024 if let Some(end) = entity.get::<Transform>().map(|tr| tr.scale) {
1025 let lens = TransformScaleLens {
1026 start: self.start,
1027 end,
1028 };
1029 let tween = Tween::from_config(self.config, lens);
1030 let anim_target = AnimTarget::component::<Transform>(entity.id());
1031 entity.world_scope(|world| {
1032 world.spawn((TweenAnim::new(tween), anim_target));
1033 });
1034 }
1035 }
1036}
1037
1038impl TweenCommand for ScaleFromCommand {
1039 #[inline]
1040 fn config(&self) -> &TweenConfig {
1041 &self.config
1042 }
1043
1044 #[inline]
1045 fn config_mut(&mut self) -> &mut TweenConfig {
1046 &mut self.config
1047 }
1048}
1049
1050/// Animation command to rotate an entity around its X axis.
1051#[derive(Clone, Copy)]
1052pub(crate) struct RotateXCommand {
1053 config: TweenConfig,
1054}
1055
1056impl EntityCommand for RotateXCommand {
1057 fn apply(self, mut entity: EntityWorldMut) {
1058 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1059 let lens = TransformRotateAdditiveXLens {
1060 base_rotation,
1061 start: 0.,
1062 end: std::f32::consts::TAU,
1063 };
1064 let tween = Tween::from_config(self.config, lens)
1065 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1066 let anim_target = AnimTarget::component::<Transform>(entity.id());
1067 entity.world_scope(|world| {
1068 world.spawn((TweenAnim::new(tween), anim_target));
1069 });
1070 }
1071 }
1072}
1073
1074impl TweenCommand for RotateXCommand {
1075 #[inline]
1076 fn config(&self) -> &TweenConfig {
1077 &self.config
1078 }
1079
1080 #[inline]
1081 fn config_mut(&mut self) -> &mut TweenConfig {
1082 &mut self.config
1083 }
1084}
1085
1086/// Animation command to rotate an entity around its Y axis.
1087#[derive(Clone, Copy)]
1088pub(crate) struct RotateYCommand {
1089 config: TweenConfig,
1090}
1091
1092impl EntityCommand for RotateYCommand {
1093 fn apply(self, mut entity: EntityWorldMut) {
1094 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1095 let lens = TransformRotateAdditiveYLens {
1096 base_rotation,
1097 start: 0.,
1098 end: std::f32::consts::TAU,
1099 };
1100 let tween = Tween::from_config(self.config, lens)
1101 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1102 let anim_target = AnimTarget::component::<Transform>(entity.id());
1103 entity.world_scope(|world| {
1104 world.spawn((TweenAnim::new(tween), anim_target));
1105 });
1106 }
1107 }
1108}
1109
1110impl TweenCommand for RotateYCommand {
1111 #[inline]
1112 fn config(&self) -> &TweenConfig {
1113 &self.config
1114 }
1115
1116 #[inline]
1117 fn config_mut(&mut self) -> &mut TweenConfig {
1118 &mut self.config
1119 }
1120}
1121
1122/// Animation command to rotate an entity around its Z axis.
1123#[derive(Clone, Copy)]
1124pub(crate) struct RotateZCommand {
1125 config: TweenConfig,
1126}
1127
1128impl EntityCommand for RotateZCommand {
1129 fn apply(self, mut entity: EntityWorldMut) {
1130 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1131 let lens = TransformRotateAdditiveZLens {
1132 base_rotation,
1133 start: 0.,
1134 end: std::f32::consts::TAU,
1135 };
1136 let tween = Tween::from_config(self.config, lens)
1137 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1138 let anim_target = AnimTarget::component::<Transform>(entity.id());
1139 entity.world_scope(|world| {
1140 world.spawn((TweenAnim::new(tween), anim_target));
1141 });
1142 }
1143 }
1144}
1145
1146impl TweenCommand for RotateZCommand {
1147 #[inline]
1148 fn config(&self) -> &TweenConfig {
1149 &self.config
1150 }
1151
1152 #[inline]
1153 fn config_mut(&mut self) -> &mut TweenConfig {
1154 &mut self.config
1155 }
1156}
1157
1158/// Animation command to rotate an entity around its X axis by a given angle.
1159#[derive(Clone, Copy)]
1160pub(crate) struct RotateXByCommand {
1161 angle: f32,
1162 config: TweenConfig,
1163}
1164
1165impl EntityCommand for RotateXByCommand {
1166 fn apply(self, mut entity: EntityWorldMut) {
1167 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1168 let lens = TransformRotateAdditiveXLens {
1169 base_rotation,
1170 start: 0.,
1171 end: self.angle,
1172 };
1173 let tween = Tween::from_config(self.config, lens);
1174 let anim_target = AnimTarget::component::<Transform>(entity.id());
1175 entity.world_scope(|world| {
1176 world.spawn((TweenAnim::new(tween), anim_target));
1177 });
1178 }
1179 }
1180}
1181
1182impl TweenCommand for RotateXByCommand {
1183 #[inline]
1184 fn config(&self) -> &TweenConfig {
1185 &self.config
1186 }
1187
1188 #[inline]
1189 fn config_mut(&mut self) -> &mut TweenConfig {
1190 &mut self.config
1191 }
1192}
1193
1194/// Animation command to rotate an entity around its Y axis by a given angle.
1195#[derive(Clone, Copy)]
1196pub(crate) struct RotateYByCommand {
1197 angle: f32,
1198 config: TweenConfig,
1199}
1200
1201impl EntityCommand for RotateYByCommand {
1202 fn apply(self, mut entity: EntityWorldMut) {
1203 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1204 let lens = TransformRotateAdditiveYLens {
1205 base_rotation,
1206 start: 0.,
1207 end: self.angle,
1208 };
1209 let tween = Tween::from_config(self.config, lens);
1210 let anim_target = AnimTarget::component::<Transform>(entity.id());
1211 entity.world_scope(|world| {
1212 world.spawn((TweenAnim::new(tween), anim_target));
1213 });
1214 }
1215 }
1216}
1217
1218impl TweenCommand for RotateYByCommand {
1219 #[inline]
1220 fn config(&self) -> &TweenConfig {
1221 &self.config
1222 }
1223
1224 #[inline]
1225 fn config_mut(&mut self) -> &mut TweenConfig {
1226 &mut self.config
1227 }
1228}
1229
1230/// Animation command to rotate an entity around its Z axis by a given angle.
1231#[derive(Clone, Copy)]
1232pub(crate) struct RotateZByCommand {
1233 angle: f32,
1234 config: TweenConfig,
1235}
1236
1237impl EntityCommand for RotateZByCommand {
1238 fn apply(self, mut entity: EntityWorldMut) {
1239 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1240 let lens = TransformRotateAdditiveZLens {
1241 base_rotation,
1242 start: 0.,
1243 end: self.angle,
1244 };
1245 let tween = Tween::from_config(self.config, lens);
1246 let anim_target = AnimTarget::component::<Transform>(entity.id());
1247 entity.world_scope(|world| {
1248 world.spawn((TweenAnim::new(tween), anim_target));
1249 });
1250 }
1251 }
1252}
1253
1254impl TweenCommand for RotateZByCommand {
1255 #[inline]
1256 fn config(&self) -> &TweenConfig {
1257 &self.config
1258 }
1259
1260 #[inline]
1261 fn config_mut(&mut self) -> &mut TweenConfig {
1262 &mut self.config
1263 }
1264}
1265
1266/// Wrapper over an [`EntityCommands`] which stores an animation command.
1267///
1268/// The wrapper acts as, and dereferences to, a regular [`EntityCommands`] as
1269/// _e.g._ returned by [`Commands::spawn()`]. In addition, it stores a pending
1270/// animation command, which can be further tweaked before being queued into the
1271/// entity commands queue. This deferred queuing allows fluent patterns like:
1272///
1273/// ```
1274/// # use std::time::Duration;
1275/// # use bevy::prelude::*;
1276/// # use bevy_tweening::*;
1277/// # fn my_system(mut commands: Commands) {
1278/// commands
1279/// .spawn(Transform::default())
1280/// // Consume the EntityCommands, and wrap it into an AnimatedEntityCommands,
1281/// // which stores an animation command to move an entity.
1282/// .move_to(
1283/// Vec3::ONE,
1284/// Duration::from_millis(400),
1285/// EaseFunction::QuadraticIn,
1286/// )
1287/// // Tweak the stored animation to set the repeat count of the Tween.
1288/// .with_repeat_count(2);
1289/// # }
1290/// ```
1291///
1292/// The animation commands always stores the last animation inserted. When the
1293/// commands is mutably dereferenced, it first flushes the pending animation
1294/// command, if any, by inserting it into the underlying [`EntityCommands`]
1295/// queue. It also flushes the animation when dropped, to ensure the last
1296/// animation is queued too.
1297///
1298/// To move from an [`AnimatedEntityCommands`] to its underlying
1299/// [`EntityCommands`], the former automatically dereferences to the latter.
1300/// Note however that once you're back on the base [`EntityCommands`], you can
1301/// only get a new [`AnimatedEntityCommands`] via functions consuming the
1302/// [`EntityCommands`] by value. In that case, you need to call [`reborrow()`]:
1303///
1304/// ```
1305/// # use std::time::Duration;
1306/// # use bevy::prelude::*;
1307/// # use bevy_tweening::*;
1308/// # fn my_system(mut commands: Commands) {
1309/// commands
1310/// .spawn(Transform::default())
1311/// .move_to(
1312/// Vec3::ONE,
1313/// Duration::from_millis(400),
1314/// EaseFunction::QuadraticIn,
1315/// )
1316/// // This call invokes std::ops::DerefMut, and returns a mutable ref
1317/// // to the underlying EntityCommands
1318/// .insert(Name::new("my_object"))
1319/// // Here we need to reborrow() to convert from `&mut EntityCommands`
1320/// // (by mutable ref) to `EntityCommands` (by value)
1321/// .reborrow()
1322/// // This call requires an `EntityCommands` (by value)
1323/// .scale_to(
1324/// Vec3::splat(1.1),
1325/// Duration::from_millis(400),
1326/// EaseFunction::Linear,
1327/// );
1328/// # }
1329/// ```
1330///
1331/// [`reborrow()`]: bevy::prelude::EntityCommands::reborrow
1332pub struct AnimatedEntityCommands<'a, C: TweenCommand> {
1333 commands: EntityCommands<'a>,
1334 cmd: Option<C>,
1335}
1336
1337impl<'a, C: TweenCommand> AnimatedEntityCommands<'a, C> {
1338 /// Wrap an [`EntityCommands`] into an animated one.
1339 pub fn new(commands: EntityCommands<'a>, cmd: C) -> Self {
1340 Self {
1341 commands,
1342 cmd: Some(cmd),
1343 }
1344 }
1345
1346 /// Set the repeat count of this animation.
1347 #[inline]
1348 pub fn with_repeat_count(mut self, repeat_count: impl Into<RepeatCount>) -> Self {
1349 if let Some(cmd) = self.cmd.as_mut() {
1350 cmd.config_mut().repeat_count = repeat_count.into();
1351 }
1352 self
1353 }
1354
1355 /// Set the repeat strategy of this animation.
1356 #[inline]
1357 pub fn with_repeat_strategy(mut self, repeat_strategy: RepeatStrategy) -> Self {
1358 if let Some(cmd) = self.cmd.as_mut() {
1359 cmd.config_mut().repeat_strategy = repeat_strategy;
1360 }
1361 self
1362 }
1363
1364 /// Configure the repeat parameters of this animation.
1365 ///
1366 /// This is a shortcut for:
1367 ///
1368 /// ```no_run
1369 /// # use bevy_tweening::*;
1370 /// # struct AnimatedEntityCommands {}
1371 /// # impl AnimatedEntityCommands {
1372 /// # fn with_repeat_count(self, r: RepeatCount) -> Self { unimplemented!() }
1373 /// # fn with_repeat_strategy(self, r: RepeatStrategy) -> Self { unimplemented!() }
1374 /// # fn xxx(self) -> Self {
1375 /// # let repeat_count = RepeatCount::Infinite;
1376 /// # let repeat_strategy = RepeatStrategy::Repeat;
1377 /// self.with_repeat_count(repeat_count)
1378 /// .with_repeat_strategy(repeat_strategy)
1379 /// # }}
1380 /// ```
1381 #[inline]
1382 pub fn with_repeat(
1383 self,
1384 repeat_count: impl Into<RepeatCount>,
1385 repeat_strategy: RepeatStrategy,
1386 ) -> Self {
1387 self.with_repeat_count(repeat_count)
1388 .with_repeat_strategy(repeat_strategy)
1389 }
1390
1391 /// Consume self and return the inner [`EntityCommands`].
1392 ///
1393 /// The current animation is inserted into the commands queue, before that
1394 /// wrapped commands queue is returned.
1395 pub fn into_inner(mut self) -> EntityCommands<'a> {
1396 self.flush();
1397 // Since we already flushed above, we don't need Drop. And trying to keep would
1398 // allow it to access self.commands after it was stolen (even though we know the
1399 // implementation doesn't in practice). Still, it's safer to just short-circuit
1400 // Drop here.
1401 let this = std::mem::ManuallyDrop::new(self);
1402 // SAFETY: We have flushed self.cmd which is now None, and we're stealing
1403 // self.commands, after which the this object is forgotten and never
1404 // accessed again.
1405 #[allow(unsafe_code)]
1406 unsafe {
1407 std::ptr::read(&this.commands)
1408 }
1409 }
1410
1411 /// Flush the current animation, inserting it into the commands queue.
1412 ///
1413 /// This makes it impossible to further tweak the animation. This is
1414 /// automatically called when a new animation is created and when the
1415 /// commands queue is dropped with the last animation pending.
1416 fn flush(&mut self) {
1417 if let Some(cmd) = self.cmd.take() {
1418 self.queue(cmd);
1419 }
1420 }
1421}
1422
1423impl<'a, C: TweenCommand> EntityCommandsTweeningExtensions<'a> for AnimatedEntityCommands<'a, C> {
1424 #[inline]
1425 fn move_to(
1426 self,
1427 end: Vec3,
1428 duration: Duration,
1429 ease_method: impl Into<EaseMethod>,
1430 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1431 self.into_inner().move_to(end, duration, ease_method)
1432 }
1433
1434 #[inline]
1435 fn move_from(
1436 self,
1437 start: Vec3,
1438 duration: Duration,
1439 ease_method: impl Into<EaseMethod>,
1440 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1441 self.into_inner().move_from(start, duration, ease_method)
1442 }
1443
1444 #[inline]
1445 fn scale_to(
1446 self,
1447 end: Vec3,
1448 duration: Duration,
1449 ease_method: impl Into<EaseMethod>,
1450 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1451 self.into_inner().scale_to(end, duration, ease_method)
1452 }
1453
1454 #[inline]
1455 fn scale_from(
1456 self,
1457 start: Vec3,
1458 duration: Duration,
1459 ease_method: impl Into<EaseMethod>,
1460 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1461 self.into_inner().scale_from(start, duration, ease_method)
1462 }
1463
1464 #[inline]
1465 fn rotate_x(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1466 self.into_inner().rotate_x(cycle_duration)
1467 }
1468
1469 #[inline]
1470 fn rotate_y(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1471 self.into_inner().rotate_y(cycle_duration)
1472 }
1473
1474 #[inline]
1475 fn rotate_z(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1476 self.into_inner().rotate_z(cycle_duration)
1477 }
1478
1479 #[inline]
1480 fn rotate_x_by(
1481 self,
1482 angle: f32,
1483 duration: Duration,
1484 ease_method: impl Into<EaseMethod>,
1485 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1486 self.into_inner().rotate_x_by(angle, duration, ease_method)
1487 }
1488
1489 #[inline]
1490 fn rotate_y_by(
1491 self,
1492 angle: f32,
1493 duration: Duration,
1494 ease_method: impl Into<EaseMethod>,
1495 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1496 self.into_inner().rotate_y_by(angle, duration, ease_method)
1497 }
1498
1499 #[inline]
1500 fn rotate_z_by(
1501 self,
1502 angle: f32,
1503 duration: Duration,
1504 ease_method: impl Into<EaseMethod>,
1505 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1506 self.into_inner().rotate_z_by(angle, duration, ease_method)
1507 }
1508}
1509
1510impl<'a, C: TweenCommand> Deref for AnimatedEntityCommands<'a, C> {
1511 type Target = EntityCommands<'a>;
1512
1513 fn deref(&self) -> &Self::Target {
1514 &self.commands
1515 }
1516}
1517
1518impl<C: TweenCommand> DerefMut for AnimatedEntityCommands<'_, C> {
1519 fn deref_mut(&mut self) -> &mut Self::Target {
1520 self.flush();
1521 &mut self.commands
1522 }
1523}
1524
1525impl<C: TweenCommand> Drop for AnimatedEntityCommands<'_, C> {
1526 fn drop(&mut self) {
1527 self.flush();
1528 }
1529}
1530
1531impl<'a> EntityCommandsTweeningExtensions<'a> for EntityCommands<'a> {
1532 #[inline]
1533 fn move_to(
1534 self,
1535 end: Vec3,
1536 duration: Duration,
1537 ease_method: impl Into<EaseMethod>,
1538 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1539 AnimatedEntityCommands::new(
1540 self,
1541 MoveToCommand {
1542 end,
1543 config: TweenConfig {
1544 ease_method: ease_method.into(),
1545 cycle_duration: duration,
1546 ..default()
1547 },
1548 },
1549 )
1550 }
1551
1552 #[inline]
1553 fn move_from(
1554 self,
1555 start: Vec3,
1556 duration: Duration,
1557 ease_method: impl Into<EaseMethod>,
1558 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1559 AnimatedEntityCommands::new(
1560 self,
1561 MoveFromCommand {
1562 start,
1563 config: TweenConfig {
1564 ease_method: ease_method.into(),
1565 cycle_duration: duration,
1566 ..default()
1567 },
1568 },
1569 )
1570 }
1571
1572 #[inline]
1573 fn scale_to(
1574 self,
1575 end: Vec3,
1576 duration: Duration,
1577 ease_method: impl Into<EaseMethod>,
1578 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1579 AnimatedEntityCommands::new(
1580 self,
1581 ScaleToCommand {
1582 end,
1583 config: TweenConfig {
1584 ease_method: ease_method.into(),
1585 cycle_duration: duration,
1586 ..default()
1587 },
1588 },
1589 )
1590 }
1591
1592 #[inline]
1593 fn scale_from(
1594 self,
1595 start: Vec3,
1596 duration: Duration,
1597 ease_method: impl Into<EaseMethod>,
1598 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1599 AnimatedEntityCommands::new(
1600 self,
1601 ScaleFromCommand {
1602 start,
1603 config: TweenConfig {
1604 ease_method: ease_method.into(),
1605 cycle_duration: duration,
1606 ..default()
1607 },
1608 },
1609 )
1610 }
1611
1612 #[inline]
1613 fn rotate_x(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1614 AnimatedEntityCommands::new(
1615 self,
1616 RotateXCommand {
1617 config: TweenConfig {
1618 ease_method: EaseFunction::Linear.into(),
1619 cycle_duration,
1620 ..default()
1621 },
1622 },
1623 )
1624 }
1625
1626 #[inline]
1627 fn rotate_y(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1628 AnimatedEntityCommands::new(
1629 self,
1630 RotateYCommand {
1631 config: TweenConfig {
1632 ease_method: EaseFunction::Linear.into(),
1633 cycle_duration,
1634 ..default()
1635 },
1636 },
1637 )
1638 }
1639
1640 #[inline]
1641 fn rotate_z(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1642 AnimatedEntityCommands::new(
1643 self,
1644 RotateZCommand {
1645 config: TweenConfig {
1646 ease_method: EaseFunction::Linear.into(),
1647 cycle_duration,
1648 ..default()
1649 },
1650 },
1651 )
1652 }
1653
1654 #[inline]
1655 fn rotate_x_by(
1656 self,
1657 angle: f32,
1658 duration: Duration,
1659 ease_method: impl Into<EaseMethod>,
1660 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1661 AnimatedEntityCommands::new(
1662 self,
1663 RotateXByCommand {
1664 angle,
1665 config: TweenConfig {
1666 ease_method: ease_method.into(),
1667 cycle_duration: duration,
1668 ..default()
1669 },
1670 },
1671 )
1672 }
1673
1674 #[inline]
1675 fn rotate_y_by(
1676 self,
1677 angle: f32,
1678 duration: Duration,
1679 ease_method: impl Into<EaseMethod>,
1680 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1681 AnimatedEntityCommands::new(
1682 self,
1683 RotateYByCommand {
1684 angle,
1685 config: TweenConfig {
1686 ease_method: ease_method.into(),
1687 cycle_duration: duration,
1688 ..default()
1689 },
1690 },
1691 )
1692 }
1693
1694 #[inline]
1695 fn rotate_z_by(
1696 self,
1697 angle: f32,
1698 duration: Duration,
1699 ease_method: impl Into<EaseMethod>,
1700 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1701 AnimatedEntityCommands::new(
1702 self,
1703 RotateZByCommand {
1704 angle,
1705 config: TweenConfig {
1706 ease_method: ease_method.into(),
1707 cycle_duration: duration,
1708 ..default()
1709 },
1710 },
1711 )
1712 }
1713}
1714
1715/// Event raised when a [`TweenAnim`] completed.
1716#[derive(Debug, Clone, Copy, EntityEvent, Message)]
1717pub struct AnimCompletedEvent {
1718 /// The entity owning the [`TweenAnim`] which completed.
1719 ///
1720 /// Note that commonly the [`TweenAnim`] is despawned on completion, so
1721 /// can't be queried anymore with this entity. You can prevent a completed
1722 /// animation from being automatically destroyed by
1723 /// setting [`TweenAnim::destroy_on_completion`] to `false`.
1724 #[event_target]
1725 pub anim_entity: Entity,
1726 /// The animation target.
1727 ///
1728 /// This is provided both as a convenience for [`TweenAnim`]s not destroyed
1729 /// on completion, and because for those animations which are destroyed
1730 /// on completion the information is not available anymore when this
1731 /// event is received.
1732 pub target: AnimTargetKind,
1733}
1734
1735/// Errors returned by various animation functions.
1736#[derive(Debug, Error, Clone, Copy)]
1737pub enum TweeningError {
1738 /// The asset resolver for the given asset is not registered.
1739 #[error("Asset resolver for asset with resource ID {0:?} is not registered.")]
1740 AssetResolverNotRegistered(ComponentId),
1741 /// The entity was not found in the World.
1742 #[error("Entity {0:?} not found in the World.")]
1743 EntityNotFound(Entity),
1744 /// The entity should have had a TweenAnim but it was not found.
1745 #[error("Entity {0:?} doesn't have a TweenAnim.")]
1746 MissingTweenAnim(Entity),
1747 /// The component of the given type is not registered.
1748 #[error("Component of type {0:?} is not registered in the World.")]
1749 ComponentNotRegistered(TypeId),
1750 /// The resource of the given type is not registered.
1751 #[error("Resource of type {0:?} is not registered in the World.")]
1752 ResourceNotRegistered(TypeId),
1753 /// The asset container for the given asset type is not registered.
1754 #[error("Asset container Assets<A> for asset type A = {0:?} is not registered in the World.")]
1755 AssetNotRegistered(TypeId),
1756 /// The component of the given type is not registered.
1757 #[error("Component of type {0:?} is not present on entity {1:?}.")]
1758 MissingComponent(TypeId, Entity),
1759 /// The asset cannot be found.
1760 #[error("Asset ID {0:?} is invalid.")]
1761 InvalidAssetId(UntypedAssetId),
1762 /// The asset ID references a different type than expected.
1763 #[error("Expected type of asset ID to be {expected:?} but got {actual:?} instead.")]
1764 InvalidAssetIdType {
1765 /// The expected asset type.
1766 expected: TypeId,
1767 /// The actual type the asset ID references.
1768 actual: TypeId,
1769 },
1770 /// Expected [`Tweenable::target_type_id()`] to return a value, but it
1771 /// returned `None`.
1772 #[error("Expected a typed Tweenable.")]
1773 UntypedTweenable,
1774 /// Invalid [`Entity`].
1775 #[error("Invalid Entity {0:?}.")]
1776 InvalidTweenId(Entity),
1777 /// Cannot change target kind.
1778 #[error("Unexpected target kind: was component={0}, now component={1}")]
1779 MismatchingTargetKind(bool, bool),
1780 /// Cannot change component type.
1781 #[error("Cannot change component type: was component_id={0:?}, now component_id={1:?}")]
1782 MismatchingComponentId(ComponentId, ComponentId),
1783 /// Cannot change asset type.
1784 #[error("Cannot change asset type: was component_id={0:?}, now component_id={1:?}")]
1785 MismatchingAssetResourceId(ComponentId, ComponentId),
1786}
1787
1788type RegisterAction = dyn Fn(&Components, &mut TweenResolver) + Send + Sync + 'static;
1789
1790/// Enumeration of the types of animation targets.
1791///
1792/// This type holds the minimum amount of data to reference ananimation target,
1793/// aside from the actual type of the target.
1794#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1795pub enum AnimTargetKind {
1796 /// Component animation target.
1797 Component {
1798 /// The entity owning the component instance.
1799 entity: Entity,
1800 },
1801 /// Resource animation target.
1802 Resource,
1803 /// Asset animation target.
1804 Asset {
1805 /// The asset ID inside the [`Assets`] collection.
1806 asset_id: UntypedAssetId,
1807 /// Type ID of the [`Assets`] collection itself.
1808 assets_type_id: TypeId,
1809 },
1810}
1811
1812/// Component defining the target of an animation.
1813///
1814/// References an object used as the target of the animation stored in the
1815/// [`TweenAnim`] component on the same entity.
1816#[derive(Component)]
1817pub struct AnimTarget {
1818 /// Target kind and additional data to identify it.
1819 pub kind: AnimTargetKind,
1820
1821 /// Self-registering action for assets and resources.
1822 pub(crate) register_action: Option<Box<RegisterAction>>,
1823}
1824
1825impl AnimTarget {
1826 /// Create a target mutating a component on the given entity.
1827 pub fn component<C: Component<Mutability = Mutable>>(entity: Entity) -> Self {
1828 Self {
1829 kind: AnimTargetKind::Component { entity },
1830 // Components have a complete typeless API, don't need any extra registration for type
1831 // erasure.
1832 register_action: None,
1833 }
1834 }
1835
1836 /// Create a target mutating the given resource.
1837 pub fn resource<R: Resource>() -> Self {
1838 let register_action = |components: &Components, resolver: &mut TweenResolver| {
1839 resolver.register_resource_resolver_for::<R>(components);
1840 };
1841 Self {
1842 kind: AnimTargetKind::Resource,
1843 register_action: Some(Box::new(register_action)),
1844 }
1845 }
1846
1847 /// Create a target mutating the given asset.
1848 ///
1849 /// The asset is identified by its type, and its [`AssetId`].
1850 pub fn asset<A: Asset>(asset_id: impl Into<AssetId<A>>) -> Self {
1851 let register_action = |components: &Components, resolver: &mut TweenResolver| {
1852 resolver.register_asset_resolver_for::<A>(components);
1853 };
1854 Self {
1855 kind: AnimTargetKind::Asset {
1856 asset_id: asset_id.into().untyped(),
1857 assets_type_id: TypeId::of::<Assets<A>>(),
1858 },
1859 register_action: Some(Box::new(register_action)),
1860 }
1861 }
1862
1863 /// Register any resolver for this target.
1864 pub(crate) fn register(&self, components: &Components, resolver: &mut TweenResolver) {
1865 if let Some(register_action) = self.register_action.as_ref() {
1866 register_action(components, resolver);
1867 }
1868 }
1869}
1870
1871/// Animation controller instance.
1872///
1873/// The [`TweenAnim`] represents a single animation instance for a single
1874/// target (component or resource or asset). Each instance is independent, even
1875/// if it mutates the same target as another instance. Spawning this component
1876/// adds an active animation, and destroying it stops that animation. The
1877/// component can also be used to control the animation playback at runtime,
1878/// like the playback speed.
1879///
1880/// The target is described by the [`AnimTarget`] component. If that component
1881/// is absent, then the animation implicitly targets a component on the current
1882/// Entity. The type of the component is derived from the type that the [`Lens`]
1883/// animates.
1884///
1885/// _If you're looking for the basic tweenable animation description, see
1886/// [`Tween`] instead._
1887///
1888/// # Example
1889///
1890/// ```
1891/// # use bevy::prelude::*;
1892/// # use bevy_tweening::*;
1893/// # fn make_tweenable<T>() -> Tween { unimplemented!() }
1894/// fn my_system(mut commands: Commands) {
1895/// let tweenable = make_tweenable::<Transform>();
1896/// let id1 = commands
1897/// .spawn((
1898/// Transform::default(),
1899/// // Implicitly targets the current entity's Transform
1900/// TweenAnim::new(tweenable),
1901/// ))
1902/// .id();
1903///
1904/// let tweenable2 = make_tweenable::<Transform>();
1905/// commands.spawn((
1906/// TweenAnim::new(tweenable2),
1907/// // Explicitly targets the Transform component of entity 'id1'
1908/// AnimTarget::component::<Transform>(id1),
1909/// ));
1910/// }
1911/// ```
1912#[derive(Component)]
1913pub struct TweenAnim {
1914 /// The animation itself. Note that the tweenable is stateful, so can't be
1915 /// shared with another [`TweenAnim`] instance.
1916 tweenable: BoxedTweenable,
1917 /// Control if the animation is played or not. Defaults to
1918 /// [`PlaybackState::Playing`].
1919 ///
1920 /// Pausing an animation with [`PlaybackState::Paused`] is functionaly
1921 /// equivalent to setting its [`speed`] to zero. The two fields remain
1922 /// independent though, for convenience.
1923 ///
1924 /// [`speed`]: Self::speed
1925 pub playback_state: PlaybackState,
1926 /// Relative playback speed. Defaults to `1.` (normal speed; 100%).
1927 ///
1928 /// Setting a negative or zero speed value effectively pauses the animation
1929 /// (although the [`playback_state`] remains unchanged). Negative values may
1930 /// be clamped to 0. when the animation is stepped, but positive or zero
1931 /// values are never modified by the library.
1932 ///
1933 /// # Time precision
1934 ///
1935 /// _This note is an implementation detail which can usually be ignored._
1936 ///
1937 /// Despite the use of `f64`, setting a playback speed different from `1.`
1938 /// (100% speed) may produce small inaccuracies in durations, especially
1939 /// for longer animations. However those are often negligible.
1940 /// This is due to the very large precision of `Duration` (typically 96
1941 /// bits or more), even compared to `f64` (64 bits), and the fact this speed
1942 /// factor is a multiplier whereas most other time quantities are added or
1943 /// subtracted.
1944 ///
1945 /// [`playback_state`]: Self::playback_state
1946 pub speed: f64,
1947 /// Destroy the animation once completed. This defaults to `true`, and makes
1948 /// the stepping functions like [`TweenAnim::step_all()`] destroy this
1949 /// animation once it completed. To keep the animation queued, and allow
1950 /// access after it completed, set this to `false`. Note however that
1951 /// you should avoid leaving all animations queued if they're unused, as
1952 /// this wastes memory and may degrade performances if too many
1953 /// completed animations are kept around for no good reason.
1954 pub destroy_on_completion: bool,
1955 /// Current tweening completion state.
1956 tween_state: TweenState,
1957}
1958
1959impl TweenAnim {
1960 /// Create a new tween animation.
1961 ///
1962 /// This component represents the runtime animation being played to mutate a
1963 /// specific target.
1964 ///
1965 /// # Panics
1966 ///
1967 /// Panics if the tweenable is "typeless", that is
1968 /// [`Tweenable::target_type_id()`] returns `None`. Animations must
1969 /// target a concrete component or asset type. This means in particular
1970 /// that you can't use a single [`Delay`] alone. You can however use a
1971 /// [`Delay`] or other typeless tweenables as part of a [`Sequence`],
1972 /// provided there's at least one other typed tweenable in the sequence
1973 /// to make it typed too.
1974 #[inline]
1975 pub fn new(tweenable: impl IntoBoxedTweenable) -> Self {
1976 let tweenable = tweenable.into_boxed();
1977 assert!(
1978 tweenable.target_type_id().is_some(),
1979 "The top-level Tweenable of a TweenAnim must be typed (Tweenable::target_type_id() returns Some)."
1980 );
1981 Self {
1982 tweenable,
1983 playback_state: PlaybackState::Playing,
1984 speed: 1.,
1985 destroy_on_completion: true,
1986 tween_state: TweenState::Active,
1987 }
1988 }
1989
1990 /// Configure the playback speed.
1991 pub fn with_speed(mut self, speed: f64) -> Self {
1992 self.speed = speed;
1993 self
1994 }
1995
1996 /// Enable or disable destroying this component on animation completion.
1997 ///
1998 /// If enabled, the component is automatically removed from its `Entity`
1999 /// when the animation completed.
2000 pub fn with_destroy_on_completed(mut self, destroy_on_completed: bool) -> Self {
2001 self.destroy_on_completion = destroy_on_completed;
2002 self
2003 }
2004
2005 /// Step a single animation.
2006 ///
2007 /// _The [`step_all()`] function is called automatically by the animation
2008 /// system registered by the [`TweeningPlugin`], you generally don't
2009 /// need to call this one._
2010 ///
2011 /// This is a shortcut for `step_many(world, delta_time, [entity])`, with
2012 /// the added benefit that it returns some error if the entity is not valid.
2013 /// See [`step_many()`] for details.
2014 ///
2015 /// # Example
2016 ///
2017 /// ```
2018 /// # use std::time::Duration;
2019 /// # use bevy::prelude::*;
2020 /// # use bevy_tweening::*;
2021 /// # fn make_tweenable() -> Tween { unimplemented!() }
2022 /// #[derive(Component)]
2023 /// struct MyMarker;
2024 ///
2025 /// fn my_system(world: &mut World) -> Result<()> {
2026 /// let mut q_anims = world.query_filtered::<Entity, (With<TweenAnim>, With<MyMarker>)>();
2027 /// let entity = q_anims.single(world)?;
2028 /// let delta_time = Duration::from_millis(200);
2029 /// TweenAnim::step_one(world, delta_time, entity);
2030 /// Ok(())
2031 /// }
2032 /// ```
2033 ///
2034 /// # Returns
2035 ///
2036 /// This returns an error if the entity is not found or doesn't own a
2037 /// [`TweenAnim`] component.
2038 ///
2039 /// [`step_all()`]: Self::step_all
2040 /// [`step_many()`]: Self::step_many
2041 #[inline]
2042 pub fn step_one(
2043 world: &mut World,
2044 delta_time: Duration,
2045 entity: Entity,
2046 ) -> Result<(), TweeningError> {
2047 let num = Self::step_many(world, delta_time, &[entity]);
2048 if num > 0 {
2049 Ok(())
2050 } else {
2051 Err(TweeningError::EntityNotFound(entity))
2052 }
2053 }
2054
2055 /// Step some animation(s).
2056 ///
2057 /// _The [`step_all()`] function is called automatically by the animation
2058 /// system registered by the [`TweeningPlugin`], you generally don't
2059 /// need to call this one._
2060 ///
2061 /// Step the given animation(s) by a given `delta_time`, which may be
2062 /// [`Duration::ZERO`]. Passing a zero delta time may be useful to force the
2063 /// current animation state to be applied to a target, in case you made
2064 /// change which do not automatically do so (for example, retargeting an
2065 /// animation). The `anims` are the entities which own a [`TweenAnim`]
2066 /// component to step; any entity without a [`TweenAnim`] component is
2067 /// silently ignored.
2068 ///
2069 /// The function doesn't check that all input entities are unique. If an
2070 /// entity is duplicated in `anims`, the behavior is undefined, including
2071 /// (but not guaranteed) stepping the animation multiple times. You're
2072 /// responsible for ensuring the input entity slice contains distinct
2073 /// entities.
2074 ///
2075 /// # Example
2076 ///
2077 /// ```
2078 /// # use std::time::Duration;
2079 /// # use bevy::prelude::*;
2080 /// # use bevy_tweening::*;
2081 /// # fn make_tweenable() -> Tween { unimplemented!() }
2082 /// #[derive(Component)]
2083 /// struct MyMarker;
2084 ///
2085 /// fn my_system(world: &mut World) -> Result<()> {
2086 /// let mut q_anims = world.query_filtered::<Entity, (With<TweenAnim>, With<MyMarker>)>();
2087 /// let entities = q_anims.iter(world).collect::<Vec<Entity>>();
2088 /// let delta_time = Duration::from_millis(200);
2089 /// TweenAnim::step_many(world, delta_time, &entities[..]);
2090 /// Ok(())
2091 /// }
2092 /// ```
2093 ///
2094 /// # Returns
2095 ///
2096 /// Returns the number of [`TweenAnim`] component found and stepped, which
2097 /// is always less than or equal to the input `anims` slice length.
2098 ///
2099 /// [`step_all()`]: Self::step_all
2100 pub fn step_many(world: &mut World, delta_time: Duration, anims: &[Entity]) -> usize {
2101 let mut targets = vec![];
2102 world.resource_scope(|world, mut resolver: Mut<TweenResolver>| {
2103 let mut q_anims = world.query::<(Entity, &TweenAnim, Option<&AnimTarget>)>();
2104 targets.reserve(anims.len());
2105 for entity in anims {
2106 if let Ok((entity, anim, maybe_target)) = q_anims.get(world, *entity) {
2107 // Lazy registration with resolver if needed
2108 if let Some(anim_target) = maybe_target {
2109 anim_target.register(world.components(), &mut resolver);
2110 }
2111
2112 // Actually step the tweenable and update the target
2113 if let Ok((target_type_id, component_id, target, is_retargetable)) =
2114 Self::resolve_target(
2115 world.components(),
2116 maybe_target,
2117 entity,
2118 anim.tweenable(),
2119 )
2120 {
2121 targets.push((
2122 entity,
2123 target_type_id,
2124 component_id,
2125 target,
2126 is_retargetable,
2127 ));
2128 }
2129 }
2130 }
2131 });
2132 Self::step_impl(world, delta_time, &targets[..]);
2133 targets.len()
2134 }
2135
2136 /// Step all animations on the given world.
2137 ///
2138 /// _This function is called automatically by the animation system
2139 /// registered by the [`TweeningPlugin`], you generally don't need to call
2140 /// it._
2141 ///
2142 /// Step all the [`TweenAnim`] components of the input world by a given
2143 /// `delta_time`, which may be [`Duration::ZERO`]. Passing a zero delta
2144 /// time may be useful to force the current animation state to be
2145 /// applied to a target, in case you made change which do not
2146 /// automatically do so (for example, retargeting an animation).
2147 pub fn step_all(world: &mut World, delta_time: Duration) {
2148 let targets = world.resource_scope(|world, mut resolver: Mut<TweenResolver>| {
2149 let mut q_anims = world.query::<(Entity, &TweenAnim, Option<&AnimTarget>)>();
2150 q_anims
2151 .iter(world)
2152 .filter_map(|(entity, anim, maybe_target)| {
2153 // Lazy registration with resolver if needed
2154 if let Some(anim_target) = maybe_target {
2155 anim_target.register(world.components(), &mut resolver);
2156 }
2157
2158 // Actually step the tweenable and update the target
2159 match Self::resolve_target(
2160 world.components(),
2161 maybe_target,
2162 entity,
2163 anim.tweenable(),
2164 ) {
2165 Ok((target_type_id, component_id, target, is_retargetable)) => Some((
2166 entity,
2167 target_type_id,
2168 component_id,
2169 target,
2170 is_retargetable,
2171 )),
2172 Err(err) => {
2173 bevy::log::error!(
2174 "Error while stepping TweenAnim on entity {:?}: {:?}",
2175 entity,
2176 err
2177 );
2178 None
2179 }
2180 }
2181 })
2182 .collect::<Vec<_>>()
2183 });
2184 Self::step_impl(world, delta_time, &targets[..]);
2185 }
2186
2187 fn resolve_target(
2188 components: &Components,
2189 maybe_target: Option<&AnimTarget>,
2190 anim_entity: Entity,
2191 tweenable: &dyn Tweenable,
2192 ) -> Result<(TypeId, ComponentId, AnimTargetKind, bool), TweeningError> {
2193 let type_id = tweenable
2194 .target_type_id()
2195 .ok_or(TweeningError::UntypedTweenable)?;
2196 if let Some(target) = maybe_target {
2197 // Target explicitly specified with AnimTarget component
2198 let component_id = match &target.kind {
2199 AnimTargetKind::Component { .. } => components
2200 .get_id(type_id)
2201 .ok_or(TweeningError::ComponentNotRegistered(type_id))?,
2202 AnimTargetKind::Resource => components
2203 .get_resource_id(type_id)
2204 .ok_or(TweeningError::ResourceNotRegistered(type_id))?,
2205 AnimTargetKind::Asset { assets_type_id, .. } => components
2206 .get_resource_id(*assets_type_id)
2207 .ok_or(TweeningError::AssetNotRegistered(type_id))?,
2208 };
2209 let is_retargetable = false; // explicit target
2210 Ok((type_id, component_id, target.kind, is_retargetable))
2211 } else {
2212 // Target implicitly self; this can only be a component target
2213 let is_retargetable = true;
2214 if let Some(component_id) = components.get_id(type_id) {
2215 Ok((
2216 type_id,
2217 component_id,
2218 AnimTargetKind::Component {
2219 entity: anim_entity,
2220 },
2221 is_retargetable,
2222 ))
2223 } else {
2224 // We can't implicitly target an asset without its AssetId
2225 Err(TweeningError::ComponentNotRegistered(type_id))
2226 }
2227 }
2228 }
2229
2230 fn step_impl(
2231 world: &mut World,
2232 delta_time: Duration,
2233 anims: &[(Entity, TypeId, ComponentId, AnimTargetKind, bool)],
2234 ) {
2235 let mut to_remove = Vec::with_capacity(anims.len());
2236 world.resource_scope(|world, resolver: Mut<TweenResolver>| {
2237 world.resource_scope(
2238 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
2239 world.resource_scope(
2240 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
2241 let anim_comp_id = world.component_id::<TweenAnim>().unwrap();
2242 for (
2243 anim_entity,
2244 target_type_id,
2245 component_id,
2246 anim_target,
2247 is_retargetable,
2248 ) in anims
2249 {
2250 let retain = match anim_target {
2251 AnimTargetKind::Component {
2252 entity: comp_entity,
2253 } => {
2254 let (mut entities, commands) =
2255 world.entities_and_commands();
2256 let ret = if *anim_entity == *comp_entity {
2257 // The TweenAnim animates another component on the same
2258 // entity
2259 let Ok([mut ent]) = entities.get_mut([*anim_entity])
2260 else {
2261 continue;
2262 };
2263 let Ok([anim, target]) =
2264 ent.get_mut_by_id([anim_comp_id, *component_id])
2265 else {
2266 continue;
2267 };
2268 // SAFETY: We fetched the EntityMut from the component
2269 // ID of
2270 // TweenAnim
2271 #[allow(unsafe_code)]
2272 let mut anim = unsafe { anim.with_type::<TweenAnim>() };
2273 anim.step_self(
2274 commands,
2275 *anim_entity,
2276 delta_time,
2277 anim_target,
2278 target,
2279 target_type_id,
2280 cycle_events.reborrow(),
2281 anim_events.reborrow(),
2282 )
2283 } else {
2284 // The TweenAnim animates a component on a different
2285 // entity
2286 let Ok([mut anim, mut target]) =
2287 entities.get_mut([*anim_entity, *comp_entity])
2288 else {
2289 continue;
2290 };
2291 let Some(mut anim) = anim.get_mut::<TweenAnim>() else {
2292 continue;
2293 };
2294 let Ok(target) = target.get_mut_by_id(*component_id)
2295 else {
2296 continue;
2297 };
2298 anim.step_self(
2299 commands,
2300 *anim_entity,
2301 delta_time,
2302 anim_target,
2303 target,
2304 target_type_id,
2305 cycle_events.reborrow(),
2306 anim_events.reborrow(),
2307 )
2308 };
2309 match ret {
2310 Ok(res) => {
2311 if res.needs_retarget {
2312 assert!(res.retain);
2313 if *is_retargetable {
2314 //to_retarget.push(anim_entity);
2315 //true
2316 bevy::log::warn!("TODO: Multi-target tweenable sequence is not yet supported. Ensure the animation of the TweenAnim component on entity {:?} targets a single component type.", *anim_entity);
2317 false
2318 } else {
2319 bevy::log::warn!("Multi-target tweenable sequence cannot be used with an explicit single target. Remove the AnimTarget component from entity {:?}, or ensure all tweenables in the sequence target the same component.", *anim_entity);
2320 false
2321 }
2322 } else {
2323 res.retain
2324 }
2325 }
2326 Err(_) => false,
2327 }
2328 }
2329 AnimTargetKind::Resource => resolver
2330 .resolve_resource(
2331 world,
2332 target_type_id,
2333 *component_id,
2334 *anim_entity,
2335 delta_time,
2336 cycle_events.reborrow(),
2337 anim_events.reborrow(),
2338 )
2339 .unwrap_or_else(|err| {
2340 bevy::log::error!(
2341 "Deleting resource animation due to error: {err:?}"
2342 );
2343 false
2344 }),
2345 AnimTargetKind::Asset { asset_id, .. } => resolver
2346 .resolve_asset(
2347 world,
2348 target_type_id,
2349 *component_id,
2350 *asset_id,
2351 *anim_entity,
2352 delta_time,
2353 cycle_events.reborrow(),
2354 anim_events.reborrow(),
2355 )
2356 .unwrap_or_else(|err| {
2357 bevy::log::error!(
2358 "Deleting asset animation due to error: {err:?}"
2359 );
2360 false
2361 }),
2362 };
2363
2364 if !retain {
2365 to_remove.push(*anim_entity);
2366 }
2367 }
2368 },
2369 );
2370 },
2371 );
2372 });
2373
2374 let mut cmds = world.commands();
2375 for entity in to_remove.drain(..) {
2376 cmds.entity(entity).try_remove::<TweenAnim>();
2377 }
2378
2379 world.flush();
2380 }
2381
2382 #[allow(clippy::too_many_arguments)]
2383 fn step_self(
2384 &mut self,
2385 mut commands: Commands,
2386 anim_entity: Entity,
2387 delta_time: Duration,
2388 target_kind: &AnimTargetKind,
2389 mut mut_untyped: MutUntyped,
2390 target_type_id: &TypeId,
2391 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2392 mut anim_events: Mut<Messages<AnimCompletedEvent>>,
2393 ) -> Result<StepResult, TweeningError> {
2394 let mut completed_events = Vec::with_capacity(8);
2395
2396 // Sanity checks on fields which can be freely modified by the user
2397 self.speed = self.speed.max(0.);
2398
2399 // Retain completed animations only if requested
2400 if self.tween_state == TweenState::Completed {
2401 let ret = StepResult {
2402 retain: !self.destroy_on_completion,
2403 needs_retarget: false,
2404 };
2405 return Ok(ret);
2406 }
2407
2408 // Skip paused animations (but retain them)
2409 if self.playback_state == PlaybackState::Paused || self.speed <= 0. {
2410 let ret = StepResult {
2411 retain: true,
2412 needs_retarget: false,
2413 };
2414 return Ok(ret);
2415 }
2416
2417 // Scale delta time by this animation's speed. Reject negative speeds; use
2418 // backward playback to play in reverse direction.
2419 // Note: must use f64 for precision; f32 produces visible roundings.
2420 let delta_time = delta_time.mul_f64(self.speed);
2421
2422 // Step the tweenable animation
2423 let mut notify_completed = || {
2424 completed_events.push(CycleCompletedEvent {
2425 anim_entity,
2426 target: *target_kind,
2427 });
2428 };
2429 let (state, needs_retarget) = self.tweenable.step(
2430 anim_entity,
2431 delta_time,
2432 mut_untyped.reborrow(),
2433 target_type_id,
2434 &mut notify_completed,
2435 );
2436 self.tween_state = state;
2437
2438 // Send tween completed events once we reclaimed mut access to world and can get
2439 // a Commands.
2440 if !completed_events.is_empty() {
2441 for event in completed_events.drain(..) {
2442 // Send buffered event
2443 cycle_events.write(event);
2444
2445 // Trigger all entity-scoped observers
2446 commands.trigger(CycleCompletedEvent {
2447 anim_entity,
2448 ..event
2449 });
2450 }
2451 }
2452
2453 // Raise animation completed event
2454 if state == TweenState::Completed {
2455 let event: AnimCompletedEvent = AnimCompletedEvent {
2456 anim_entity,
2457 target: *target_kind,
2458 };
2459
2460 // Send buffered event
2461 anim_events.write(event);
2462
2463 // Trigger all entity-scoped observers
2464 commands.trigger(event);
2465 }
2466
2467 let ret = StepResult {
2468 retain: state == TweenState::Active || !self.destroy_on_completion,
2469 needs_retarget,
2470 };
2471 Ok(ret)
2472 }
2473
2474 /// Stop animation playback and rewind the animation.
2475 ///
2476 /// This changes the animator state to [`PlaybackState::Paused`] and rewinds
2477 /// its tweenable.
2478 ///
2479 /// # Panics
2480 ///
2481 /// Like [`Tweenable::rewind()`], this panics if the current playback
2482 /// direction is [`PlaybackDirection::Backward`] and the animation is
2483 /// infinitely repeating.
2484 pub fn stop(&mut self) {
2485 self.playback_state = PlaybackState::Paused;
2486 self.tweenable.rewind();
2487 self.tween_state = TweenState::Active;
2488 }
2489
2490 /// Get the tweenable describing this animation.
2491 ///
2492 /// To change the tweenable, use [`TweenAnim::set_tweenable()`].
2493 #[inline]
2494 pub fn tweenable(&self) -> &dyn Tweenable {
2495 self.tweenable.as_ref()
2496 }
2497
2498 /// Set a new animation description.
2499 ///
2500 /// Attempt to change the tweenable of an animation already spawned.
2501 ///
2502 /// If the tweenable is successfully swapped, this resets the
2503 /// [`tween_state()`] to [`TweenState::Active`], even if the tweenable would
2504 /// otherwise be completed _e.g._ because its current elapsed time is past
2505 /// its total duration. Conversely, this doesn't update the target
2506 /// component or asset, as this function doesn't have mutable access to
2507 /// it. To force applying the new state to the target without stepping the
2508 /// animation forward or backward, call one of the stepping functions like
2509 /// [`TweenAnim::step_one()`] passing a delta time of [`Duration::ZERO`].
2510 ///
2511 /// To ensure the old and new animations have the same elapsed time (for
2512 /// example if they need to be synchronized, if they're variants of each
2513 /// other), call [`set_elapsed()`] first on the input `tweenable`, with
2514 /// the duration value of the old tweenable returned by [`elapsed()`].
2515 ///
2516 /// ```
2517 /// # use std::time::Duration;
2518 /// # use bevy::prelude::*;
2519 /// # use bevy_tweening::*;
2520 /// # fn make_tweenable() -> Tween { unimplemented!() }
2521 /// fn my_system(mut anim: Single<&mut TweenAnim>) {
2522 /// let mut tweenable = make_tweenable();
2523 /// let elapsed = anim.tweenable().elapsed();
2524 /// tweenable.set_elapsed(elapsed);
2525 /// anim.set_tweenable(tweenable);
2526 /// }
2527 /// ```
2528 ///
2529 /// # Returns
2530 ///
2531 /// On success, returns the previous tweenable which has been swapped out.
2532 ///
2533 /// [`tween_state()`]: Self::tween_state
2534 /// [`set_elapsed()`]: crate::Tweenable::set_elapsed
2535 /// [`elapsed()`]: crate::Tweenable::elapsed
2536 /// [`step_one()`]: Self::step_one
2537 pub fn set_tweenable<T>(&mut self, tweenable: T) -> Result<BoxedTweenable, TweeningError>
2538 where
2539 T: Tweenable + 'static,
2540 {
2541 let mut old_tweenable: BoxedTweenable = Box::new(tweenable);
2542 std::mem::swap(&mut self.tweenable, &mut old_tweenable);
2543 // Reset tweening state, the new tweenable is at t=0
2544 self.tween_state = TweenState::Active;
2545 Ok(old_tweenable)
2546 }
2547
2548 /// Get the tweening completion state.
2549 ///
2550 /// In general this is [`TweenState::Active`], unless the animation
2551 /// completed and [`destroy_on_completion`] is `false`.
2552 ///
2553 /// [`destroy_on_completion`]: Self::destroy_on_completion
2554 #[inline]
2555 pub fn tween_state(&self) -> TweenState {
2556 self.tween_state
2557 }
2558}
2559
2560type ResourceResolver = Box<
2561 dyn for<'w> Fn(
2562 &mut World,
2563 Entity,
2564 &TypeId,
2565 Duration,
2566 Mut<Messages<CycleCompletedEvent>>,
2567 Mut<Messages<AnimCompletedEvent>>,
2568 ) -> Result<bool, TweeningError>
2569 + Send
2570 + Sync
2571 + 'static,
2572>;
2573
2574type AssetResolver = Box<
2575 dyn for<'w> Fn(
2576 &mut World,
2577 UntypedAssetId,
2578 Entity,
2579 &TypeId,
2580 Duration,
2581 Mut<Messages<CycleCompletedEvent>>,
2582 Mut<Messages<AnimCompletedEvent>>,
2583 ) -> Result<bool, TweeningError>
2584 + Send
2585 + Sync
2586 + 'static,
2587>;
2588
2589/// Resolver for resources and assets.
2590///
2591/// _This resource is largely an implementation detail. You can safely ignore
2592/// it._
2593///
2594/// Bevy doesn't provide a suitable untyped API to access resources and assets
2595/// at runtime without knowing their compile-time type.
2596/// - For resources, most of the API is in place, but unfortunately there's no
2597/// `World::resource_scope_untyped()` to temporarily extract a resource by ID
2598/// to allow concurrent mutability of the resource with other parts of the
2599/// [`World`], in particular the animation target.
2600/// - For assets, there's simply no untyped API. [`Assets`] doesn't allow
2601/// untyped asset access.
2602///
2603/// To work around those limitations, this resolver resource contains
2604/// type-erased closures allowing to resolve an animation target definition into
2605/// a mutable pointer [`MutUntyped`] to that instance, to allow the animation
2606/// engine to apply the animation on it.
2607#[derive(Default, Resource)]
2608pub struct TweenResolver {
2609 /// Resource resolver allowing to call `World::resource_scope()` to extract
2610 /// that resource type form the `World` while in parallel accessing mutably
2611 /// the animation entity itself.
2612 resource_resolver: HashMap<ComponentId, ResourceResolver>,
2613 /// Asset resolver allowing to convert a pair of { untyped pointer to
2614 /// `Assets<A>`, untyped `AssetId` } into an untyped pointer to the asset A
2615 /// itself. This is necessary because there's no UntypedAssets interface in
2616 /// Bevy. The TypeId key must be the type of the `Assets<A>` type itself.
2617 /// The resolver is allowed to fail (return `None`), for example when the
2618 /// asset ID doesn't reference a valid asset.
2619 asset_resolver: HashMap<ComponentId, AssetResolver>,
2620}
2621
2622impl TweenResolver {
2623 /// Register a resolver for the given resource type.
2624 pub(crate) fn register_resource_resolver_for<R: Resource>(&mut self, components: &Components) {
2625 let resource_id = components.resource_id::<R>().unwrap();
2626 let resolver = |world: &mut World,
2627 entity: Entity,
2628 target_type_id: &TypeId,
2629 delta_time: Duration,
2630 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2631 mut anim_events: Mut<Messages<AnimCompletedEvent>>|
2632 -> Result<bool, TweeningError> {
2633 // First, remove the resource R from the world so we can access it mutably in
2634 // parallel of the TweenAnim
2635 world.resource_scope(|world, resource: Mut<R>| {
2636 let target = AnimTargetKind::Resource;
2637
2638 let (mut entities, commands) = world.entities_and_commands();
2639
2640 // Resolve the TweenAnim component
2641 let Ok([mut ent]) = entities.get_mut([entity]) else {
2642 return Err(TweeningError::EntityNotFound(entity));
2643 };
2644 let Some(mut anim) = ent.get_mut::<TweenAnim>() else {
2645 return Err(TweeningError::MissingTweenAnim(ent.id()));
2646 };
2647
2648 // Finally, step the TweenAnim and mutate the target
2649 let ret = anim.step_self(
2650 commands,
2651 entity,
2652 delta_time,
2653 &target,
2654 resource.into(),
2655 target_type_id,
2656 cycle_events.reborrow(),
2657 anim_events.reborrow(),
2658 );
2659 ret.map(|result| {
2660 assert!(!result.needs_retarget, "Cannot use a multi-target sequence of tweenable animations with a resource target.");
2661 result.retain
2662 })
2663 })
2664 };
2665 self.resource_resolver
2666 .entry(resource_id)
2667 .or_insert(Box::new(resolver));
2668 }
2669
2670 /// Register a resolver for the given asset type.
2671 pub(crate) fn register_asset_resolver_for<A: Asset>(&mut self, components: &Components) {
2672 let resource_id = components.resource_id::<Assets<A>>().unwrap();
2673 let resolver = |world: &mut World,
2674 asset_id: UntypedAssetId,
2675 entity: Entity,
2676 target_type_id: &TypeId,
2677 delta_time: Duration,
2678 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2679 mut anim_events: Mut<Messages<AnimCompletedEvent>>|
2680 -> Result<bool, TweeningError> {
2681 let asset_id = asset_id.typed::<A>();
2682 // First, remove the Assets<A> from the world so we can access it mutably in
2683 // parallel of the TweenAnim
2684 world.resource_scope(|world, assets: Mut<Assets<A>>| {
2685 // Next, fetch the asset A itself from its Assets<A> based on its asset ID
2686 let Some(asset) = assets.filter_map_unchanged(|assets| assets.get_mut(asset_id))
2687 else {
2688 return Err(TweeningError::InvalidAssetId(asset_id.into()));
2689 };
2690
2691 let target = AnimTargetKind::Asset {
2692 asset_id: asset_id.untyped(),
2693 assets_type_id: TypeId::of::<Assets<A>>(),
2694 };
2695
2696 let (mut entities, commands) = world.entities_and_commands();
2697
2698 // Resolve the TweenAnim component
2699 let Ok([mut ent]) = entities.get_mut([entity]) else {
2700 return Err(TweeningError::EntityNotFound(entity));
2701 };
2702 let Some(mut anim) = ent.get_mut::<TweenAnim>() else {
2703 return Err(TweeningError::MissingTweenAnim(ent.id()));
2704 };
2705
2706 // Finally, step the TweenAnim and mutate the target
2707 let ret = anim.step_self(
2708 commands,
2709 entity,
2710 delta_time,
2711 &target,
2712 asset.into(),
2713 target_type_id,
2714 cycle_events.reborrow(),
2715 anim_events.reborrow(),
2716 );
2717 ret.map(|result| {
2718 assert!(!result.needs_retarget, "Cannot use a multi-target sequence of tweenable animations with an asset target.");
2719 result.retain
2720 })
2721 })
2722 };
2723 self.asset_resolver
2724 .entry(resource_id)
2725 .or_insert(Box::new(resolver));
2726 }
2727
2728 #[allow(clippy::too_many_arguments)]
2729 #[inline]
2730 pub(crate) fn resolve_resource(
2731 &self,
2732 world: &mut World,
2733 target_type_id: &TypeId,
2734 resource_id: ComponentId,
2735 entity: Entity,
2736 delta_time: Duration,
2737 cycle_events: Mut<Messages<CycleCompletedEvent>>,
2738 anim_events: Mut<Messages<AnimCompletedEvent>>,
2739 ) -> Result<bool, TweeningError> {
2740 let Some(resolver) = self.resource_resolver.get(&resource_id) else {
2741 println!("ERROR: resource not registered {:?}", resource_id);
2742 return Err(TweeningError::AssetResolverNotRegistered(resource_id));
2743 };
2744 resolver(
2745 world,
2746 entity,
2747 target_type_id,
2748 delta_time,
2749 cycle_events,
2750 anim_events,
2751 )
2752 }
2753
2754 #[allow(clippy::too_many_arguments)]
2755 #[inline]
2756 pub(crate) fn resolve_asset(
2757 &self,
2758 world: &mut World,
2759 target_type_id: &TypeId,
2760 resource_id: ComponentId,
2761 untyped_asset_id: UntypedAssetId,
2762 entity: Entity,
2763 delta_time: Duration,
2764 cycle_events: Mut<Messages<CycleCompletedEvent>>,
2765 anim_events: Mut<Messages<AnimCompletedEvent>>,
2766 ) -> Result<bool, TweeningError> {
2767 let Some(resolver) = self.asset_resolver.get(&resource_id) else {
2768 println!("ERROR: asset not registered {:?}", resource_id);
2769 return Err(TweeningError::AssetResolverNotRegistered(resource_id));
2770 };
2771 resolver(
2772 world,
2773 untyped_asset_id,
2774 entity,
2775 target_type_id,
2776 delta_time,
2777 cycle_events,
2778 anim_events,
2779 )
2780 }
2781}
2782
2783pub(crate) struct StepResult {
2784 /// Whether to retain the current [`TweenAnim`]? If `false`, the
2785 /// [`TweenAnim`] is destroyed unless [`TweenAnim::destroy_on_completion`]
2786 /// is `false`.
2787 pub retain: bool,
2788 /// Whether to recompute the new animation target and step again. This is
2789 /// used by sequences when the animation target changes type in a sequence.
2790 pub needs_retarget: bool,
2791}
2792
2793#[cfg(test)]
2794mod tests {
2795 use std::{
2796 f32::consts::{FRAC_PI_2, TAU},
2797 marker::PhantomData,
2798 };
2799
2800 use bevy::ecs::{change_detection::MaybeLocation, change_detection::Tick};
2801
2802 use super::*;
2803 use crate::test_utils::*;
2804
2805 struct DummyLens {
2806 start: f32,
2807 end: f32,
2808 }
2809
2810 struct DummyLens2 {
2811 start: i32,
2812 end: i32,
2813 }
2814
2815 #[derive(Debug, Default, Clone, Copy, Component)]
2816 struct DummyComponent {
2817 value: f32,
2818 }
2819
2820 #[derive(Debug, Default, Clone, Copy, Component)]
2821 struct DummyComponent2 {
2822 value: i32,
2823 }
2824
2825 #[derive(Debug, Default, Clone, Copy, Resource)]
2826 struct DummyResource {
2827 value: f32,
2828 }
2829
2830 #[derive(Asset, Debug, Default, Reflect)]
2831 struct DummyAsset {
2832 value: f32,
2833 }
2834
2835 impl Lens<DummyComponent> for DummyLens {
2836 fn lerp(&mut self, mut target: Mut<DummyComponent>, ratio: f32) {
2837 target.value = self.start.lerp(self.end, ratio);
2838 }
2839 }
2840
2841 impl Lens<DummyComponent2> for DummyLens2 {
2842 fn lerp(&mut self, mut target: Mut<DummyComponent2>, ratio: f32) {
2843 target.value = ((self.start as f32) * (1. - ratio) + (self.end as f32) * ratio) as i32;
2844 }
2845 }
2846
2847 #[test]
2848 fn dummy_lens_component() {
2849 let mut c = DummyComponent::default();
2850 let mut l = DummyLens { start: 0., end: 1. };
2851 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2852 {
2853 let mut added = Tick::new(0);
2854 let mut last_changed = Tick::new(0);
2855 let mut caller = MaybeLocation::caller();
2856 let mut target = Mut::new(
2857 &mut c,
2858 &mut added,
2859 &mut last_changed,
2860 Tick::new(0),
2861 Tick::new(1),
2862 caller.as_mut(),
2863 );
2864
2865 l.lerp(target.reborrow(), r);
2866
2867 assert!(target.is_changed());
2868 }
2869 assert_approx_eq!(c.value, r);
2870 }
2871 }
2872
2873 impl Lens<DummyResource> for DummyLens {
2874 fn lerp(&mut self, mut target: Mut<DummyResource>, ratio: f32) {
2875 target.value = self.start.lerp(self.end, ratio);
2876 }
2877 }
2878
2879 #[test]
2880 fn dummy_lens_resource() {
2881 let mut res = DummyResource::default();
2882 let mut l = DummyLens { start: 0., end: 1. };
2883 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2884 {
2885 let mut added = Tick::new(0);
2886 let mut last_changed = Tick::new(0);
2887 let mut caller = MaybeLocation::caller();
2888 let mut target = Mut::new(
2889 &mut res,
2890 &mut added,
2891 &mut last_changed,
2892 Tick::new(0),
2893 Tick::new(0),
2894 caller.as_mut(),
2895 );
2896 l.lerp(target.reborrow(), r);
2897 }
2898 assert_approx_eq!(res.value, r);
2899 }
2900 }
2901
2902 impl Lens<DummyAsset> for DummyLens {
2903 fn lerp(&mut self, mut target: Mut<DummyAsset>, ratio: f32) {
2904 target.value = self.start.lerp(self.end, ratio);
2905 }
2906 }
2907
2908 #[test]
2909 fn dummy_lens_asset() {
2910 let mut assets = Assets::<DummyAsset>::default();
2911 let handle = assets.add(DummyAsset::default());
2912
2913 let mut l = DummyLens { start: 0., end: 1. };
2914 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2915 {
2916 let mut added = Tick::new(0);
2917 let mut last_changed = Tick::new(0);
2918 let mut caller = MaybeLocation::caller();
2919 let asset = assets.get_mut(handle.id()).unwrap();
2920 let target = Mut::new(
2921 asset,
2922 &mut added,
2923 &mut last_changed,
2924 Tick::new(0),
2925 Tick::new(0),
2926 caller.as_mut(),
2927 );
2928 l.lerp(target, r);
2929 }
2930 assert_approx_eq!(assets.get(handle.id()).unwrap().value, r);
2931 }
2932 }
2933
2934 #[test]
2935 fn repeat_count() {
2936 let cycle_duration = Duration::from_millis(100);
2937
2938 let repeat = RepeatCount::default();
2939 assert_eq!(repeat, RepeatCount::Finite(1));
2940 assert_eq!(
2941 repeat.total_duration(cycle_duration),
2942 TotalDuration::Finite(cycle_duration)
2943 );
2944
2945 let repeat: RepeatCount = 3u32.into();
2946 assert_eq!(repeat, RepeatCount::Finite(3));
2947 assert_eq!(
2948 repeat.total_duration(cycle_duration),
2949 TotalDuration::Finite(cycle_duration * 3)
2950 );
2951
2952 let duration = Duration::from_secs(5);
2953 let repeat: RepeatCount = duration.into();
2954 assert_eq!(repeat, RepeatCount::For(duration));
2955 assert_eq!(
2956 repeat.total_duration(cycle_duration),
2957 TotalDuration::Finite(duration)
2958 );
2959
2960 let repeat = RepeatCount::Infinite;
2961 assert_eq!(
2962 repeat.total_duration(cycle_duration),
2963 TotalDuration::Infinite
2964 );
2965 }
2966
2967 #[test]
2968 fn repeat_strategy() {
2969 let strategy = RepeatStrategy::default();
2970 assert_eq!(strategy, RepeatStrategy::Repeat);
2971 }
2972
2973 #[test]
2974 fn playback_direction() {
2975 let tweening_direction = PlaybackDirection::default();
2976 assert_eq!(tweening_direction, PlaybackDirection::Forward);
2977 }
2978
2979 #[test]
2980 fn playback_state() {
2981 let mut state = PlaybackState::default();
2982 assert_eq!(state, PlaybackState::Playing);
2983 state = !state;
2984 assert_eq!(state, PlaybackState::Paused);
2985 state = !state;
2986 assert_eq!(state, PlaybackState::Playing);
2987 }
2988
2989 #[test]
2990 fn ease_method() {
2991 let ease = EaseMethod::default();
2992 assert!(matches!(
2993 ease,
2994 EaseMethod::EaseFunction(EaseFunction::Linear)
2995 ));
2996
2997 let ease = EaseMethod::EaseFunction(EaseFunction::QuadraticIn);
2998 assert_eq!(0., ease.sample(0.));
2999 assert_eq!(0.25, ease.sample(0.5));
3000 assert_eq!(1., ease.sample(1.));
3001
3002 let ease = EaseMethod::EaseFunction(EaseFunction::Linear);
3003 assert_eq!(0., ease.sample(0.));
3004 assert_eq!(0.5, ease.sample(0.5));
3005 assert_eq!(1., ease.sample(1.));
3006
3007 let ease = EaseMethod::Discrete(0.3);
3008 assert_eq!(0., ease.sample(0.));
3009 assert_eq!(1., ease.sample(0.5));
3010 assert_eq!(1., ease.sample(1.));
3011
3012 let ease = EaseMethod::CustomFunction(|f| 1. - f);
3013 assert_eq!(0., ease.sample(1.));
3014 assert_eq!(0.5, ease.sample(0.5));
3015 assert_eq!(1., ease.sample(0.));
3016 }
3017
3018 // TweenAnim::playback_state is entirely user-controlled; stepping animations
3019 // won't change it.
3020 #[test]
3021 fn animation_playback_state() {
3022 for state in [PlaybackState::Playing, PlaybackState::Paused] {
3023 let tween = Tween::new::<DummyComponent, DummyLens>(
3024 EaseFunction::QuadraticInOut,
3025 Duration::from_secs(1),
3026 DummyLens { start: 0., end: 1. },
3027 );
3028 let mut env = TestEnv::<DummyComponent>::new(tween);
3029 let mut anim = env.anim_mut().unwrap();
3030 anim.playback_state = state;
3031 anim.destroy_on_completion = false;
3032
3033 // Tick once
3034 let dt = Duration::from_millis(100);
3035 env.step_all(dt);
3036 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3037 assert_eq!(env.anim().unwrap().playback_state, state);
3038
3039 // Check elapsed
3040 let elapsed = match state {
3041 PlaybackState::Playing => dt,
3042 PlaybackState::Paused => Duration::ZERO,
3043 };
3044 assert_eq!(env.anim().unwrap().tweenable.elapsed(), elapsed);
3045
3046 // Force playback, otherwise we can't complete
3047 env.anim_mut().unwrap().playback_state = PlaybackState::Playing;
3048
3049 // Even after completion, the playback state is untouched
3050 env.step_all(Duration::from_secs(10) - elapsed);
3051 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Completed);
3052 assert_eq!(env.anim().unwrap().playback_state, PlaybackState::Playing);
3053 }
3054 }
3055
3056 #[test]
3057 fn animation_events() {
3058 let tween = Tween::new::<DummyComponent, DummyLens>(
3059 EaseFunction::QuadraticInOut,
3060 Duration::from_secs(1),
3061 DummyLens { start: 0., end: 1. },
3062 )
3063 .with_repeat_count(2)
3064 .with_cycle_completed_event(true);
3065 let mut env = TestEnv::<DummyComponent>::new(tween);
3066
3067 // Tick until one cycle is completed, but not the entire animation
3068 let dt = Duration::from_millis(1200);
3069 env.step_all(dt);
3070 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3071
3072 // Check events
3073 assert_eq!(env.event_count::<CycleCompletedEvent>(), 1);
3074 assert_eq!(env.event_count::<AnimCompletedEvent>(), 0);
3075
3076 // Tick until completion
3077 let dt = Duration::from_millis(1000);
3078 env.step_all(dt);
3079 assert!(env.anim().is_none());
3080
3081 // Check events (note that we didn't clear previous events, so that's a
3082 // cumulative count).
3083 assert_eq!(env.event_count::<CycleCompletedEvent>(), 1);
3084 assert_eq!(env.event_count::<AnimCompletedEvent>(), 1);
3085 }
3086
3087 #[derive(Debug, Resource)]
3088 struct Count<E: Event, T = ()> {
3089 pub count: i32,
3090 pub phantom: PhantomData<E>,
3091 pub phantom2: PhantomData<T>,
3092 }
3093
3094 impl<E: Event, T> Default for Count<E, T> {
3095 fn default() -> Self {
3096 Self {
3097 count: 0,
3098 phantom: PhantomData,
3099 phantom2: PhantomData,
3100 }
3101 }
3102 }
3103
3104 struct GlobalMarker;
3105
3106 #[test]
3107 fn animation_observe() {
3108 let tween = Tween::new::<DummyComponent, DummyLens>(
3109 EaseFunction::QuadraticInOut,
3110 Duration::from_secs(1),
3111 DummyLens { start: 0., end: 1. },
3112 )
3113 .with_repeat_count(2)
3114 .with_cycle_completed_event(true);
3115 let mut env = TestEnv::<DummyComponent>::new(tween);
3116
3117 env.world.init_resource::<Count<CycleCompletedEvent>>();
3118 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 0);
3119 env.world
3120 .init_resource::<Count<CycleCompletedEvent, GlobalMarker>>();
3121 assert_eq!(
3122 env.world
3123 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3124 .count,
3125 0
3126 );
3127
3128 fn observe_global(
3129 _trigger: On<CycleCompletedEvent>,
3130 mut count: ResMut<Count<CycleCompletedEvent, GlobalMarker>>,
3131 ) {
3132 count.count += 1;
3133 }
3134 env.world.add_observer(observe_global);
3135
3136 fn observe_entity(
3137 _trigger: On<CycleCompletedEvent>,
3138 mut count: ResMut<Count<CycleCompletedEvent>>,
3139 ) {
3140 count.count += 1;
3141 }
3142 env.world.entity_mut(env.entity).observe(observe_entity);
3143
3144 // Tick until one cycle is completed, but not the entire animation
3145 let dt = Duration::from_millis(1200);
3146 env.step_all(dt);
3147 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3148
3149 // Check observer system ran
3150 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 1);
3151 assert_eq!(
3152 env.world
3153 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3154 .count,
3155 1
3156 );
3157
3158 // Tick until completion
3159 let dt = Duration::from_millis(1000);
3160 env.step_all(dt);
3161 assert!(env.anim().is_none());
3162
3163 // Check observer system ran (note that we didn't clear previous events, so
3164 // that's a cumulative count).
3165 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 2);
3166 assert_eq!(
3167 env.world
3168 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3169 .count,
3170 2
3171 );
3172 }
3173
3174 // #[test]
3175 // fn animator_controls() {
3176 // let tween = Tween::<DummyComponent>::new(
3177 // EaseFunction::QuadraticInOut,
3178 // Duration::from_secs(1),
3179 // DummyLens { start: 0., end: 1. },
3180 // );
3181 // let mut animator = Animator::new(tween);
3182 // assert_eq!(animator.state, AnimatorState::Playing);
3183 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3184
3185 // animator.stop();
3186 // assert_eq!(animator.state, AnimatorState::Paused);
3187 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3188
3189 // animator.tweenable_mut().set_progress(0.5);
3190 // assert_eq!(animator.state, AnimatorState::Paused);
3191 // assert_approx_eq!(animator.tweenable().progress(), 0.5);
3192
3193 // animator.tweenable_mut().rewind();
3194 // assert_eq!(animator.state, AnimatorState::Paused);
3195 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3196
3197 // animator.tweenable_mut().set_progress(0.5);
3198 // animator.state = AnimatorState::Playing;
3199 // assert_eq!(animator.state, AnimatorState::Playing);
3200 // assert_approx_eq!(animator.tweenable().progress(), 0.5);
3201
3202 // animator.tweenable_mut().rewind();
3203 // assert_eq!(animator.state, AnimatorState::Playing);
3204 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3205
3206 // animator.stop();
3207 // assert_eq!(animator.state, AnimatorState::Paused);
3208 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3209 // }
3210
3211 #[test]
3212 fn animation_speed() {
3213 let tween = Tween::new::<DummyComponent, DummyLens>(
3214 EaseFunction::QuadraticInOut,
3215 Duration::from_secs(1),
3216 DummyLens { start: 0., end: 1. },
3217 );
3218
3219 let mut env = TestEnv::<DummyComponent>::new(tween);
3220
3221 assert_approx_eq!(env.anim().unwrap().speed, 1.); // default speed
3222
3223 env.anim_mut().unwrap().speed = 2.4;
3224 assert_approx_eq!(env.anim().unwrap().speed, 2.4);
3225
3226 env.step_all(Duration::from_millis(100));
3227 // Here we have enough precision for exact equality, but that may not always be
3228 // the case for larger durations or speed values.
3229 assert_eq!(
3230 env.anim().unwrap().tweenable.elapsed(),
3231 Duration::from_millis(240)
3232 );
3233
3234 env.anim_mut().unwrap().speed = -1.;
3235 env.step_all(Duration::from_millis(100));
3236 // Safety: invalid negative speed clamped to 0.
3237 assert_eq!(env.anim().unwrap().speed, 0.);
3238 // At zero speed, step is a no-op so elapse() didn't change
3239 assert_eq!(
3240 env.anim().unwrap().tweenable.elapsed(),
3241 Duration::from_millis(240)
3242 );
3243 }
3244
3245 #[test]
3246 fn animator_set_tweenable() {
3247 let tween = Tween::new::<DummyComponent, DummyLens>(
3248 EaseFunction::QuadraticInOut,
3249 Duration::from_secs(1),
3250 DummyLens { start: 0., end: 1. },
3251 );
3252 let tween2 = Tween::new::<DummyComponent, DummyLens>(
3253 EaseFunction::SmoothStep,
3254 Duration::from_secs(2),
3255 DummyLens { start: 2., end: 3. },
3256 );
3257
3258 let mut env = TestEnv::<DummyComponent>::new(tween);
3259 env.anim_mut().unwrap().destroy_on_completion = false;
3260
3261 let dt = Duration::from_millis(1500);
3262
3263 env.step_all(dt);
3264 assert_eq!(env.component().value, 1.);
3265 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Completed);
3266
3267 // Swap tweens
3268 let old_tweenable = env.anim_mut().unwrap().set_tweenable(tween2).unwrap();
3269
3270 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3271 // The elapsed is stored inside the tweenable
3272 assert_eq!(old_tweenable.elapsed(), Duration::from_secs(1)); // capped at total_duration()
3273 assert_eq!(env.anim().unwrap().tweenable.elapsed(), Duration::ZERO);
3274
3275 env.step_all(dt);
3276 assert!(env.component().value >= 2. && env.component().value <= 3.);
3277 }
3278
3279 // Currently multi-target sequences are not implemented. This _could_ work with
3280 // implicit targets (so, multiple components on the same entity), but is a bit
3281 // complex to implement with the current code. So leave that out for now, and
3282 // test we assert if the user attempts it. The workaround is to create separate
3283 // animations for each comopnent/target. Anyway multi-target sequence can't work
3284 // with other target types, since they need an explicit TweenAnim, and we
3285 // can't have more than one per entity.
3286 #[test]
3287 #[should_panic(
3288 expected = "TODO: Cannot use tweenable animations with different targets inside the same Sequence. Create separate animations for each target."
3289 )]
3290 fn seq_multi_target() {
3291 let tween = Tween::new::<DummyComponent, DummyLens>(
3292 EaseFunction::QuadraticInOut,
3293 Duration::from_secs(1),
3294 DummyLens { start: 0., end: 1. },
3295 )
3296 .then(Tween::new::<DummyComponent2, DummyLens2>(
3297 EaseFunction::SmoothStep,
3298 Duration::from_secs(1),
3299 DummyLens2 { start: -5, end: 5 },
3300 ));
3301 let mut env = TestEnv::<DummyComponent>::new(tween);
3302 let entity = env.entity;
3303 env.world
3304 .entity_mut(entity)
3305 .insert(DummyComponent2 { value: -42 });
3306 TweenAnim::step_one(&mut env.world, Duration::from_millis(1100), entity).unwrap();
3307 }
3308
3309 // #[test]
3310 // fn animator_set_target() {
3311 // let tween = Tween::new::<DummyComponent, DummyLens>(
3312 // EaseFunction::QuadraticInOut,
3313 // Duration::from_secs(1),
3314 // DummyLens { start: 0., end: 1. },
3315 // );
3316 // let mut env = TestEnv::<DummyComponent>::new(tween);
3317
3318 // // Register our custom asset type
3319 // env.world.init_resource::<Assets<DummyAsset>>();
3320
3321 // // Invalid ID
3322 // {
3323 // let entity = env.entity;
3324 // let target =
3325 //
3326 // ComponentAnimTarget::new::<DummyComponent>(env.world.components(),
3327 // entity).unwrap(); let err = env
3328 // .animator_mut()
3329 // .set_target(Entity::PLACEHOLDER, target.into())
3330 // .err()
3331 // .unwrap();
3332 // let TweeningError::InvalidTweenId(err_id) = err else {
3333 // panic!();
3334 // };
3335 // assert_eq!(err_id, Entity::PLACEHOLDER);
3336 // }
3337
3338 // // Spawn a second entity without any animation
3339 // let entity1 = env.entity;
3340 // let entity2 = env.world_mut().spawn(DummyComponent { value: 0.
3341 // }).id(); assert_ne!(entity1, entity2);
3342 // assert_eq!(env.component().value, 0.);
3343
3344 // // Step the current target
3345 // let dt = Duration::from_millis(100);
3346 // env.step_all(dt);
3347 // assert!(env.component().value > 0.);
3348 // assert_eq!(
3349 // env.world
3350 // .entity(entity2)
3351 // .get_components::<&DummyComponent>()
3352 // .unwrap()
3353 // .value,
3354 // 0.
3355 // );
3356
3357 // // Now retarget
3358 // let id = env.entity;
3359 // let target2 =
3360 // ComponentAnimTarget::new::<DummyComponent>(env.world.
3361 // components(), entity2).unwrap(); let target1 =
3362 // env.animator_mut().set_target(id, target2.into()).unwrap();
3363 // assert!(target1.is_component());
3364 // let comp1 = target1.as_component().unwrap();
3365 // assert_eq!(comp1.entity, entity1);
3366 // assert_eq!(
3367 // comp1.component_id,
3368 // env.world.component_id::<DummyComponent>().unwrap()
3369 // );
3370
3371 // // Step the new target
3372 // env.step_all(dt);
3373 // assert!(env.component().value > 0.);
3374 // assert!(
3375 // env.world
3376 // .entity(entity1)
3377 // .get_components::<&DummyComponent>()
3378 // .unwrap()
3379 // .value
3380 // > 0.
3381 // );
3382
3383 // // Invalid target
3384 // {
3385 // let target3 =
3386 // AssetAnimTarget::new(env.world.components(),
3387 // Handle::<DummyAsset>::default().id()) .unwrap();
3388 // let err3 = env.animator_mut().set_target(id, target3.into());
3389 // assert!(err3.is_err());
3390 // let err3 = err3.err().unwrap();
3391 // let TweeningError::MismatchingTargetKind(oc, nc) = err3 else {
3392 // panic!();
3393 // };
3394 // assert_eq!(oc, true);
3395 // assert_eq!(nc, false);
3396 // }
3397 // }
3398
3399 #[test]
3400 fn anim_target_component() {
3401 let mut env = TestEnv::<Transform>::empty();
3402 let entity = env.world.spawn(Transform::default()).id();
3403 let tween = Tween::new::<Transform, TransformPositionLens>(
3404 EaseFunction::Linear,
3405 Duration::from_secs(1),
3406 TransformPositionLens {
3407 start: Vec3::ZERO,
3408 end: Vec3::ONE,
3409 },
3410 );
3411 let target = AnimTarget::component::<Transform>(entity);
3412 let anim_entity = env
3413 .world
3414 .spawn((
3415 TweenAnim::new(tween)
3416 .with_speed(2.)
3417 .with_destroy_on_completed(true),
3418 target,
3419 ))
3420 .id();
3421
3422 // Step
3423 assert!(
3424 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3425 );
3426 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3427 assert_eq!(tr.translation, Vec3::ONE * 0.2);
3428
3429 // Complete
3430 assert_eq!(
3431 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3432 1
3433 );
3434
3435 // Destroyed on completion
3436 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3437 }
3438
3439 #[test]
3440 fn anim_target_resource() {
3441 let mut env = TestEnv::<Transform>::empty();
3442 env.world.init_resource::<DummyResource>();
3443 let tween = Tween::new::<DummyResource, DummyLens>(
3444 EaseFunction::Linear,
3445 Duration::from_secs(1),
3446 DummyLens { start: 0., end: 1. },
3447 );
3448 let target = AnimTarget::resource::<DummyResource>();
3449 let anim_entity = env
3450 .world
3451 .spawn((
3452 TweenAnim::new(tween)
3453 .with_speed(2.)
3454 .with_destroy_on_completed(true),
3455 target,
3456 ))
3457 .id();
3458
3459 // Step
3460 assert!(
3461 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3462 );
3463 let res = env.world.resource::<DummyResource>();
3464 assert_eq!(res.value, 0.2);
3465
3466 // Complete
3467 assert_eq!(
3468 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3469 1
3470 );
3471
3472 // Destroyed on completion
3473 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3474 }
3475
3476 #[test]
3477 fn anim_target_asset() {
3478 let mut env = TestEnv::<Transform>::empty();
3479 let mut assets = Assets::<DummyAsset>::default();
3480 let handle = assets.add(DummyAsset::default());
3481 env.world.insert_resource(assets);
3482 let tween = Tween::new::<DummyAsset, DummyLens>(
3483 EaseFunction::Linear,
3484 Duration::from_secs(1),
3485 DummyLens { start: 0., end: 1. },
3486 );
3487 let target = AnimTarget::asset::<DummyAsset>(&handle);
3488 let anim_entity = env
3489 .world
3490 .spawn((
3491 TweenAnim::new(tween)
3492 .with_speed(2.)
3493 .with_destroy_on_completed(true),
3494 target,
3495 ))
3496 .id();
3497
3498 // Step
3499 assert!(
3500 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3501 );
3502 let assets = env.world.resource::<Assets<DummyAsset>>();
3503 let asset = assets.get(&handle).unwrap();
3504 assert_eq!(asset.value, 0.2);
3505
3506 // Complete
3507 assert_eq!(
3508 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3509 1
3510 );
3511
3512 // Destroyed on completion
3513 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3514 }
3515
3516 #[test]
3517 fn animated_entity_commands_common() {
3518 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3519 EaseFunction::QuadraticInOut,
3520 Duration::from_secs(1),
3521 DummyLens { start: 0., end: 1. },
3522 );
3523 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3524
3525 let entity = env
3526 .world
3527 .commands()
3528 .spawn(Transform::default())
3529 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3530 .with_repeat_count(4)
3531 .with_repeat_strategy(RepeatStrategy::MirroredRepeat)
3532 .id();
3533 let entity2 = env
3534 .world
3535 .commands()
3536 .spawn(Transform::default())
3537 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3538 .with_repeat(4, RepeatStrategy::MirroredRepeat)
3539 .into_inner()
3540 .id();
3541 env.world.flush();
3542
3543 env.step_all(Duration::from_millis(3300));
3544
3545 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3546 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3547 let tr = env.world.entity(entity2).get::<Transform>().unwrap();
3548 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3549 }
3550
3551 #[test]
3552 fn animated_entity_commands_move_to() {
3553 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3554 EaseFunction::QuadraticInOut,
3555 Duration::from_secs(1),
3556 DummyLens { start: 0., end: 1. },
3557 );
3558 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3559
3560 let entity = env
3561 .world
3562 .commands()
3563 .spawn(Transform::default())
3564 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3565 .id();
3566 env.world.flush();
3567
3568 env.step_all(Duration::from_millis(300));
3569
3570 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3571 assert_eq!(tr.translation, Vec3::ONE * 0.3);
3572 }
3573
3574 #[test]
3575 fn animated_entity_commands_move_from() {
3576 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3577 EaseFunction::QuadraticInOut,
3578 Duration::from_secs(1),
3579 DummyLens { start: 0., end: 1. },
3580 );
3581 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3582
3583 let entity = env
3584 .world
3585 .commands()
3586 .spawn(Transform::default())
3587 .move_from(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3588 .id();
3589 env.world.flush();
3590
3591 env.step_all(Duration::from_millis(300));
3592
3593 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3594 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3595 }
3596
3597 #[test]
3598 fn animated_entity_commands_scale_to() {
3599 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3600 EaseFunction::QuadraticInOut,
3601 Duration::from_secs(1),
3602 DummyLens { start: 0., end: 1. },
3603 );
3604 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3605
3606 let entity = env
3607 .world
3608 .commands()
3609 .spawn(Transform::default())
3610 .scale_to(Vec3::ONE * 2., Duration::from_secs(1), EaseFunction::Linear)
3611 .id();
3612 env.world.flush();
3613
3614 env.step_all(Duration::from_millis(300));
3615
3616 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3617 assert_eq!(tr.scale, Vec3::ONE * 1.3);
3618 }
3619
3620 #[test]
3621 fn animated_entity_commands_scale_from() {
3622 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3623 EaseFunction::QuadraticInOut,
3624 Duration::from_secs(1),
3625 DummyLens { start: 0., end: 1. },
3626 );
3627 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3628
3629 let entity = env
3630 .world
3631 .commands()
3632 .spawn(Transform::default())
3633 .scale_from(Vec3::ONE * 2., Duration::from_secs(1), EaseFunction::Linear)
3634 .id();
3635 env.world.flush();
3636
3637 env.step_all(Duration::from_millis(300));
3638
3639 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3640 assert_eq!(tr.scale, Vec3::ONE * 1.7);
3641 }
3642
3643 #[test]
3644 fn animated_entity_commands_rotate_x() {
3645 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3646 EaseFunction::QuadraticInOut,
3647 Duration::from_secs(1),
3648 DummyLens { start: 0., end: 1. },
3649 );
3650 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3651
3652 let entity = env
3653 .world
3654 .commands()
3655 .spawn(Transform::default())
3656 .rotate_x(Duration::from_secs(1))
3657 .id();
3658 env.world.flush();
3659
3660 env.step_all(Duration::from_millis(1300));
3661
3662 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3663 assert_eq!(tr.rotation, Quat::from_rotation_x(TAU * 0.3));
3664 }
3665
3666 #[test]
3667 fn animated_entity_commands_rotate_y() {
3668 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3669 EaseFunction::QuadraticInOut,
3670 Duration::from_secs(1),
3671 DummyLens { start: 0., end: 1. },
3672 );
3673 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3674
3675 let entity = env
3676 .world
3677 .commands()
3678 .spawn(Transform::default())
3679 .rotate_y(Duration::from_secs(1))
3680 .id();
3681 env.world.flush();
3682
3683 env.step_all(Duration::from_millis(1300));
3684
3685 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3686 assert_eq!(tr.rotation, Quat::from_rotation_y(TAU * 0.3));
3687 }
3688
3689 #[test]
3690 fn animated_entity_commands_rotate_z() {
3691 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3692 EaseFunction::QuadraticInOut,
3693 Duration::from_secs(1),
3694 DummyLens { start: 0., end: 1. },
3695 );
3696 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3697
3698 let entity = env
3699 .world
3700 .commands()
3701 .spawn(Transform::default())
3702 .rotate_z(Duration::from_secs(1))
3703 .id();
3704 env.world.flush();
3705
3706 env.step_all(Duration::from_millis(1300));
3707
3708 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3709 assert_eq!(tr.rotation, Quat::from_rotation_z(TAU * 0.3));
3710 }
3711
3712 #[test]
3713 fn animated_entity_commands_rotate_x_by() {
3714 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3715 EaseFunction::QuadraticInOut,
3716 Duration::from_secs(1),
3717 DummyLens { start: 0., end: 1. },
3718 );
3719 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3720
3721 let entity = env
3722 .world
3723 .commands()
3724 .spawn(Transform::default())
3725 .rotate_x_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3726 .id();
3727 env.world.flush();
3728
3729 env.step_all(Duration::from_millis(1300)); // 130%
3730
3731 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3732 assert_eq!(tr.rotation, Quat::from_rotation_x(FRAC_PI_2)); // 100%
3733 }
3734
3735 #[test]
3736 fn animated_entity_commands_rotate_y_by() {
3737 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3738 EaseFunction::QuadraticInOut,
3739 Duration::from_secs(1),
3740 DummyLens { start: 0., end: 1. },
3741 );
3742 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3743
3744 let entity = env
3745 .world
3746 .commands()
3747 .spawn(Transform::default())
3748 .rotate_y_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3749 .id();
3750 env.world.flush();
3751
3752 env.step_all(Duration::from_millis(1300)); // 130%
3753
3754 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3755 assert_eq!(tr.rotation, Quat::from_rotation_y(FRAC_PI_2)); // 100%
3756 }
3757
3758 #[test]
3759 fn animated_entity_commands_rotate_z_by() {
3760 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3761 EaseFunction::QuadraticInOut,
3762 Duration::from_secs(1),
3763 DummyLens { start: 0., end: 1. },
3764 );
3765 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3766
3767 let entity = env
3768 .world
3769 .commands()
3770 .spawn(Transform::default())
3771 .rotate_z_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3772 .id();
3773 env.world.flush();
3774
3775 env.step_all(Duration::from_millis(1300)); // 130%
3776
3777 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3778 assert_eq!(tr.rotation, Quat::from_rotation_z(FRAC_PI_2)); // 100%
3779 }
3780
3781 #[test]
3782 fn resolver_resource() {
3783 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3784 EaseFunction::QuadraticInOut,
3785 Duration::from_secs(1),
3786 DummyLens { start: 0., end: 1. },
3787 );
3788 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3789
3790 // Register the resource and create a TweenAnim for it
3791 env.world.init_resource::<DummyResource>();
3792 let tween = Tween::new::<DummyResource, DummyLens>(
3793 EaseFunction::QuadraticInOut,
3794 Duration::from_secs(1),
3795 DummyLens { start: 0., end: 1. },
3796 );
3797 let entity = env.world.commands().spawn(TweenAnim::new(tween)).id();
3798
3799 // Ensure all commands are applied before starting the test
3800 env.world.flush();
3801
3802 let delta_time = Duration::from_millis(200);
3803 let resource_id = env.world.resource_id::<DummyResource>().unwrap();
3804
3805 // Resource resolver not registered; fails
3806 env.world
3807 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3808 world.resource_scope(
3809 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3810 world.resource_scope(
3811 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3812 assert!(resolver
3813 .resolve_resource(
3814 world,
3815 &TypeId::of::<DummyResource>(),
3816 resource_id,
3817 entity,
3818 delta_time,
3819 cycle_events.reborrow(),
3820 anim_events.reborrow(),
3821 )
3822 .is_err());
3823 },
3824 );
3825 },
3826 );
3827 });
3828
3829 // Register the resource resolver
3830 env.world
3831 .resource_scope(|world, mut resolver: Mut<TweenResolver>| {
3832 resolver.register_resource_resolver_for::<DummyResource>(world.components());
3833 });
3834
3835 // Resource resolver registered; succeeds
3836 env.world
3837 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3838 world.resource_scope(
3839 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3840 world.resource_scope(
3841 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3842 assert!(resolver
3843 .resolve_resource(
3844 world,
3845 &TypeId::of::<DummyResource>(),
3846 resource_id,
3847 entity,
3848 delta_time,
3849 cycle_events.reborrow(),
3850 anim_events.reborrow(),
3851 )
3852 .unwrap());
3853 },
3854 );
3855 },
3856 );
3857 });
3858 }
3859
3860 #[test]
3861 fn resolver_asset() {
3862 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3863 EaseFunction::QuadraticInOut,
3864 Duration::from_secs(1),
3865 DummyLens { start: 0., end: 1. },
3866 );
3867 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3868
3869 // Register the asset and create a TweenAnim for it
3870 let mut assets = Assets::<DummyAsset>::default();
3871 let handle = assets.add(DummyAsset::default());
3872 let untyped_asset_id = handle.id().untyped();
3873 env.world.insert_resource(assets);
3874 let tween = Tween::new::<DummyAsset, DummyLens>(
3875 EaseFunction::QuadraticInOut,
3876 Duration::from_secs(1),
3877 DummyLens { start: 0., end: 1. },
3878 );
3879 let entity = env.world.commands().spawn(TweenAnim::new(tween)).id();
3880
3881 // Ensure all commands are applied before starting the test
3882 env.world.flush();
3883
3884 let delta_time = Duration::from_millis(200);
3885 let resource_id = env.world.resource_id::<Assets<DummyAsset>>().unwrap();
3886
3887 // Asset resolver not registered; fails
3888 env.world
3889 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3890 world.resource_scope(
3891 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3892 world.resource_scope(
3893 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3894 assert!(resolver
3895 .resolve_asset(
3896 world,
3897 &TypeId::of::<DummyAsset>(),
3898 resource_id,
3899 untyped_asset_id,
3900 entity,
3901 delta_time,
3902 cycle_events.reborrow(),
3903 anim_events.reborrow(),
3904 )
3905 .is_err());
3906 },
3907 );
3908 },
3909 );
3910 });
3911
3912 // Register the asset resolver
3913 env.world
3914 .resource_scope(|world, mut resolver: Mut<TweenResolver>| {
3915 resolver.register_asset_resolver_for::<DummyAsset>(world.components());
3916 });
3917
3918 // Asset resolver registered; succeeds
3919 env.world
3920 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3921 world.resource_scope(
3922 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3923 world.resource_scope(
3924 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3925 assert!(resolver
3926 .resolve_asset(
3927 world,
3928 &TypeId::of::<DummyAsset>(),
3929 resource_id,
3930 untyped_asset_id,
3931 entity,
3932 delta_time,
3933 cycle_events.reborrow(),
3934 anim_events.reborrow(),
3935 )
3936 .unwrap());
3937 },
3938 );
3939 },
3940 );
3941 });
3942 }
3943}