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.19/bevy/transform/components/struct.Transform.html#structfield.translation
287//! [`Entity`]: https://docs.rs/bevy/0.19/bevy/ecs/entity/struct.Entity.html
288//! [`ColorMaterial`]: https://docs.rs/bevy/0.19/bevy/sprite/struct.ColorMaterial.html
289//! [`Transform`]: https://docs.rs/bevy/0.19/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::{MaybeLocation, MutUntyped, Tick},
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 type Out = ();
919 fn apply(self, mut entity: EntityWorldMut) {
920 if let Some(start) = entity.get::<Transform>().map(|tr| tr.translation) {
921 let lens = TransformPositionLens {
922 start,
923 end: self.end,
924 };
925 let tween = Tween::from_config(self.config, lens);
926 let anim_target = AnimTarget::component::<Transform>(entity.id());
927 entity.world_scope(|world| {
928 world.spawn((TweenAnim::new(tween), anim_target));
929 });
930 }
931 }
932}
933
934impl TweenCommand for MoveToCommand {
935 #[inline]
936 fn config(&self) -> &TweenConfig {
937 &self.config
938 }
939
940 #[inline]
941 fn config_mut(&mut self) -> &mut TweenConfig {
942 &mut self.config
943 }
944}
945
946/// Animation command to move an entity from a source position.
947#[derive(Clone, Copy)]
948pub(crate) struct MoveFromCommand {
949 start: Vec3,
950 config: TweenConfig,
951}
952
953impl EntityCommand for MoveFromCommand {
954 type Out = ();
955 fn apply(self, mut entity: EntityWorldMut) {
956 if let Some(end) = entity.get::<Transform>().map(|tr| tr.translation) {
957 let lens = TransformPositionLens {
958 start: self.start,
959 end,
960 };
961 let tween = Tween::from_config(self.config, lens);
962 let anim_target = AnimTarget::component::<Transform>(entity.id());
963 entity.world_scope(|world| {
964 world.spawn((TweenAnim::new(tween), anim_target));
965 });
966 }
967 }
968}
969
970impl TweenCommand for MoveFromCommand {
971 #[inline]
972 fn config(&self) -> &TweenConfig {
973 &self.config
974 }
975
976 #[inline]
977 fn config_mut(&mut self) -> &mut TweenConfig {
978 &mut self.config
979 }
980}
981
982/// Animation command to scale an entity to a target size.
983#[derive(Clone, Copy)]
984pub(crate) struct ScaleToCommand {
985 end: Vec3,
986 config: TweenConfig,
987}
988
989impl EntityCommand for ScaleToCommand {
990 type Out = ();
991 fn apply(self, mut entity: EntityWorldMut) {
992 if let Some(start) = entity.get::<Transform>().map(|tr| tr.scale) {
993 let lens = TransformScaleLens {
994 start,
995 end: self.end,
996 };
997 let tween = Tween::from_config(self.config, lens);
998 let anim_target = AnimTarget::component::<Transform>(entity.id());
999 entity.world_scope(|world| {
1000 world.spawn((TweenAnim::new(tween), anim_target));
1001 });
1002 }
1003 }
1004}
1005
1006impl TweenCommand for ScaleToCommand {
1007 #[inline]
1008 fn config(&self) -> &TweenConfig {
1009 &self.config
1010 }
1011
1012 #[inline]
1013 fn config_mut(&mut self) -> &mut TweenConfig {
1014 &mut self.config
1015 }
1016}
1017
1018/// Animation command to scale an entity from a source size.
1019#[derive(Clone, Copy)]
1020pub(crate) struct ScaleFromCommand {
1021 start: Vec3,
1022 config: TweenConfig,
1023}
1024
1025impl EntityCommand for ScaleFromCommand {
1026 type Out = ();
1027 fn apply(self, mut entity: EntityWorldMut) {
1028 if let Some(end) = entity.get::<Transform>().map(|tr| tr.scale) {
1029 let lens = TransformScaleLens {
1030 start: self.start,
1031 end,
1032 };
1033 let tween = Tween::from_config(self.config, lens);
1034 let anim_target = AnimTarget::component::<Transform>(entity.id());
1035 entity.world_scope(|world| {
1036 world.spawn((TweenAnim::new(tween), anim_target));
1037 });
1038 }
1039 }
1040}
1041
1042impl TweenCommand for ScaleFromCommand {
1043 #[inline]
1044 fn config(&self) -> &TweenConfig {
1045 &self.config
1046 }
1047
1048 #[inline]
1049 fn config_mut(&mut self) -> &mut TweenConfig {
1050 &mut self.config
1051 }
1052}
1053
1054/// Animation command to rotate an entity around its X axis.
1055#[derive(Clone, Copy)]
1056pub(crate) struct RotateXCommand {
1057 config: TweenConfig,
1058}
1059
1060impl EntityCommand for RotateXCommand {
1061 type Out = ();
1062 fn apply(self, mut entity: EntityWorldMut) {
1063 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1064 let lens = TransformRotateAdditiveXLens {
1065 base_rotation,
1066 start: 0.,
1067 end: std::f32::consts::TAU,
1068 };
1069 let tween = Tween::from_config(self.config, lens)
1070 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1071 let anim_target = AnimTarget::component::<Transform>(entity.id());
1072 entity.world_scope(|world| {
1073 world.spawn((TweenAnim::new(tween), anim_target));
1074 });
1075 }
1076 }
1077}
1078
1079impl TweenCommand for RotateXCommand {
1080 #[inline]
1081 fn config(&self) -> &TweenConfig {
1082 &self.config
1083 }
1084
1085 #[inline]
1086 fn config_mut(&mut self) -> &mut TweenConfig {
1087 &mut self.config
1088 }
1089}
1090
1091/// Animation command to rotate an entity around its Y axis.
1092#[derive(Clone, Copy)]
1093pub(crate) struct RotateYCommand {
1094 config: TweenConfig,
1095}
1096
1097impl EntityCommand for RotateYCommand {
1098 type Out = ();
1099 fn apply(self, mut entity: EntityWorldMut) {
1100 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1101 let lens = TransformRotateAdditiveYLens {
1102 base_rotation,
1103 start: 0.,
1104 end: std::f32::consts::TAU,
1105 };
1106 let tween = Tween::from_config(self.config, lens)
1107 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1108 let anim_target = AnimTarget::component::<Transform>(entity.id());
1109 entity.world_scope(|world| {
1110 world.spawn((TweenAnim::new(tween), anim_target));
1111 });
1112 }
1113 }
1114}
1115
1116impl TweenCommand for RotateYCommand {
1117 #[inline]
1118 fn config(&self) -> &TweenConfig {
1119 &self.config
1120 }
1121
1122 #[inline]
1123 fn config_mut(&mut self) -> &mut TweenConfig {
1124 &mut self.config
1125 }
1126}
1127
1128/// Animation command to rotate an entity around its Z axis.
1129#[derive(Clone, Copy)]
1130pub(crate) struct RotateZCommand {
1131 config: TweenConfig,
1132}
1133
1134impl EntityCommand for RotateZCommand {
1135 type Out = ();
1136 fn apply(self, mut entity: EntityWorldMut) {
1137 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1138 let lens = TransformRotateAdditiveZLens {
1139 base_rotation,
1140 start: 0.,
1141 end: std::f32::consts::TAU,
1142 };
1143 let tween = Tween::from_config(self.config, lens)
1144 .with_repeat(RepeatCount::Infinite, RepeatStrategy::Repeat);
1145 let anim_target = AnimTarget::component::<Transform>(entity.id());
1146 entity.world_scope(|world| {
1147 world.spawn((TweenAnim::new(tween), anim_target));
1148 });
1149 }
1150 }
1151}
1152
1153impl TweenCommand for RotateZCommand {
1154 #[inline]
1155 fn config(&self) -> &TweenConfig {
1156 &self.config
1157 }
1158
1159 #[inline]
1160 fn config_mut(&mut self) -> &mut TweenConfig {
1161 &mut self.config
1162 }
1163}
1164
1165/// Animation command to rotate an entity around its X axis by a given angle.
1166#[derive(Clone, Copy)]
1167pub(crate) struct RotateXByCommand {
1168 angle: f32,
1169 config: TweenConfig,
1170}
1171
1172impl EntityCommand for RotateXByCommand {
1173 type Out = ();
1174 fn apply(self, mut entity: EntityWorldMut) {
1175 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1176 let lens = TransformRotateAdditiveXLens {
1177 base_rotation,
1178 start: 0.,
1179 end: self.angle,
1180 };
1181 let tween = Tween::from_config(self.config, lens);
1182 let anim_target = AnimTarget::component::<Transform>(entity.id());
1183 entity.world_scope(|world| {
1184 world.spawn((TweenAnim::new(tween), anim_target));
1185 });
1186 }
1187 }
1188}
1189
1190impl TweenCommand for RotateXByCommand {
1191 #[inline]
1192 fn config(&self) -> &TweenConfig {
1193 &self.config
1194 }
1195
1196 #[inline]
1197 fn config_mut(&mut self) -> &mut TweenConfig {
1198 &mut self.config
1199 }
1200}
1201
1202/// Animation command to rotate an entity around its Y axis by a given angle.
1203#[derive(Clone, Copy)]
1204pub(crate) struct RotateYByCommand {
1205 angle: f32,
1206 config: TweenConfig,
1207}
1208
1209impl EntityCommand for RotateYByCommand {
1210 type Out = ();
1211 fn apply(self, mut entity: EntityWorldMut) {
1212 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1213 let lens = TransformRotateAdditiveYLens {
1214 base_rotation,
1215 start: 0.,
1216 end: self.angle,
1217 };
1218 let tween = Tween::from_config(self.config, lens);
1219 let anim_target = AnimTarget::component::<Transform>(entity.id());
1220 entity.world_scope(|world| {
1221 world.spawn((TweenAnim::new(tween), anim_target));
1222 });
1223 }
1224 }
1225}
1226
1227impl TweenCommand for RotateYByCommand {
1228 #[inline]
1229 fn config(&self) -> &TweenConfig {
1230 &self.config
1231 }
1232
1233 #[inline]
1234 fn config_mut(&mut self) -> &mut TweenConfig {
1235 &mut self.config
1236 }
1237}
1238
1239/// Animation command to rotate an entity around its Z axis by a given angle.
1240#[derive(Clone, Copy)]
1241pub(crate) struct RotateZByCommand {
1242 angle: f32,
1243 config: TweenConfig,
1244}
1245
1246impl EntityCommand for RotateZByCommand {
1247 type Out = ();
1248 fn apply(self, mut entity: EntityWorldMut) {
1249 if let Some(base_rotation) = entity.get::<Transform>().map(|tr| tr.rotation) {
1250 let lens = TransformRotateAdditiveZLens {
1251 base_rotation,
1252 start: 0.,
1253 end: self.angle,
1254 };
1255 let tween = Tween::from_config(self.config, lens);
1256 let anim_target = AnimTarget::component::<Transform>(entity.id());
1257 entity.world_scope(|world| {
1258 world.spawn((TweenAnim::new(tween), anim_target));
1259 });
1260 }
1261 }
1262}
1263
1264impl TweenCommand for RotateZByCommand {
1265 #[inline]
1266 fn config(&self) -> &TweenConfig {
1267 &self.config
1268 }
1269
1270 #[inline]
1271 fn config_mut(&mut self) -> &mut TweenConfig {
1272 &mut self.config
1273 }
1274}
1275
1276/// Wrapper over an [`EntityCommands`] which stores an animation command.
1277///
1278/// The wrapper acts as, and dereferences to, a regular [`EntityCommands`] as
1279/// _e.g._ returned by [`Commands::spawn()`]. In addition, it stores a pending
1280/// animation command, which can be further tweaked before being queued into the
1281/// entity commands queue. This deferred queuing allows fluent patterns like:
1282///
1283/// ```
1284/// # use std::time::Duration;
1285/// # use bevy::prelude::*;
1286/// # use bevy_tweening::*;
1287/// # fn my_system(mut commands: Commands) {
1288/// commands
1289/// .spawn(Transform::default())
1290/// // Consume the EntityCommands, and wrap it into an AnimatedEntityCommands,
1291/// // which stores an animation command to move an entity.
1292/// .move_to(
1293/// Vec3::ONE,
1294/// Duration::from_millis(400),
1295/// EaseFunction::QuadraticIn,
1296/// )
1297/// // Tweak the stored animation to set the repeat count of the Tween.
1298/// .with_repeat_count(2);
1299/// # }
1300/// ```
1301///
1302/// The animation commands always stores the last animation inserted. When the
1303/// commands is mutably dereferenced, it first flushes the pending animation
1304/// command, if any, by inserting it into the underlying [`EntityCommands`]
1305/// queue. It also flushes the animation when dropped, to ensure the last
1306/// animation is queued too.
1307///
1308/// To move from an [`AnimatedEntityCommands`] to its underlying
1309/// [`EntityCommands`], the former automatically dereferences to the latter.
1310/// Note however that once you're back on the base [`EntityCommands`], you can
1311/// only get a new [`AnimatedEntityCommands`] via functions consuming the
1312/// [`EntityCommands`] by value. In that case, you need to call [`reborrow()`]:
1313///
1314/// ```
1315/// # use std::time::Duration;
1316/// # use bevy::prelude::*;
1317/// # use bevy_tweening::*;
1318/// # fn my_system(mut commands: Commands) {
1319/// commands
1320/// .spawn(Transform::default())
1321/// .move_to(
1322/// Vec3::ONE,
1323/// Duration::from_millis(400),
1324/// EaseFunction::QuadraticIn,
1325/// )
1326/// // This call invokes std::ops::DerefMut, and returns a mutable ref
1327/// // to the underlying EntityCommands
1328/// .insert(Name::new("my_object"))
1329/// // Here we need to reborrow() to convert from `&mut EntityCommands`
1330/// // (by mutable ref) to `EntityCommands` (by value)
1331/// .reborrow()
1332/// // This call requires an `EntityCommands` (by value)
1333/// .scale_to(
1334/// Vec3::splat(1.1),
1335/// Duration::from_millis(400),
1336/// EaseFunction::Linear,
1337/// );
1338/// # }
1339/// ```
1340///
1341/// [`reborrow()`]: bevy::prelude::EntityCommands::reborrow
1342pub struct AnimatedEntityCommands<'a, C: TweenCommand> {
1343 commands: EntityCommands<'a>,
1344 cmd: Option<C>,
1345}
1346
1347impl<'a, C: TweenCommand> AnimatedEntityCommands<'a, C> {
1348 /// Wrap an [`EntityCommands`] into an animated one.
1349 pub fn new(commands: EntityCommands<'a>, cmd: C) -> Self {
1350 Self {
1351 commands,
1352 cmd: Some(cmd),
1353 }
1354 }
1355
1356 /// Set the repeat count of this animation.
1357 #[inline]
1358 pub fn with_repeat_count(mut self, repeat_count: impl Into<RepeatCount>) -> Self {
1359 if let Some(cmd) = self.cmd.as_mut() {
1360 cmd.config_mut().repeat_count = repeat_count.into();
1361 }
1362 self
1363 }
1364
1365 /// Set the repeat strategy of this animation.
1366 #[inline]
1367 pub fn with_repeat_strategy(mut self, repeat_strategy: RepeatStrategy) -> Self {
1368 if let Some(cmd) = self.cmd.as_mut() {
1369 cmd.config_mut().repeat_strategy = repeat_strategy;
1370 }
1371 self
1372 }
1373
1374 /// Configure the repeat parameters of this animation.
1375 ///
1376 /// This is a shortcut for:
1377 ///
1378 /// ```no_run
1379 /// # use bevy_tweening::*;
1380 /// # struct AnimatedEntityCommands {}
1381 /// # impl AnimatedEntityCommands {
1382 /// # fn with_repeat_count(self, r: RepeatCount) -> Self { unimplemented!() }
1383 /// # fn with_repeat_strategy(self, r: RepeatStrategy) -> Self { unimplemented!() }
1384 /// # fn xxx(self) -> Self {
1385 /// # let repeat_count = RepeatCount::Infinite;
1386 /// # let repeat_strategy = RepeatStrategy::Repeat;
1387 /// self.with_repeat_count(repeat_count)
1388 /// .with_repeat_strategy(repeat_strategy)
1389 /// # }}
1390 /// ```
1391 #[inline]
1392 pub fn with_repeat(
1393 self,
1394 repeat_count: impl Into<RepeatCount>,
1395 repeat_strategy: RepeatStrategy,
1396 ) -> Self {
1397 self.with_repeat_count(repeat_count)
1398 .with_repeat_strategy(repeat_strategy)
1399 }
1400
1401 /// Consume self and return the inner [`EntityCommands`].
1402 ///
1403 /// The current animation is inserted into the commands queue, before that
1404 /// wrapped commands queue is returned.
1405 pub fn into_inner(mut self) -> EntityCommands<'a> {
1406 self.flush();
1407 // Since we already flushed above, we don't need Drop. And trying to keep would
1408 // allow it to access self.commands after it was stolen (even though we know the
1409 // implementation doesn't in practice). Still, it's safer to just short-circuit
1410 // Drop here.
1411 let this = std::mem::ManuallyDrop::new(self);
1412 // SAFETY: We have flushed self.cmd which is now None, and we're stealing
1413 // self.commands, after which the this object is forgotten and never
1414 // accessed again.
1415 #[allow(unsafe_code)]
1416 unsafe {
1417 std::ptr::read(&this.commands)
1418 }
1419 }
1420
1421 /// Flush the current animation, inserting it into the commands queue.
1422 ///
1423 /// This makes it impossible to further tweak the animation. This is
1424 /// automatically called when a new animation is created and when the
1425 /// commands queue is dropped with the last animation pending.
1426 fn flush(&mut self) {
1427 if let Some(cmd) = self.cmd.take() {
1428 self.queue(cmd);
1429 }
1430 }
1431}
1432
1433impl<'a, C: TweenCommand> EntityCommandsTweeningExtensions<'a> for AnimatedEntityCommands<'a, C> {
1434 #[inline]
1435 fn move_to(
1436 self,
1437 end: Vec3,
1438 duration: Duration,
1439 ease_method: impl Into<EaseMethod>,
1440 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1441 self.into_inner().move_to(end, duration, ease_method)
1442 }
1443
1444 #[inline]
1445 fn move_from(
1446 self,
1447 start: Vec3,
1448 duration: Duration,
1449 ease_method: impl Into<EaseMethod>,
1450 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1451 self.into_inner().move_from(start, duration, ease_method)
1452 }
1453
1454 #[inline]
1455 fn scale_to(
1456 self,
1457 end: Vec3,
1458 duration: Duration,
1459 ease_method: impl Into<EaseMethod>,
1460 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1461 self.into_inner().scale_to(end, duration, ease_method)
1462 }
1463
1464 #[inline]
1465 fn scale_from(
1466 self,
1467 start: Vec3,
1468 duration: Duration,
1469 ease_method: impl Into<EaseMethod>,
1470 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1471 self.into_inner().scale_from(start, duration, ease_method)
1472 }
1473
1474 #[inline]
1475 fn rotate_x(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1476 self.into_inner().rotate_x(cycle_duration)
1477 }
1478
1479 #[inline]
1480 fn rotate_y(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1481 self.into_inner().rotate_y(cycle_duration)
1482 }
1483
1484 #[inline]
1485 fn rotate_z(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1486 self.into_inner().rotate_z(cycle_duration)
1487 }
1488
1489 #[inline]
1490 fn rotate_x_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_x_by(angle, duration, ease_method)
1497 }
1498
1499 #[inline]
1500 fn rotate_y_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_y_by(angle, duration, ease_method)
1507 }
1508
1509 #[inline]
1510 fn rotate_z_by(
1511 self,
1512 angle: f32,
1513 duration: Duration,
1514 ease_method: impl Into<EaseMethod>,
1515 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1516 self.into_inner().rotate_z_by(angle, duration, ease_method)
1517 }
1518}
1519
1520impl<'a, C: TweenCommand> Deref for AnimatedEntityCommands<'a, C> {
1521 type Target = EntityCommands<'a>;
1522
1523 fn deref(&self) -> &Self::Target {
1524 &self.commands
1525 }
1526}
1527
1528impl<C: TweenCommand> DerefMut for AnimatedEntityCommands<'_, C> {
1529 fn deref_mut(&mut self) -> &mut Self::Target {
1530 self.flush();
1531 &mut self.commands
1532 }
1533}
1534
1535impl<C: TweenCommand> Drop for AnimatedEntityCommands<'_, C> {
1536 fn drop(&mut self) {
1537 self.flush();
1538 }
1539}
1540
1541impl<'a> EntityCommandsTweeningExtensions<'a> for EntityCommands<'a> {
1542 #[inline]
1543 fn move_to(
1544 self,
1545 end: Vec3,
1546 duration: Duration,
1547 ease_method: impl Into<EaseMethod>,
1548 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1549 AnimatedEntityCommands::new(
1550 self,
1551 MoveToCommand {
1552 end,
1553 config: TweenConfig {
1554 ease_method: ease_method.into(),
1555 cycle_duration: duration,
1556 ..default()
1557 },
1558 },
1559 )
1560 }
1561
1562 #[inline]
1563 fn move_from(
1564 self,
1565 start: Vec3,
1566 duration: Duration,
1567 ease_method: impl Into<EaseMethod>,
1568 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1569 AnimatedEntityCommands::new(
1570 self,
1571 MoveFromCommand {
1572 start,
1573 config: TweenConfig {
1574 ease_method: ease_method.into(),
1575 cycle_duration: duration,
1576 ..default()
1577 },
1578 },
1579 )
1580 }
1581
1582 #[inline]
1583 fn scale_to(
1584 self,
1585 end: Vec3,
1586 duration: Duration,
1587 ease_method: impl Into<EaseMethod>,
1588 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1589 AnimatedEntityCommands::new(
1590 self,
1591 ScaleToCommand {
1592 end,
1593 config: TweenConfig {
1594 ease_method: ease_method.into(),
1595 cycle_duration: duration,
1596 ..default()
1597 },
1598 },
1599 )
1600 }
1601
1602 #[inline]
1603 fn scale_from(
1604 self,
1605 start: Vec3,
1606 duration: Duration,
1607 ease_method: impl Into<EaseMethod>,
1608 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1609 AnimatedEntityCommands::new(
1610 self,
1611 ScaleFromCommand {
1612 start,
1613 config: TweenConfig {
1614 ease_method: ease_method.into(),
1615 cycle_duration: duration,
1616 ..default()
1617 },
1618 },
1619 )
1620 }
1621
1622 #[inline]
1623 fn rotate_x(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1624 AnimatedEntityCommands::new(
1625 self,
1626 RotateXCommand {
1627 config: TweenConfig {
1628 ease_method: EaseFunction::Linear.into(),
1629 cycle_duration,
1630 ..default()
1631 },
1632 },
1633 )
1634 }
1635
1636 #[inline]
1637 fn rotate_y(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1638 AnimatedEntityCommands::new(
1639 self,
1640 RotateYCommand {
1641 config: TweenConfig {
1642 ease_method: EaseFunction::Linear.into(),
1643 cycle_duration,
1644 ..default()
1645 },
1646 },
1647 )
1648 }
1649
1650 #[inline]
1651 fn rotate_z(self, cycle_duration: Duration) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1652 AnimatedEntityCommands::new(
1653 self,
1654 RotateZCommand {
1655 config: TweenConfig {
1656 ease_method: EaseFunction::Linear.into(),
1657 cycle_duration,
1658 ..default()
1659 },
1660 },
1661 )
1662 }
1663
1664 #[inline]
1665 fn rotate_x_by(
1666 self,
1667 angle: f32,
1668 duration: Duration,
1669 ease_method: impl Into<EaseMethod>,
1670 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1671 AnimatedEntityCommands::new(
1672 self,
1673 RotateXByCommand {
1674 angle,
1675 config: TweenConfig {
1676 ease_method: ease_method.into(),
1677 cycle_duration: duration,
1678 ..default()
1679 },
1680 },
1681 )
1682 }
1683
1684 #[inline]
1685 fn rotate_y_by(
1686 self,
1687 angle: f32,
1688 duration: Duration,
1689 ease_method: impl Into<EaseMethod>,
1690 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1691 AnimatedEntityCommands::new(
1692 self,
1693 RotateYByCommand {
1694 angle,
1695 config: TweenConfig {
1696 ease_method: ease_method.into(),
1697 cycle_duration: duration,
1698 ..default()
1699 },
1700 },
1701 )
1702 }
1703
1704 #[inline]
1705 fn rotate_z_by(
1706 self,
1707 angle: f32,
1708 duration: Duration,
1709 ease_method: impl Into<EaseMethod>,
1710 ) -> AnimatedEntityCommands<'a, impl TweenCommand> {
1711 AnimatedEntityCommands::new(
1712 self,
1713 RotateZByCommand {
1714 angle,
1715 config: TweenConfig {
1716 ease_method: ease_method.into(),
1717 cycle_duration: duration,
1718 ..default()
1719 },
1720 },
1721 )
1722 }
1723}
1724
1725/// Event raised when a [`TweenAnim`] completed.
1726#[derive(Debug, Clone, Copy, EntityEvent, Message)]
1727pub struct AnimCompletedEvent {
1728 /// The entity owning the [`TweenAnim`] which completed.
1729 ///
1730 /// Note that commonly the [`TweenAnim`] is despawned on completion, so
1731 /// can't be queried anymore with this entity. You can prevent a completed
1732 /// animation from being automatically destroyed by
1733 /// setting [`TweenAnim::destroy_on_completion`] to `false`.
1734 #[event_target]
1735 pub anim_entity: Entity,
1736 /// The animation target.
1737 ///
1738 /// This is provided both as a convenience for [`TweenAnim`]s not destroyed
1739 /// on completion, and because for those animations which are destroyed
1740 /// on completion the information is not available anymore when this
1741 /// event is received.
1742 pub target: AnimTargetKind,
1743}
1744
1745/// Errors returned by various animation functions.
1746#[derive(Debug, Error, Clone, Copy)]
1747pub enum TweeningError {
1748 /// The asset resolver for the given asset is not registered.
1749 #[error("Asset resolver for asset with resource ID {0:?} is not registered.")]
1750 AssetResolverNotRegistered(ComponentId),
1751 /// The entity was not found in the World.
1752 #[error("Entity {0:?} not found in the World.")]
1753 EntityNotFound(Entity),
1754 /// The entity should have had a TweenAnim but it was not found.
1755 #[error("Entity {0:?} doesn't have a TweenAnim.")]
1756 MissingTweenAnim(Entity),
1757 /// The component of the given type is not registered.
1758 #[error("Component of type {0:?} is not registered in the World.")]
1759 ComponentNotRegistered(TypeId),
1760 /// The resource of the given type is not registered.
1761 #[error("Resource of type {0:?} is not registered in the World.")]
1762 ResourceNotRegistered(TypeId),
1763 /// The asset container for the given asset type is not registered.
1764 #[error("Asset container Assets<A> for asset type A = {0:?} is not registered in the World.")]
1765 AssetNotRegistered(TypeId),
1766 /// The component of the given type is not registered.
1767 #[error("Component of type {0:?} is not present on entity {1:?}.")]
1768 MissingComponent(TypeId, Entity),
1769 /// The asset cannot be found.
1770 #[error("Asset ID {0:?} is invalid.")]
1771 InvalidAssetId(UntypedAssetId),
1772 /// The asset ID references a different type than expected.
1773 #[error("Expected type of asset ID to be {expected:?} but got {actual:?} instead.")]
1774 InvalidAssetIdType {
1775 /// The expected asset type.
1776 expected: TypeId,
1777 /// The actual type the asset ID references.
1778 actual: TypeId,
1779 },
1780 /// Expected [`Tweenable::target_type_id()`] to return a value, but it
1781 /// returned `None`.
1782 #[error("Expected a typed Tweenable.")]
1783 UntypedTweenable,
1784 /// Invalid [`Entity`].
1785 #[error("Invalid Entity {0:?}.")]
1786 InvalidTweenId(Entity),
1787 /// Cannot change target kind.
1788 #[error("Unexpected target kind: was component={0}, now component={1}")]
1789 MismatchingTargetKind(bool, bool),
1790 /// Cannot change component type.
1791 #[error("Cannot change component type: was component_id={0:?}, now component_id={1:?}")]
1792 MismatchingComponentId(ComponentId, ComponentId),
1793 /// Cannot change asset type.
1794 #[error("Cannot change asset type: was component_id={0:?}, now component_id={1:?}")]
1795 MismatchingAssetResourceId(ComponentId, ComponentId),
1796}
1797
1798type RegisterAction = dyn Fn(&Components, &mut TweenResolver) + Send + Sync + 'static;
1799
1800/// Enumeration of the types of animation targets.
1801///
1802/// This type holds the minimum amount of data to reference ananimation target,
1803/// aside from the actual type of the target.
1804#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1805pub enum AnimTargetKind {
1806 /// Component animation target.
1807 Component {
1808 /// The entity owning the component instance.
1809 entity: Entity,
1810 },
1811 /// Resource animation target.
1812 Resource,
1813 /// Asset animation target.
1814 Asset {
1815 /// The asset ID inside the [`Assets`] collection.
1816 asset_id: UntypedAssetId,
1817 /// Type ID of the [`Assets`] collection itself.
1818 assets_type_id: TypeId,
1819 },
1820}
1821
1822/// Component defining the target of an animation.
1823///
1824/// References an object used as the target of the animation stored in the
1825/// [`TweenAnim`] component on the same entity.
1826#[derive(Component)]
1827pub struct AnimTarget {
1828 /// Target kind and additional data to identify it.
1829 pub kind: AnimTargetKind,
1830
1831 /// Self-registering action for assets and resources.
1832 pub(crate) register_action: Option<Box<RegisterAction>>,
1833}
1834
1835impl AnimTarget {
1836 /// Create a target mutating a component on the given entity.
1837 pub fn component<C: Component<Mutability = Mutable>>(entity: Entity) -> Self {
1838 Self {
1839 kind: AnimTargetKind::Component { entity },
1840 // Components have a complete typeless API, don't need any extra registration for type
1841 // erasure.
1842 register_action: None,
1843 }
1844 }
1845
1846 /// Create a target mutating the given resource.
1847 pub fn resource<R: Resource>() -> Self {
1848 let register_action = |components: &Components, resolver: &mut TweenResolver| {
1849 resolver.register_resource_resolver_for::<R>(components);
1850 };
1851 Self {
1852 kind: AnimTargetKind::Resource,
1853 register_action: Some(Box::new(register_action)),
1854 }
1855 }
1856
1857 /// Create a target mutating the given asset.
1858 ///
1859 /// The asset is identified by its type, and its [`AssetId`].
1860 pub fn asset<A: Asset>(asset_id: impl Into<AssetId<A>>) -> Self {
1861 let register_action = |components: &Components, resolver: &mut TweenResolver| {
1862 resolver.register_asset_resolver_for::<A>(components);
1863 };
1864 Self {
1865 kind: AnimTargetKind::Asset {
1866 asset_id: asset_id.into().untyped(),
1867 assets_type_id: TypeId::of::<Assets<A>>(),
1868 },
1869 register_action: Some(Box::new(register_action)),
1870 }
1871 }
1872
1873 /// Register any resolver for this target.
1874 pub(crate) fn register(&self, components: &Components, resolver: &mut TweenResolver) {
1875 if let Some(register_action) = self.register_action.as_ref() {
1876 register_action(components, resolver);
1877 }
1878 }
1879}
1880
1881/// Animation controller instance.
1882///
1883/// The [`TweenAnim`] represents a single animation instance for a single
1884/// target (component or resource or asset). Each instance is independent, even
1885/// if it mutates the same target as another instance. Spawning this component
1886/// adds an active animation, and destroying it stops that animation. The
1887/// component can also be used to control the animation playback at runtime,
1888/// like the playback speed.
1889///
1890/// The target is described by the [`AnimTarget`] component. If that component
1891/// is absent, then the animation implicitly targets a component on the current
1892/// Entity. The type of the component is derived from the type that the [`Lens`]
1893/// animates.
1894///
1895/// _If you're looking for the basic tweenable animation description, see
1896/// [`Tween`] instead._
1897///
1898/// # Example
1899///
1900/// ```
1901/// # use bevy::prelude::*;
1902/// # use bevy_tweening::*;
1903/// # fn make_tweenable<T>() -> Tween { unimplemented!() }
1904/// fn my_system(mut commands: Commands) {
1905/// let tweenable = make_tweenable::<Transform>();
1906/// let id1 = commands
1907/// .spawn((
1908/// Transform::default(),
1909/// // Implicitly targets the current entity's Transform
1910/// TweenAnim::new(tweenable),
1911/// ))
1912/// .id();
1913///
1914/// let tweenable2 = make_tweenable::<Transform>();
1915/// commands.spawn((
1916/// TweenAnim::new(tweenable2),
1917/// // Explicitly targets the Transform component of entity 'id1'
1918/// AnimTarget::component::<Transform>(id1),
1919/// ));
1920/// }
1921/// ```
1922#[derive(Component)]
1923pub struct TweenAnim {
1924 /// The animation itself. Note that the tweenable is stateful, so can't be
1925 /// shared with another [`TweenAnim`] instance.
1926 tweenable: BoxedTweenable,
1927 /// Control if the animation is played or not. Defaults to
1928 /// [`PlaybackState::Playing`].
1929 ///
1930 /// Pausing an animation with [`PlaybackState::Paused`] is functionaly
1931 /// equivalent to setting its [`speed`] to zero. The two fields remain
1932 /// independent though, for convenience.
1933 ///
1934 /// [`speed`]: Self::speed
1935 pub playback_state: PlaybackState,
1936 /// Relative playback speed. Defaults to `1.` (normal speed; 100%).
1937 ///
1938 /// Setting a negative or zero speed value effectively pauses the animation
1939 /// (although the [`playback_state`] remains unchanged). Negative values may
1940 /// be clamped to 0. when the animation is stepped, but positive or zero
1941 /// values are never modified by the library.
1942 ///
1943 /// # Time precision
1944 ///
1945 /// _This note is an implementation detail which can usually be ignored._
1946 ///
1947 /// Despite the use of `f64`, setting a playback speed different from `1.`
1948 /// (100% speed) may produce small inaccuracies in durations, especially
1949 /// for longer animations. However those are often negligible.
1950 /// This is due to the very large precision of `Duration` (typically 96
1951 /// bits or more), even compared to `f64` (64 bits), and the fact this speed
1952 /// factor is a multiplier whereas most other time quantities are added or
1953 /// subtracted.
1954 ///
1955 /// [`playback_state`]: Self::playback_state
1956 pub speed: f64,
1957 /// Destroy the animation once completed. This defaults to `true`, and makes
1958 /// the stepping functions like [`TweenAnim::step_all()`] destroy this
1959 /// animation once it completed. To keep the animation queued, and allow
1960 /// access after it completed, set this to `false`. Note however that
1961 /// you should avoid leaving all animations queued if they're unused, as
1962 /// this wastes memory and may degrade performances if too many
1963 /// completed animations are kept around for no good reason.
1964 pub destroy_on_completion: bool,
1965 /// Current tweening completion state.
1966 tween_state: TweenState,
1967}
1968
1969impl TweenAnim {
1970 /// Create a new tween animation.
1971 ///
1972 /// This component represents the runtime animation being played to mutate a
1973 /// specific target.
1974 ///
1975 /// # Panics
1976 ///
1977 /// Panics if the tweenable is "typeless", that is
1978 /// [`Tweenable::target_type_id()`] returns `None`. Animations must
1979 /// target a concrete component or asset type. This means in particular
1980 /// that you can't use a single [`Delay`] alone. You can however use a
1981 /// [`Delay`] or other typeless tweenables as part of a [`Sequence`],
1982 /// provided there's at least one other typed tweenable in the sequence
1983 /// to make it typed too.
1984 #[inline]
1985 pub fn new(tweenable: impl IntoBoxedTweenable) -> Self {
1986 let tweenable = tweenable.into_boxed();
1987 assert!(
1988 tweenable.target_type_id().is_some(),
1989 "The top-level Tweenable of a TweenAnim must be typed (Tweenable::target_type_id() returns Some)."
1990 );
1991 Self {
1992 tweenable,
1993 playback_state: PlaybackState::Playing,
1994 speed: 1.,
1995 destroy_on_completion: true,
1996 tween_state: TweenState::Active,
1997 }
1998 }
1999
2000 /// Configure the playback speed.
2001 pub fn with_speed(mut self, speed: f64) -> Self {
2002 self.speed = speed;
2003 self
2004 }
2005
2006 /// Enable or disable destroying this component on animation completion.
2007 ///
2008 /// If enabled, the component is automatically removed from its `Entity`
2009 /// when the animation completed.
2010 pub fn with_destroy_on_completed(mut self, destroy_on_completed: bool) -> Self {
2011 self.destroy_on_completion = destroy_on_completed;
2012 self
2013 }
2014
2015 /// Step a single animation.
2016 ///
2017 /// _The [`step_all()`] function is called automatically by the animation
2018 /// system registered by the [`TweeningPlugin`], you generally don't
2019 /// need to call this one._
2020 ///
2021 /// This is a shortcut for `step_many(world, delta_time, [entity])`, with
2022 /// the added benefit that it returns some error if the entity is not valid.
2023 /// See [`step_many()`] for details.
2024 ///
2025 /// # Example
2026 ///
2027 /// ```
2028 /// # use std::time::Duration;
2029 /// # use bevy::prelude::*;
2030 /// # use bevy_tweening::*;
2031 /// # fn make_tweenable() -> Tween { unimplemented!() }
2032 /// #[derive(Component)]
2033 /// struct MyMarker;
2034 ///
2035 /// fn my_system(world: &mut World) -> Result<()> {
2036 /// let mut q_anims = world.query_filtered::<Entity, (With<TweenAnim>, With<MyMarker>)>();
2037 /// let entity = q_anims.single(world)?;
2038 /// let delta_time = Duration::from_millis(200);
2039 /// TweenAnim::step_one(world, delta_time, entity);
2040 /// Ok(())
2041 /// }
2042 /// ```
2043 ///
2044 /// # Returns
2045 ///
2046 /// This returns an error if the entity is not found or doesn't own a
2047 /// [`TweenAnim`] component.
2048 ///
2049 /// [`step_all()`]: Self::step_all
2050 /// [`step_many()`]: Self::step_many
2051 #[inline]
2052 pub fn step_one(
2053 world: &mut World,
2054 delta_time: Duration,
2055 entity: Entity,
2056 ) -> Result<(), TweeningError> {
2057 let num = Self::step_many(world, delta_time, &[entity]);
2058 if num > 0 {
2059 Ok(())
2060 } else {
2061 Err(TweeningError::EntityNotFound(entity))
2062 }
2063 }
2064
2065 /// Step some animation(s).
2066 ///
2067 /// _The [`step_all()`] function is called automatically by the animation
2068 /// system registered by the [`TweeningPlugin`], you generally don't
2069 /// need to call this one._
2070 ///
2071 /// Step the given animation(s) by a given `delta_time`, which may be
2072 /// [`Duration::ZERO`]. Passing a zero delta time may be useful to force the
2073 /// current animation state to be applied to a target, in case you made
2074 /// change which do not automatically do so (for example, retargeting an
2075 /// animation). The `anims` are the entities which own a [`TweenAnim`]
2076 /// component to step; any entity without a [`TweenAnim`] component is
2077 /// silently ignored.
2078 ///
2079 /// The function doesn't check that all input entities are unique. If an
2080 /// entity is duplicated in `anims`, the behavior is undefined, including
2081 /// (but not guaranteed) stepping the animation multiple times. You're
2082 /// responsible for ensuring the input entity slice contains distinct
2083 /// entities.
2084 ///
2085 /// # Example
2086 ///
2087 /// ```
2088 /// # use std::time::Duration;
2089 /// # use bevy::prelude::*;
2090 /// # use bevy_tweening::*;
2091 /// # fn make_tweenable() -> Tween { unimplemented!() }
2092 /// #[derive(Component)]
2093 /// struct MyMarker;
2094 ///
2095 /// fn my_system(world: &mut World) -> Result<()> {
2096 /// let mut q_anims = world.query_filtered::<Entity, (With<TweenAnim>, With<MyMarker>)>();
2097 /// let entities = q_anims.iter(world).collect::<Vec<Entity>>();
2098 /// let delta_time = Duration::from_millis(200);
2099 /// TweenAnim::step_many(world, delta_time, &entities[..]);
2100 /// Ok(())
2101 /// }
2102 /// ```
2103 ///
2104 /// # Returns
2105 ///
2106 /// Returns the number of [`TweenAnim`] component found and stepped, which
2107 /// is always less than or equal to the input `anims` slice length.
2108 ///
2109 /// [`step_all()`]: Self::step_all
2110 pub fn step_many(world: &mut World, delta_time: Duration, anims: &[Entity]) -> usize {
2111 let mut targets = vec![];
2112 world.resource_scope(|world, mut resolver: Mut<TweenResolver>| {
2113 let mut q_anims = world.query::<(Entity, &TweenAnim, Option<&AnimTarget>)>();
2114 targets.reserve(anims.len());
2115 for entity in anims {
2116 if let Ok((entity, anim, maybe_target)) = q_anims.get(world, *entity) {
2117 // Lazy registration with resolver if needed
2118 if let Some(anim_target) = maybe_target {
2119 anim_target.register(world.components(), &mut resolver);
2120 }
2121
2122 // Actually step the tweenable and update the target
2123 if let Ok((target_type_id, component_id, target, is_retargetable)) =
2124 Self::resolve_target(
2125 world.components(),
2126 maybe_target,
2127 entity,
2128 anim.tweenable(),
2129 )
2130 {
2131 targets.push((
2132 entity,
2133 target_type_id,
2134 component_id,
2135 target,
2136 is_retargetable,
2137 ));
2138 }
2139 }
2140 }
2141 });
2142 Self::step_impl(world, delta_time, &targets[..]);
2143 targets.len()
2144 }
2145
2146 /// Step all animations on the given world.
2147 ///
2148 /// _This function is called automatically by the animation system
2149 /// registered by the [`TweeningPlugin`], you generally don't need to call
2150 /// it._
2151 ///
2152 /// Step all the [`TweenAnim`] components of the input world by a given
2153 /// `delta_time`, which may be [`Duration::ZERO`]. Passing a zero delta
2154 /// time may be useful to force the current animation state to be
2155 /// applied to a target, in case you made change which do not
2156 /// automatically do so (for example, retargeting an animation).
2157 pub fn step_all(world: &mut World, delta_time: Duration) {
2158 let targets = world.resource_scope(|world, mut resolver: Mut<TweenResolver>| {
2159 let mut q_anims = world.query::<(Entity, &TweenAnim, Option<&AnimTarget>)>();
2160 q_anims
2161 .iter(world)
2162 .filter_map(|(entity, anim, maybe_target)| {
2163 // Lazy registration with resolver if needed
2164 if let Some(anim_target) = maybe_target {
2165 anim_target.register(world.components(), &mut resolver);
2166 }
2167
2168 // Actually step the tweenable and update the target
2169 match Self::resolve_target(
2170 world.components(),
2171 maybe_target,
2172 entity,
2173 anim.tweenable(),
2174 ) {
2175 Ok((target_type_id, component_id, target, is_retargetable)) => Some((
2176 entity,
2177 target_type_id,
2178 component_id,
2179 target,
2180 is_retargetable,
2181 )),
2182 Err(err) => {
2183 bevy::log::error!(
2184 "Error while stepping TweenAnim on entity {:?}: {:?}",
2185 entity,
2186 err
2187 );
2188 None
2189 }
2190 }
2191 })
2192 .collect::<Vec<_>>()
2193 });
2194 Self::step_impl(world, delta_time, &targets[..]);
2195 }
2196
2197 fn resolve_target(
2198 components: &Components,
2199 maybe_target: Option<&AnimTarget>,
2200 anim_entity: Entity,
2201 tweenable: &dyn Tweenable,
2202 ) -> Result<(TypeId, ComponentId, AnimTargetKind, bool), TweeningError> {
2203 let type_id = tweenable
2204 .target_type_id()
2205 .ok_or(TweeningError::UntypedTweenable)?;
2206 if let Some(target) = maybe_target {
2207 // Target explicitly specified with AnimTarget component
2208 let component_id = match &target.kind {
2209 AnimTargetKind::Component { .. } => components
2210 .get_id(type_id)
2211 .ok_or(TweeningError::ComponentNotRegistered(type_id))?,
2212 AnimTargetKind::Resource => components
2213 .get_id(type_id)
2214 .ok_or(TweeningError::ResourceNotRegistered(type_id))?,
2215 AnimTargetKind::Asset { assets_type_id, .. } => components
2216 .get_id(*assets_type_id)
2217 .ok_or(TweeningError::AssetNotRegistered(type_id))?,
2218 };
2219 let is_retargetable = false; // explicit target
2220 Ok((type_id, component_id, target.kind, is_retargetable))
2221 } else {
2222 // Target implicitly self; this can only be a component target
2223 let is_retargetable = true;
2224 if let Some(component_id) = components.get_id(type_id) {
2225 Ok((
2226 type_id,
2227 component_id,
2228 AnimTargetKind::Component {
2229 entity: anim_entity,
2230 },
2231 is_retargetable,
2232 ))
2233 } else {
2234 // We can't implicitly target an asset without its AssetId
2235 Err(TweeningError::ComponentNotRegistered(type_id))
2236 }
2237 }
2238 }
2239
2240 fn step_impl(
2241 world: &mut World,
2242 delta_time: Duration,
2243 anims: &[(Entity, TypeId, ComponentId, AnimTargetKind, bool)],
2244 ) {
2245 let mut to_remove = Vec::with_capacity(anims.len());
2246 world.resource_scope(|world, resolver: Mut<TweenResolver>| {
2247 world.resource_scope(
2248 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
2249 world.resource_scope(
2250 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
2251 let anim_comp_id = world.component_id::<TweenAnim>().unwrap();
2252 for (
2253 anim_entity,
2254 target_type_id,
2255 component_id,
2256 anim_target,
2257 is_retargetable,
2258 ) in anims
2259 {
2260 let retain = match anim_target {
2261 AnimTargetKind::Component {
2262 entity: comp_entity,
2263 } => {
2264 let (mut entities, commands) =
2265 world.entities_and_commands();
2266 let ret = if *anim_entity == *comp_entity {
2267 // The TweenAnim animates another component on the same
2268 // entity
2269 let Ok([mut ent]) = entities.get_mut([*anim_entity])
2270 else {
2271 continue;
2272 };
2273 let Ok([anim, target]) =
2274 ent.get_mut_by_id([anim_comp_id, *component_id])
2275 else {
2276 continue;
2277 };
2278 // SAFETY: We fetched the EntityMut from the component
2279 // ID of
2280 // TweenAnim
2281 #[allow(unsafe_code)]
2282 let mut anim = unsafe { anim.with_type::<TweenAnim>() };
2283 anim.step_self(
2284 commands,
2285 *anim_entity,
2286 delta_time,
2287 anim_target,
2288 target,
2289 target_type_id,
2290 cycle_events.reborrow(),
2291 anim_events.reborrow(),
2292 )
2293 } else {
2294 // The TweenAnim animates a component on a different
2295 // entity
2296 let Ok([mut anim, mut target]) =
2297 entities.get_mut([*anim_entity, *comp_entity])
2298 else {
2299 continue;
2300 };
2301 let Some(mut anim) = anim.get_mut::<TweenAnim>() else {
2302 continue;
2303 };
2304 let Ok(target) = target.get_mut_by_id(*component_id)
2305 else {
2306 continue;
2307 };
2308 anim.step_self(
2309 commands,
2310 *anim_entity,
2311 delta_time,
2312 anim_target,
2313 target,
2314 target_type_id,
2315 cycle_events.reborrow(),
2316 anim_events.reborrow(),
2317 )
2318 };
2319 match ret {
2320 Ok(res) => {
2321 if res.needs_retarget {
2322 assert!(res.retain);
2323 if *is_retargetable {
2324 //to_retarget.push(anim_entity);
2325 //true
2326 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);
2327 false
2328 } else {
2329 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);
2330 false
2331 }
2332 } else {
2333 res.retain
2334 }
2335 }
2336 Err(_) => false,
2337 }
2338 }
2339 AnimTargetKind::Resource => resolver
2340 .resolve_resource(
2341 world,
2342 target_type_id,
2343 *component_id,
2344 *anim_entity,
2345 delta_time,
2346 cycle_events.reborrow(),
2347 anim_events.reborrow(),
2348 )
2349 .unwrap_or_else(|err| {
2350 bevy::log::error!(
2351 "Deleting resource animation due to error: {err:?}"
2352 );
2353 false
2354 }),
2355 AnimTargetKind::Asset { asset_id, .. } => resolver
2356 .resolve_asset(
2357 world,
2358 target_type_id,
2359 *component_id,
2360 *asset_id,
2361 *anim_entity,
2362 delta_time,
2363 cycle_events.reborrow(),
2364 anim_events.reborrow(),
2365 )
2366 .unwrap_or_else(|err| {
2367 bevy::log::error!(
2368 "Deleting asset animation due to error: {err:?}"
2369 );
2370 false
2371 }),
2372 };
2373
2374 if !retain {
2375 to_remove.push(*anim_entity);
2376 }
2377 }
2378 },
2379 );
2380 },
2381 );
2382 });
2383
2384 let mut cmds = world.commands();
2385 for entity in to_remove.drain(..) {
2386 cmds.entity(entity).try_remove::<TweenAnim>();
2387 }
2388
2389 world.flush();
2390 }
2391
2392 #[allow(clippy::too_many_arguments)]
2393 fn step_self(
2394 &mut self,
2395 mut commands: Commands,
2396 anim_entity: Entity,
2397 delta_time: Duration,
2398 target_kind: &AnimTargetKind,
2399 mut mut_untyped: MutUntyped,
2400 target_type_id: &TypeId,
2401 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2402 mut anim_events: Mut<Messages<AnimCompletedEvent>>,
2403 ) -> Result<StepResult, TweeningError> {
2404 let mut completed_events = Vec::with_capacity(8);
2405
2406 // Sanity checks on fields which can be freely modified by the user
2407 self.speed = self.speed.max(0.);
2408
2409 // Retain completed animations only if requested
2410 if self.tween_state == TweenState::Completed {
2411 let ret = StepResult {
2412 retain: !self.destroy_on_completion,
2413 needs_retarget: false,
2414 };
2415 return Ok(ret);
2416 }
2417
2418 // Skip paused animations (but retain them)
2419 if self.playback_state == PlaybackState::Paused || self.speed <= 0. {
2420 let ret = StepResult {
2421 retain: true,
2422 needs_retarget: false,
2423 };
2424 return Ok(ret);
2425 }
2426
2427 // Scale delta time by this animation's speed. Reject negative speeds; use
2428 // backward playback to play in reverse direction.
2429 // Note: must use f64 for precision; f32 produces visible roundings.
2430 let delta_time = delta_time.mul_f64(self.speed);
2431
2432 // Step the tweenable animation
2433 let mut notify_completed = || {
2434 completed_events.push(CycleCompletedEvent {
2435 anim_entity,
2436 target: *target_kind,
2437 });
2438 };
2439 let (state, needs_retarget) = self.tweenable.step(
2440 anim_entity,
2441 delta_time,
2442 mut_untyped.reborrow(),
2443 target_type_id,
2444 &mut notify_completed,
2445 );
2446 self.tween_state = state;
2447
2448 // Send tween completed events once we reclaimed mut access to world and can get
2449 // a Commands.
2450 if !completed_events.is_empty() {
2451 for event in completed_events.drain(..) {
2452 // Send buffered event
2453 cycle_events.write(event);
2454
2455 // Trigger all entity-scoped observers
2456 commands.trigger(CycleCompletedEvent {
2457 anim_entity,
2458 ..event
2459 });
2460 }
2461 }
2462
2463 // Raise animation completed event
2464 if state == TweenState::Completed {
2465 let event: AnimCompletedEvent = AnimCompletedEvent {
2466 anim_entity,
2467 target: *target_kind,
2468 };
2469
2470 // Send buffered event
2471 anim_events.write(event);
2472
2473 // Trigger all entity-scoped observers
2474 commands.trigger(event);
2475 }
2476
2477 let ret = StepResult {
2478 retain: state == TweenState::Active || !self.destroy_on_completion,
2479 needs_retarget,
2480 };
2481 Ok(ret)
2482 }
2483
2484 /// Stop animation playback and rewind the animation.
2485 ///
2486 /// This changes the animator state to [`PlaybackState::Paused`] and rewinds
2487 /// its tweenable.
2488 ///
2489 /// # Panics
2490 ///
2491 /// Like [`Tweenable::rewind()`], this panics if the current playback
2492 /// direction is [`PlaybackDirection::Backward`] and the animation is
2493 /// infinitely repeating.
2494 pub fn stop(&mut self) {
2495 self.playback_state = PlaybackState::Paused;
2496 self.tweenable.rewind();
2497 self.tween_state = TweenState::Active;
2498 }
2499
2500 /// Get the tweenable describing this animation.
2501 ///
2502 /// To change the tweenable, use [`TweenAnim::set_tweenable()`].
2503 #[inline]
2504 pub fn tweenable(&self) -> &dyn Tweenable {
2505 self.tweenable.as_ref()
2506 }
2507
2508 /// Set a new animation description.
2509 ///
2510 /// Attempt to change the tweenable of an animation already spawned.
2511 ///
2512 /// If the tweenable is successfully swapped, this resets the
2513 /// [`tween_state()`] to [`TweenState::Active`], even if the tweenable would
2514 /// otherwise be completed _e.g._ because its current elapsed time is past
2515 /// its total duration. Conversely, this doesn't update the target
2516 /// component or asset, as this function doesn't have mutable access to
2517 /// it. To force applying the new state to the target without stepping the
2518 /// animation forward or backward, call one of the stepping functions like
2519 /// [`TweenAnim::step_one()`] passing a delta time of [`Duration::ZERO`].
2520 ///
2521 /// To ensure the old and new animations have the same elapsed time (for
2522 /// example if they need to be synchronized, if they're variants of each
2523 /// other), call [`set_elapsed()`] first on the input `tweenable`, with
2524 /// the duration value of the old tweenable returned by [`elapsed()`].
2525 ///
2526 /// ```
2527 /// # use std::time::Duration;
2528 /// # use bevy::prelude::*;
2529 /// # use bevy_tweening::*;
2530 /// # fn make_tweenable() -> Tween { unimplemented!() }
2531 /// fn my_system(mut anim: Single<&mut TweenAnim>) {
2532 /// let mut tweenable = make_tweenable();
2533 /// let elapsed = anim.tweenable().elapsed();
2534 /// tweenable.set_elapsed(elapsed);
2535 /// anim.set_tweenable(tweenable);
2536 /// }
2537 /// ```
2538 ///
2539 /// # Returns
2540 ///
2541 /// On success, returns the previous tweenable which has been swapped out.
2542 ///
2543 /// [`tween_state()`]: Self::tween_state
2544 /// [`set_elapsed()`]: crate::Tweenable::set_elapsed
2545 /// [`elapsed()`]: crate::Tweenable::elapsed
2546 /// [`step_one()`]: Self::step_one
2547 pub fn set_tweenable<T>(&mut self, tweenable: T) -> Result<BoxedTweenable, TweeningError>
2548 where
2549 T: Tweenable + 'static,
2550 {
2551 let mut old_tweenable: BoxedTweenable = Box::new(tweenable);
2552 std::mem::swap(&mut self.tweenable, &mut old_tweenable);
2553 // Reset tweening state, the new tweenable is at t=0
2554 self.tween_state = TweenState::Active;
2555 Ok(old_tweenable)
2556 }
2557
2558 /// Get the tweening completion state.
2559 ///
2560 /// In general this is [`TweenState::Active`], unless the animation
2561 /// completed and [`destroy_on_completion`] is `false`.
2562 ///
2563 /// [`destroy_on_completion`]: Self::destroy_on_completion
2564 #[inline]
2565 pub fn tween_state(&self) -> TweenState {
2566 self.tween_state
2567 }
2568}
2569
2570type ResourceResolver = Box<
2571 dyn for<'w> Fn(
2572 &mut World,
2573 Entity,
2574 &TypeId,
2575 Duration,
2576 Mut<Messages<CycleCompletedEvent>>,
2577 Mut<Messages<AnimCompletedEvent>>,
2578 ) -> Result<bool, TweeningError>
2579 + Send
2580 + Sync
2581 + 'static,
2582>;
2583
2584type AssetResolver = Box<
2585 dyn for<'w> Fn(
2586 &mut World,
2587 UntypedAssetId,
2588 Entity,
2589 &TypeId,
2590 Duration,
2591 Mut<Messages<CycleCompletedEvent>>,
2592 Mut<Messages<AnimCompletedEvent>>,
2593 ) -> Result<bool, TweeningError>
2594 + Send
2595 + Sync
2596 + 'static,
2597>;
2598
2599/// Resolver for resources and assets.
2600///
2601/// _This resource is largely an implementation detail. You can safely ignore
2602/// it._
2603///
2604/// Bevy doesn't provide a suitable untyped API to access resources and assets
2605/// at runtime without knowing their compile-time type.
2606/// - For resources, most of the API is in place, but unfortunately there's no
2607/// `World::resource_scope_untyped()` to temporarily extract a resource by ID
2608/// to allow concurrent mutability of the resource with other parts of the
2609/// [`World`], in particular the animation target.
2610/// - For assets, there's simply no untyped API. [`Assets`] doesn't allow
2611/// untyped asset access.
2612///
2613/// To work around those limitations, this resolver resource contains
2614/// type-erased closures allowing to resolve an animation target definition into
2615/// a mutable pointer [`MutUntyped`] to that instance, to allow the animation
2616/// engine to apply the animation on it.
2617#[derive(Default, Resource)]
2618pub struct TweenResolver {
2619 /// Resource resolver allowing to call `World::resource_scope()` to extract
2620 /// that resource type form the `World` while in parallel accessing mutably
2621 /// the animation entity itself.
2622 resource_resolver: HashMap<ComponentId, ResourceResolver>,
2623 /// Asset resolver allowing to convert a pair of { untyped pointer to
2624 /// `Assets<A>`, untyped `AssetId` } into an untyped pointer to the asset A
2625 /// itself. This is necessary because there's no UntypedAssets interface in
2626 /// Bevy. The TypeId key must be the type of the `Assets<A>` type itself.
2627 /// The resolver is allowed to fail (return `None`), for example when the
2628 /// asset ID doesn't reference a valid asset.
2629 asset_resolver: HashMap<ComponentId, AssetResolver>,
2630}
2631
2632impl TweenResolver {
2633 /// Register a resolver for the given resource type.
2634 pub(crate) fn register_resource_resolver_for<R: Resource>(&mut self, components: &Components) {
2635 let resource_id = components.component_id::<R>().unwrap();
2636 let resolver = |world: &mut World,
2637 entity: Entity,
2638 target_type_id: &TypeId,
2639 delta_time: Duration,
2640 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2641 mut anim_events: Mut<Messages<AnimCompletedEvent>>|
2642 -> Result<bool, TweeningError> {
2643 // First, remove the resource R from the world so we can access it mutably in
2644 // parallel of the TweenAnim
2645 world.resource_scope(|world, resource: Mut<R>| {
2646 let target = AnimTargetKind::Resource;
2647
2648 let (mut entities, commands) = world.entities_and_commands();
2649
2650 // Resolve the TweenAnim component
2651 let Ok([mut ent]) = entities.get_mut([entity]) else {
2652 return Err(TweeningError::EntityNotFound(entity));
2653 };
2654 let Some(mut anim) = ent.get_mut::<TweenAnim>() else {
2655 return Err(TweeningError::MissingTweenAnim(ent.id()));
2656 };
2657
2658 // Finally, step the TweenAnim and mutate the target
2659 let ret = anim.step_self(
2660 commands,
2661 entity,
2662 delta_time,
2663 &target,
2664 resource.into(),
2665 target_type_id,
2666 cycle_events.reborrow(),
2667 anim_events.reborrow(),
2668 );
2669 ret.map(|result| {
2670 assert!(!result.needs_retarget, "Cannot use a multi-target sequence of tweenable animations with a resource target.");
2671 result.retain
2672 })
2673 })
2674 };
2675 self.resource_resolver
2676 .entry(resource_id)
2677 .or_insert(Box::new(resolver));
2678 }
2679
2680 /// Register a resolver for the given asset type.
2681 pub(crate) fn register_asset_resolver_for<A: Asset>(&mut self, components: &Components) {
2682 let resource_id = components.component_id::<Assets<A>>().unwrap();
2683 let resolver = |world: &mut World,
2684 asset_id: UntypedAssetId,
2685 entity: Entity,
2686 target_type_id: &TypeId,
2687 delta_time: Duration,
2688 mut cycle_events: Mut<Messages<CycleCompletedEvent>>,
2689 mut anim_events: Mut<Messages<AnimCompletedEvent>>|
2690 -> Result<bool, TweeningError> {
2691 let asset_id = asset_id.typed::<A>();
2692 // First, remove the Assets<A> from the world so we can access it mutably in
2693 // parallel of the TweenAnim
2694 world.resource_scope(|world, mut assets: Mut<Assets<A>>| {
2695 // We abuse the fact that Assets<A> is changed every single frame by the asset_events()
2696 // system, and assume that the current tick is therefore equal to the last time
2697 // Assets<A> was changed.
2698 let this_tick = assets.last_changed().get();
2699
2700 let Some(mut asset_mut) = assets.get_mut(asset_id)
2701 else {
2702 return Err(TweeningError::InvalidAssetId(asset_id.into()));
2703 };
2704
2705 let target = AnimTargetKind::Asset {
2706 asset_id: asset_id.untyped(),
2707 assets_type_id: TypeId::of::<Assets<A>>(),
2708 };
2709
2710 let (mut entities, commands) = world.entities_and_commands();
2711
2712 // Resolve the TweenAnim component
2713 let Ok([mut ent]) = entities.get_mut([entity]) else {
2714 return Err(TweeningError::EntityNotFound(entity));
2715 };
2716 let Some(mut anim) = ent.get_mut::<TweenAnim>() else {
2717 return Err(TweeningError::MissingTweenAnim(ent.id()));
2718 };
2719
2720 // Create a fake Mut<A> which is always unchanged before the anim steps.
2721 // Its sole purpose is to know if the Lens::lerp() changed the asset.
2722 // Ideally we'd directly use AssetMut<> but the interface doesn't match.
2723 let mut added = Tick::MAX; // hopefully unused...
2724 let last_tick = this_tick.saturating_sub(1);
2725 let mut last_changed = Tick::new(last_tick);
2726 let last_run = last_changed;
2727 let this_run = Tick::new(this_tick);
2728 let mut caller = MaybeLocation::caller();
2729 let typed_mut = Mut::new(asset_mut.bypass_change_detection(), &mut added, &mut last_changed, last_run, this_run, caller.as_mut());
2730 assert!(!typed_mut.is_changed());
2731 let mut mut_untyped: MutUntyped = typed_mut.into();
2732
2733 // Finally, step the TweenAnim and mutate the target
2734 let ret = anim.step_self(
2735 commands,
2736 entity,
2737 delta_time,
2738 &target,
2739 mut_untyped.reborrow(),
2740 target_type_id,
2741 cycle_events.reborrow(),
2742 anim_events.reborrow(),
2743 );
2744
2745 // If the asset actually changed (as reported by Mut<>), mark it as such
2746 // through Assets<A> (which will send the asset modified message).
2747 if mut_untyped.is_changed() {
2748 // into_inner() marks the asset as changed, which triggers the regular
2749 // asset modified flow (emits AssetEvents::Modified).
2750 let _ = asset_mut.into_inner();
2751 }
2752
2753 ret.map(|result| {
2754 assert!(!result.needs_retarget, "Cannot use a multi-target sequence of tweenable animations with an asset target.");
2755 result.retain
2756 })
2757 })
2758 };
2759 self.asset_resolver
2760 .entry(resource_id)
2761 .or_insert(Box::new(resolver));
2762 }
2763
2764 #[allow(clippy::too_many_arguments)]
2765 #[inline]
2766 pub(crate) fn resolve_resource(
2767 &self,
2768 world: &mut World,
2769 target_type_id: &TypeId,
2770 resource_id: ComponentId,
2771 entity: Entity,
2772 delta_time: Duration,
2773 cycle_events: Mut<Messages<CycleCompletedEvent>>,
2774 anim_events: Mut<Messages<AnimCompletedEvent>>,
2775 ) -> Result<bool, TweeningError> {
2776 let Some(resolver) = self.resource_resolver.get(&resource_id) else {
2777 println!("ERROR: resource not registered {:?}", resource_id);
2778 return Err(TweeningError::AssetResolverNotRegistered(resource_id));
2779 };
2780 resolver(
2781 world,
2782 entity,
2783 target_type_id,
2784 delta_time,
2785 cycle_events,
2786 anim_events,
2787 )
2788 }
2789
2790 #[allow(clippy::too_many_arguments)]
2791 #[inline]
2792 pub(crate) fn resolve_asset(
2793 &self,
2794 world: &mut World,
2795 target_type_id: &TypeId,
2796 resource_id: ComponentId,
2797 untyped_asset_id: UntypedAssetId,
2798 entity: Entity,
2799 delta_time: Duration,
2800 cycle_events: Mut<Messages<CycleCompletedEvent>>,
2801 anim_events: Mut<Messages<AnimCompletedEvent>>,
2802 ) -> Result<bool, TweeningError> {
2803 let Some(resolver) = self.asset_resolver.get(&resource_id) else {
2804 println!("ERROR: asset not registered {:?}", resource_id);
2805 return Err(TweeningError::AssetResolverNotRegistered(resource_id));
2806 };
2807 resolver(
2808 world,
2809 untyped_asset_id,
2810 entity,
2811 target_type_id,
2812 delta_time,
2813 cycle_events,
2814 anim_events,
2815 )
2816 }
2817}
2818
2819pub(crate) struct StepResult {
2820 /// Whether to retain the current [`TweenAnim`]? If `false`, the
2821 /// [`TweenAnim`] is destroyed unless [`TweenAnim::destroy_on_completion`]
2822 /// is `false`.
2823 pub retain: bool,
2824 /// Whether to recompute the new animation target and step again. This is
2825 /// used by sequences when the animation target changes type in a sequence.
2826 pub needs_retarget: bool,
2827}
2828
2829#[cfg(test)]
2830mod tests {
2831 use std::{
2832 f32::consts::{FRAC_PI_2, TAU},
2833 marker::PhantomData,
2834 };
2835
2836 use bevy::ecs::{change_detection::MaybeLocation, change_detection::Tick};
2837
2838 use super::*;
2839 use crate::test_utils::*;
2840
2841 struct DummyLens {
2842 start: f32,
2843 end: f32,
2844 }
2845
2846 struct DummyLens2 {
2847 start: i32,
2848 end: i32,
2849 }
2850
2851 #[derive(Debug, Default, Clone, Copy, Component)]
2852 struct DummyComponent {
2853 value: f32,
2854 }
2855
2856 #[derive(Debug, Default, Clone, Copy, Component)]
2857 struct DummyComponent2 {
2858 value: i32,
2859 }
2860
2861 #[derive(Debug, Default, Clone, Copy, Resource)]
2862 struct DummyResource {
2863 value: f32,
2864 }
2865
2866 #[derive(Asset, Debug, Default, Reflect)]
2867 struct DummyAsset {
2868 value: f32,
2869 }
2870
2871 impl Lens<DummyComponent> for DummyLens {
2872 fn lerp(&mut self, mut target: Mut<DummyComponent>, ratio: f32) {
2873 target.value = self.start.lerp(self.end, ratio);
2874 }
2875 }
2876
2877 impl Lens<DummyComponent2> for DummyLens2 {
2878 fn lerp(&mut self, mut target: Mut<DummyComponent2>, ratio: f32) {
2879 target.value = ((self.start as f32) * (1. - ratio) + (self.end as f32) * ratio) as i32;
2880 }
2881 }
2882
2883 #[test]
2884 fn dummy_lens_component() {
2885 let mut c = DummyComponent::default();
2886 let mut l = DummyLens { start: 0., end: 1. };
2887 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2888 {
2889 let mut added = Tick::new(0);
2890 let mut last_changed = Tick::new(0);
2891 let mut caller = MaybeLocation::caller();
2892 let mut target = Mut::new(
2893 &mut c,
2894 &mut added,
2895 &mut last_changed,
2896 Tick::new(0),
2897 Tick::new(1),
2898 caller.as_mut(),
2899 );
2900
2901 l.lerp(target.reborrow(), r);
2902
2903 assert!(target.is_changed());
2904 }
2905 assert_approx_eq!(c.value, r);
2906 }
2907 }
2908
2909 impl Lens<DummyResource> for DummyLens {
2910 fn lerp(&mut self, mut target: Mut<DummyResource>, ratio: f32) {
2911 target.value = self.start.lerp(self.end, ratio);
2912 }
2913 }
2914
2915 #[test]
2916 fn dummy_lens_resource() {
2917 let mut res = DummyResource::default();
2918 let mut l = DummyLens { start: 0., end: 1. };
2919 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2920 {
2921 let mut added = Tick::new(0);
2922 let mut last_changed = Tick::new(0);
2923 let mut caller = MaybeLocation::caller();
2924 let mut target = Mut::new(
2925 &mut res,
2926 &mut added,
2927 &mut last_changed,
2928 Tick::new(0),
2929 Tick::new(0),
2930 caller.as_mut(),
2931 );
2932 l.lerp(target.reborrow(), r);
2933 }
2934 assert_approx_eq!(res.value, r);
2935 }
2936 }
2937
2938 impl Lens<DummyAsset> for DummyLens {
2939 fn lerp(&mut self, mut target: Mut<DummyAsset>, ratio: f32) {
2940 target.value = self.start.lerp(self.end, ratio);
2941 }
2942 }
2943
2944 #[test]
2945 fn dummy_lens_asset() {
2946 let mut assets = Assets::<DummyAsset>::default();
2947 let handle = assets.add(DummyAsset::default());
2948
2949 let mut l = DummyLens { start: 0., end: 1. };
2950 for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2951 {
2952 let mut added = Tick::new(0);
2953 let mut last_changed = Tick::new(0);
2954 let mut caller = MaybeLocation::caller();
2955 let asset = assets.get_mut_untracked(handle.id()).unwrap();
2956 let target = Mut::new(
2957 asset,
2958 &mut added,
2959 &mut last_changed,
2960 Tick::new(0),
2961 Tick::new(0),
2962 caller.as_mut(),
2963 );
2964 l.lerp(target, r);
2965 }
2966 assert_approx_eq!(assets.get(handle.id()).unwrap().value, r);
2967 }
2968 }
2969
2970 #[test]
2971 fn repeat_count() {
2972 let cycle_duration = Duration::from_millis(100);
2973
2974 let repeat = RepeatCount::default();
2975 assert_eq!(repeat, RepeatCount::Finite(1));
2976 assert_eq!(
2977 repeat.total_duration(cycle_duration),
2978 TotalDuration::Finite(cycle_duration)
2979 );
2980
2981 let repeat: RepeatCount = 3u32.into();
2982 assert_eq!(repeat, RepeatCount::Finite(3));
2983 assert_eq!(
2984 repeat.total_duration(cycle_duration),
2985 TotalDuration::Finite(cycle_duration * 3)
2986 );
2987
2988 let duration = Duration::from_secs(5);
2989 let repeat: RepeatCount = duration.into();
2990 assert_eq!(repeat, RepeatCount::For(duration));
2991 assert_eq!(
2992 repeat.total_duration(cycle_duration),
2993 TotalDuration::Finite(duration)
2994 );
2995
2996 let repeat = RepeatCount::Infinite;
2997 assert_eq!(
2998 repeat.total_duration(cycle_duration),
2999 TotalDuration::Infinite
3000 );
3001 }
3002
3003 #[test]
3004 fn repeat_strategy() {
3005 let strategy = RepeatStrategy::default();
3006 assert_eq!(strategy, RepeatStrategy::Repeat);
3007 }
3008
3009 #[test]
3010 fn playback_direction() {
3011 let tweening_direction = PlaybackDirection::default();
3012 assert_eq!(tweening_direction, PlaybackDirection::Forward);
3013 }
3014
3015 #[test]
3016 fn playback_state() {
3017 let mut state = PlaybackState::default();
3018 assert_eq!(state, PlaybackState::Playing);
3019 state = !state;
3020 assert_eq!(state, PlaybackState::Paused);
3021 state = !state;
3022 assert_eq!(state, PlaybackState::Playing);
3023 }
3024
3025 #[test]
3026 fn ease_method() {
3027 let ease = EaseMethod::default();
3028 assert!(matches!(
3029 ease,
3030 EaseMethod::EaseFunction(EaseFunction::Linear)
3031 ));
3032
3033 let ease = EaseMethod::EaseFunction(EaseFunction::QuadraticIn);
3034 assert_eq!(0., ease.sample(0.));
3035 assert_eq!(0.25, ease.sample(0.5));
3036 assert_eq!(1., ease.sample(1.));
3037
3038 let ease = EaseMethod::EaseFunction(EaseFunction::Linear);
3039 assert_eq!(0., ease.sample(0.));
3040 assert_eq!(0.5, ease.sample(0.5));
3041 assert_eq!(1., ease.sample(1.));
3042
3043 let ease = EaseMethod::Discrete(0.3);
3044 assert_eq!(0., ease.sample(0.));
3045 assert_eq!(1., ease.sample(0.5));
3046 assert_eq!(1., ease.sample(1.));
3047
3048 let ease = EaseMethod::CustomFunction(|f| 1. - f);
3049 assert_eq!(0., ease.sample(1.));
3050 assert_eq!(0.5, ease.sample(0.5));
3051 assert_eq!(1., ease.sample(0.));
3052 }
3053
3054 // TweenAnim::playback_state is entirely user-controlled; stepping animations
3055 // won't change it.
3056 #[test]
3057 fn animation_playback_state() {
3058 for state in [PlaybackState::Playing, PlaybackState::Paused] {
3059 let tween = Tween::new::<DummyComponent, DummyLens>(
3060 EaseFunction::QuadraticInOut,
3061 Duration::from_secs(1),
3062 DummyLens { start: 0., end: 1. },
3063 );
3064 let mut env = TestEnv::<DummyComponent>::new(tween);
3065 let mut anim = env.anim_mut().unwrap();
3066 anim.playback_state = state;
3067 anim.destroy_on_completion = false;
3068
3069 // Tick once
3070 let dt = Duration::from_millis(100);
3071 env.step_all(dt);
3072 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3073 assert_eq!(env.anim().unwrap().playback_state, state);
3074
3075 // Check elapsed
3076 let elapsed = match state {
3077 PlaybackState::Playing => dt,
3078 PlaybackState::Paused => Duration::ZERO,
3079 };
3080 assert_eq!(env.anim().unwrap().tweenable.elapsed(), elapsed);
3081
3082 // Force playback, otherwise we can't complete
3083 env.anim_mut().unwrap().playback_state = PlaybackState::Playing;
3084
3085 // Even after completion, the playback state is untouched
3086 env.step_all(Duration::from_secs(10) - elapsed);
3087 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Completed);
3088 assert_eq!(env.anim().unwrap().playback_state, PlaybackState::Playing);
3089 }
3090 }
3091
3092 #[test]
3093 fn animation_events() {
3094 let tween = Tween::new::<DummyComponent, DummyLens>(
3095 EaseFunction::QuadraticInOut,
3096 Duration::from_secs(1),
3097 DummyLens { start: 0., end: 1. },
3098 )
3099 .with_repeat_count(2)
3100 .with_cycle_completed_event(true);
3101 let mut env = TestEnv::<DummyComponent>::new(tween);
3102
3103 // Tick until one cycle is completed, but not the entire animation
3104 let dt = Duration::from_millis(1200);
3105 env.step_all(dt);
3106 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3107
3108 // Check events
3109 assert_eq!(env.event_count::<CycleCompletedEvent>(), 1);
3110 assert_eq!(env.event_count::<AnimCompletedEvent>(), 0);
3111
3112 // Tick until completion
3113 let dt = Duration::from_millis(1000);
3114 env.step_all(dt);
3115 assert!(env.anim().is_none());
3116
3117 // Check events (note that we didn't clear previous events, so that's a
3118 // cumulative count).
3119 assert_eq!(env.event_count::<CycleCompletedEvent>(), 1);
3120 assert_eq!(env.event_count::<AnimCompletedEvent>(), 1);
3121 }
3122
3123 #[derive(Debug, Resource)]
3124 struct Count<E: Event, T = ()> {
3125 pub count: i32,
3126 pub phantom: PhantomData<E>,
3127 pub phantom2: PhantomData<T>,
3128 }
3129
3130 impl<E: Event, T> Default for Count<E, T> {
3131 fn default() -> Self {
3132 Self {
3133 count: 0,
3134 phantom: PhantomData,
3135 phantom2: PhantomData,
3136 }
3137 }
3138 }
3139
3140 struct GlobalMarker;
3141
3142 #[test]
3143 fn animation_observe() {
3144 let tween = Tween::new::<DummyComponent, DummyLens>(
3145 EaseFunction::QuadraticInOut,
3146 Duration::from_secs(1),
3147 DummyLens { start: 0., end: 1. },
3148 )
3149 .with_repeat_count(2)
3150 .with_cycle_completed_event(true);
3151 let mut env = TestEnv::<DummyComponent>::new(tween);
3152
3153 env.world.init_resource::<Count<CycleCompletedEvent>>();
3154 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 0);
3155 env.world
3156 .init_resource::<Count<CycleCompletedEvent, GlobalMarker>>();
3157 assert_eq!(
3158 env.world
3159 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3160 .count,
3161 0
3162 );
3163
3164 fn observe_global(
3165 _trigger: On<CycleCompletedEvent>,
3166 mut count: ResMut<Count<CycleCompletedEvent, GlobalMarker>>,
3167 ) {
3168 count.count += 1;
3169 }
3170 env.world.add_observer(observe_global);
3171
3172 fn observe_entity(
3173 _trigger: On<CycleCompletedEvent>,
3174 mut count: ResMut<Count<CycleCompletedEvent>>,
3175 ) {
3176 count.count += 1;
3177 }
3178 env.world.entity_mut(env.entity).observe(observe_entity);
3179
3180 // Tick until one cycle is completed, but not the entire animation
3181 let dt = Duration::from_millis(1200);
3182 env.step_all(dt);
3183 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3184
3185 // Check observer system ran
3186 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 1);
3187 assert_eq!(
3188 env.world
3189 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3190 .count,
3191 1
3192 );
3193
3194 // Tick until completion
3195 let dt = Duration::from_millis(1000);
3196 env.step_all(dt);
3197 assert!(env.anim().is_none());
3198
3199 // Check observer system ran (note that we didn't clear previous events, so
3200 // that's a cumulative count).
3201 assert_eq!(env.world.resource::<Count<CycleCompletedEvent>>().count, 2);
3202 assert_eq!(
3203 env.world
3204 .resource::<Count<CycleCompletedEvent, GlobalMarker>>()
3205 .count,
3206 2
3207 );
3208 }
3209
3210 // #[test]
3211 // fn animator_controls() {
3212 // let tween = Tween::<DummyComponent>::new(
3213 // EaseFunction::QuadraticInOut,
3214 // Duration::from_secs(1),
3215 // DummyLens { start: 0., end: 1. },
3216 // );
3217 // let mut animator = Animator::new(tween);
3218 // assert_eq!(animator.state, AnimatorState::Playing);
3219 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3220
3221 // animator.stop();
3222 // assert_eq!(animator.state, AnimatorState::Paused);
3223 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3224
3225 // animator.tweenable_mut().set_progress(0.5);
3226 // assert_eq!(animator.state, AnimatorState::Paused);
3227 // assert_approx_eq!(animator.tweenable().progress(), 0.5);
3228
3229 // animator.tweenable_mut().rewind();
3230 // assert_eq!(animator.state, AnimatorState::Paused);
3231 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3232
3233 // animator.tweenable_mut().set_progress(0.5);
3234 // animator.state = AnimatorState::Playing;
3235 // assert_eq!(animator.state, AnimatorState::Playing);
3236 // assert_approx_eq!(animator.tweenable().progress(), 0.5);
3237
3238 // animator.tweenable_mut().rewind();
3239 // assert_eq!(animator.state, AnimatorState::Playing);
3240 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3241
3242 // animator.stop();
3243 // assert_eq!(animator.state, AnimatorState::Paused);
3244 // assert_approx_eq!(animator.tweenable().progress(), 0.);
3245 // }
3246
3247 #[test]
3248 fn animation_speed() {
3249 let tween = Tween::new::<DummyComponent, DummyLens>(
3250 EaseFunction::QuadraticInOut,
3251 Duration::from_secs(1),
3252 DummyLens { start: 0., end: 1. },
3253 );
3254
3255 let mut env = TestEnv::<DummyComponent>::new(tween);
3256
3257 assert_approx_eq!(env.anim().unwrap().speed, 1.); // default speed
3258
3259 env.anim_mut().unwrap().speed = 2.4;
3260 assert_approx_eq!(env.anim().unwrap().speed, 2.4);
3261
3262 env.step_all(Duration::from_millis(100));
3263 // Here we have enough precision for exact equality, but that may not always be
3264 // the case for larger durations or speed values.
3265 assert_eq!(
3266 env.anim().unwrap().tweenable.elapsed(),
3267 Duration::from_millis(240)
3268 );
3269
3270 env.anim_mut().unwrap().speed = -1.;
3271 env.step_all(Duration::from_millis(100));
3272 // Safety: invalid negative speed clamped to 0.
3273 assert_eq!(env.anim().unwrap().speed, 0.);
3274 // At zero speed, step is a no-op so elapse() didn't change
3275 assert_eq!(
3276 env.anim().unwrap().tweenable.elapsed(),
3277 Duration::from_millis(240)
3278 );
3279 }
3280
3281 #[test]
3282 fn animator_set_tweenable() {
3283 let tween = Tween::new::<DummyComponent, DummyLens>(
3284 EaseFunction::QuadraticInOut,
3285 Duration::from_secs(1),
3286 DummyLens { start: 0., end: 1. },
3287 );
3288 let tween2 = Tween::new::<DummyComponent, DummyLens>(
3289 EaseFunction::SmoothStep,
3290 Duration::from_secs(2),
3291 DummyLens { start: 2., end: 3. },
3292 );
3293
3294 let mut env = TestEnv::<DummyComponent>::new(tween);
3295 env.anim_mut().unwrap().destroy_on_completion = false;
3296
3297 let dt = Duration::from_millis(1500);
3298
3299 env.step_all(dt);
3300 assert_eq!(env.component().value, 1.);
3301 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Completed);
3302
3303 // Swap tweens
3304 let old_tweenable = env.anim_mut().unwrap().set_tweenable(tween2).unwrap();
3305
3306 assert_eq!(env.anim().unwrap().tween_state(), TweenState::Active);
3307 // The elapsed is stored inside the tweenable
3308 assert_eq!(old_tweenable.elapsed(), Duration::from_secs(1)); // capped at total_duration()
3309 assert_eq!(env.anim().unwrap().tweenable.elapsed(), Duration::ZERO);
3310
3311 env.step_all(dt);
3312 assert!(env.component().value >= 2. && env.component().value <= 3.);
3313 }
3314
3315 // Currently multi-target sequences are not implemented. This _could_ work with
3316 // implicit targets (so, multiple components on the same entity), but is a bit
3317 // complex to implement with the current code. So leave that out for now, and
3318 // test we assert if the user attempts it. The workaround is to create separate
3319 // animations for each comopnent/target. Anyway multi-target sequence can't work
3320 // with other target types, since they need an explicit TweenAnim, and we
3321 // can't have more than one per entity.
3322 #[test]
3323 #[should_panic(
3324 expected = "TODO: Cannot use tweenable animations with different targets inside the same Sequence. Create separate animations for each target."
3325 )]
3326 fn seq_multi_target() {
3327 let tween = Tween::new::<DummyComponent, DummyLens>(
3328 EaseFunction::QuadraticInOut,
3329 Duration::from_secs(1),
3330 DummyLens { start: 0., end: 1. },
3331 )
3332 .then(Tween::new::<DummyComponent2, DummyLens2>(
3333 EaseFunction::SmoothStep,
3334 Duration::from_secs(1),
3335 DummyLens2 { start: -5, end: 5 },
3336 ));
3337 let mut env = TestEnv::<DummyComponent>::new(tween);
3338 let entity = env.entity;
3339 env.world
3340 .entity_mut(entity)
3341 .insert(DummyComponent2 { value: -42 });
3342 TweenAnim::step_one(&mut env.world, Duration::from_millis(1100), entity).unwrap();
3343 }
3344
3345 // #[test]
3346 // fn animator_set_target() {
3347 // let tween = Tween::new::<DummyComponent, DummyLens>(
3348 // EaseFunction::QuadraticInOut,
3349 // Duration::from_secs(1),
3350 // DummyLens { start: 0., end: 1. },
3351 // );
3352 // let mut env = TestEnv::<DummyComponent>::new(tween);
3353
3354 // // Register our custom asset type
3355 // env.world.init_resource::<Assets<DummyAsset>>();
3356
3357 // // Invalid ID
3358 // {
3359 // let entity = env.entity;
3360 // let target =
3361 //
3362 // ComponentAnimTarget::new::<DummyComponent>(env.world.components(),
3363 // entity).unwrap(); let err = env
3364 // .animator_mut()
3365 // .set_target(Entity::PLACEHOLDER, target.into())
3366 // .err()
3367 // .unwrap();
3368 // let TweeningError::InvalidTweenId(err_id) = err else {
3369 // panic!();
3370 // };
3371 // assert_eq!(err_id, Entity::PLACEHOLDER);
3372 // }
3373
3374 // // Spawn a second entity without any animation
3375 // let entity1 = env.entity;
3376 // let entity2 = env.world_mut().spawn(DummyComponent { value: 0.
3377 // }).id(); assert_ne!(entity1, entity2);
3378 // assert_eq!(env.component().value, 0.);
3379
3380 // // Step the current target
3381 // let dt = Duration::from_millis(100);
3382 // env.step_all(dt);
3383 // assert!(env.component().value > 0.);
3384 // assert_eq!(
3385 // env.world
3386 // .entity(entity2)
3387 // .get_components::<&DummyComponent>()
3388 // .unwrap()
3389 // .value,
3390 // 0.
3391 // );
3392
3393 // // Now retarget
3394 // let id = env.entity;
3395 // let target2 =
3396 // ComponentAnimTarget::new::<DummyComponent>(env.world.
3397 // components(), entity2).unwrap(); let target1 =
3398 // env.animator_mut().set_target(id, target2.into()).unwrap();
3399 // assert!(target1.is_component());
3400 // let comp1 = target1.as_component().unwrap();
3401 // assert_eq!(comp1.entity, entity1);
3402 // assert_eq!(
3403 // comp1.component_id,
3404 // env.world.component_id::<DummyComponent>().unwrap()
3405 // );
3406
3407 // // Step the new target
3408 // env.step_all(dt);
3409 // assert!(env.component().value > 0.);
3410 // assert!(
3411 // env.world
3412 // .entity(entity1)
3413 // .get_components::<&DummyComponent>()
3414 // .unwrap()
3415 // .value
3416 // > 0.
3417 // );
3418
3419 // // Invalid target
3420 // {
3421 // let target3 =
3422 // AssetAnimTarget::new(env.world.components(),
3423 // Handle::<DummyAsset>::default().id()) .unwrap();
3424 // let err3 = env.animator_mut().set_target(id, target3.into());
3425 // assert!(err3.is_err());
3426 // let err3 = err3.err().unwrap();
3427 // let TweeningError::MismatchingTargetKind(oc, nc) = err3 else {
3428 // panic!();
3429 // };
3430 // assert_eq!(oc, true);
3431 // assert_eq!(nc, false);
3432 // }
3433 // }
3434
3435 #[test]
3436 fn anim_target_component() {
3437 let mut env = TestEnv::<Transform>::empty();
3438 let entity = env.world.spawn(Transform::default()).id();
3439 let tween = Tween::new::<Transform, TransformPositionLens>(
3440 EaseFunction::Linear,
3441 Duration::from_secs(1),
3442 TransformPositionLens {
3443 start: Vec3::ZERO,
3444 end: Vec3::ONE,
3445 },
3446 );
3447 let target = AnimTarget::component::<Transform>(entity);
3448 let anim_entity = env
3449 .world
3450 .spawn((
3451 TweenAnim::new(tween)
3452 .with_speed(2.)
3453 .with_destroy_on_completed(true),
3454 target,
3455 ))
3456 .id();
3457
3458 // Step
3459 assert!(
3460 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3461 );
3462 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3463 assert_eq!(tr.translation, Vec3::ONE * 0.2);
3464
3465 // Complete
3466 assert_eq!(
3467 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3468 1
3469 );
3470
3471 // Destroyed on completion
3472 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3473 }
3474
3475 #[test]
3476 fn anim_target_resource() {
3477 let mut env = TestEnv::<Transform>::empty();
3478 env.world.init_resource::<DummyResource>();
3479 let tween = Tween::new::<DummyResource, DummyLens>(
3480 EaseFunction::Linear,
3481 Duration::from_secs(1),
3482 DummyLens { start: 0., end: 1. },
3483 );
3484 let target = AnimTarget::resource::<DummyResource>();
3485 let anim_entity = env
3486 .world
3487 .spawn((
3488 TweenAnim::new(tween)
3489 .with_speed(2.)
3490 .with_destroy_on_completed(true),
3491 target,
3492 ))
3493 .id();
3494
3495 // Step
3496 assert!(
3497 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3498 );
3499 let res = env.world.resource::<DummyResource>();
3500 assert_eq!(res.value, 0.2);
3501
3502 // Complete
3503 assert_eq!(
3504 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3505 1
3506 );
3507
3508 // Destroyed on completion
3509 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3510 }
3511
3512 #[test]
3513 fn anim_target_asset() {
3514 let mut env = TestEnv::<Transform>::empty();
3515 let mut assets = Assets::<DummyAsset>::default();
3516 let handle = assets.add(DummyAsset::default());
3517 env.world.insert_resource(assets);
3518 let tween = Tween::new::<DummyAsset, DummyLens>(
3519 EaseFunction::Linear,
3520 Duration::from_secs(1),
3521 DummyLens { start: 0., end: 1. },
3522 );
3523 let target = AnimTarget::asset::<DummyAsset>(&handle);
3524 let anim_entity = env
3525 .world
3526 .spawn((
3527 TweenAnim::new(tween)
3528 .with_speed(2.)
3529 .with_destroy_on_completed(true),
3530 target,
3531 ))
3532 .id();
3533
3534 // Step
3535 assert!(
3536 TweenAnim::step_one(&mut env.world, Duration::from_millis(100), anim_entity).is_ok()
3537 );
3538 let assets = env.world.resource::<Assets<DummyAsset>>();
3539 let asset = assets.get(&handle).unwrap();
3540 assert_eq!(asset.value, 0.2);
3541
3542 // Complete
3543 assert_eq!(
3544 TweenAnim::step_many(&mut env.world, Duration::from_millis(400), &[anim_entity]),
3545 1
3546 );
3547
3548 // Destroyed on completion
3549 assert!(env.world.entity(anim_entity).get::<TweenAnim>().is_none());
3550 }
3551
3552 #[test]
3553 fn animated_entity_commands_common() {
3554 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3555 EaseFunction::QuadraticInOut,
3556 Duration::from_secs(1),
3557 DummyLens { start: 0., end: 1. },
3558 );
3559 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3560
3561 let entity = env
3562 .world
3563 .commands()
3564 .spawn(Transform::default())
3565 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3566 .with_repeat_count(4)
3567 .with_repeat_strategy(RepeatStrategy::MirroredRepeat)
3568 .id();
3569 let entity2 = env
3570 .world
3571 .commands()
3572 .spawn(Transform::default())
3573 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3574 .with_repeat(4, RepeatStrategy::MirroredRepeat)
3575 .into_inner()
3576 .id();
3577 env.world.flush();
3578
3579 env.step_all(Duration::from_millis(3300));
3580
3581 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3582 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3583 let tr = env.world.entity(entity2).get::<Transform>().unwrap();
3584 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3585 }
3586
3587 #[test]
3588 fn animated_entity_commands_move_to() {
3589 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3590 EaseFunction::QuadraticInOut,
3591 Duration::from_secs(1),
3592 DummyLens { start: 0., end: 1. },
3593 );
3594 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3595
3596 let entity = env
3597 .world
3598 .commands()
3599 .spawn(Transform::default())
3600 .move_to(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3601 .id();
3602 env.world.flush();
3603
3604 env.step_all(Duration::from_millis(300));
3605
3606 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3607 assert_eq!(tr.translation, Vec3::ONE * 0.3);
3608 }
3609
3610 #[test]
3611 fn animated_entity_commands_move_from() {
3612 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3613 EaseFunction::QuadraticInOut,
3614 Duration::from_secs(1),
3615 DummyLens { start: 0., end: 1. },
3616 );
3617 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3618
3619 let entity = env
3620 .world
3621 .commands()
3622 .spawn(Transform::default())
3623 .move_from(Vec3::ONE, Duration::from_secs(1), EaseFunction::Linear)
3624 .id();
3625 env.world.flush();
3626
3627 env.step_all(Duration::from_millis(300));
3628
3629 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3630 assert_eq!(tr.translation, Vec3::ONE * 0.7);
3631 }
3632
3633 #[test]
3634 fn animated_entity_commands_scale_to() {
3635 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3636 EaseFunction::QuadraticInOut,
3637 Duration::from_secs(1),
3638 DummyLens { start: 0., end: 1. },
3639 );
3640 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3641
3642 let entity = env
3643 .world
3644 .commands()
3645 .spawn(Transform::default())
3646 .scale_to(Vec3::ONE * 2., Duration::from_secs(1), EaseFunction::Linear)
3647 .id();
3648 env.world.flush();
3649
3650 env.step_all(Duration::from_millis(300));
3651
3652 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3653 assert_eq!(tr.scale, Vec3::ONE * 1.3);
3654 }
3655
3656 #[test]
3657 fn animated_entity_commands_scale_from() {
3658 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3659 EaseFunction::QuadraticInOut,
3660 Duration::from_secs(1),
3661 DummyLens { start: 0., end: 1. },
3662 );
3663 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3664
3665 let entity = env
3666 .world
3667 .commands()
3668 .spawn(Transform::default())
3669 .scale_from(Vec3::ONE * 2., Duration::from_secs(1), EaseFunction::Linear)
3670 .id();
3671 env.world.flush();
3672
3673 env.step_all(Duration::from_millis(300));
3674
3675 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3676 assert_eq!(tr.scale, Vec3::ONE * 1.7);
3677 }
3678
3679 #[test]
3680 fn animated_entity_commands_rotate_x() {
3681 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3682 EaseFunction::QuadraticInOut,
3683 Duration::from_secs(1),
3684 DummyLens { start: 0., end: 1. },
3685 );
3686 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3687
3688 let entity = env
3689 .world
3690 .commands()
3691 .spawn(Transform::default())
3692 .rotate_x(Duration::from_secs(1))
3693 .id();
3694 env.world.flush();
3695
3696 env.step_all(Duration::from_millis(1300));
3697
3698 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3699 assert_eq!(tr.rotation, Quat::from_rotation_x(TAU * 0.3));
3700 }
3701
3702 #[test]
3703 fn animated_entity_commands_rotate_y() {
3704 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3705 EaseFunction::QuadraticInOut,
3706 Duration::from_secs(1),
3707 DummyLens { start: 0., end: 1. },
3708 );
3709 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3710
3711 let entity = env
3712 .world
3713 .commands()
3714 .spawn(Transform::default())
3715 .rotate_y(Duration::from_secs(1))
3716 .id();
3717 env.world.flush();
3718
3719 env.step_all(Duration::from_millis(1300));
3720
3721 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3722 assert_eq!(tr.rotation, Quat::from_rotation_y(TAU * 0.3));
3723 }
3724
3725 #[test]
3726 fn animated_entity_commands_rotate_z() {
3727 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3728 EaseFunction::QuadraticInOut,
3729 Duration::from_secs(1),
3730 DummyLens { start: 0., end: 1. },
3731 );
3732 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3733
3734 let entity = env
3735 .world
3736 .commands()
3737 .spawn(Transform::default())
3738 .rotate_z(Duration::from_secs(1))
3739 .id();
3740 env.world.flush();
3741
3742 env.step_all(Duration::from_millis(1300));
3743
3744 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3745 assert_eq!(tr.rotation, Quat::from_rotation_z(TAU * 0.3));
3746 }
3747
3748 #[test]
3749 fn animated_entity_commands_rotate_x_by() {
3750 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3751 EaseFunction::QuadraticInOut,
3752 Duration::from_secs(1),
3753 DummyLens { start: 0., end: 1. },
3754 );
3755 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3756
3757 let entity = env
3758 .world
3759 .commands()
3760 .spawn(Transform::default())
3761 .rotate_x_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3762 .id();
3763 env.world.flush();
3764
3765 env.step_all(Duration::from_millis(1300)); // 130%
3766
3767 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3768 assert_eq!(tr.rotation, Quat::from_rotation_x(FRAC_PI_2)); // 100%
3769 }
3770
3771 #[test]
3772 fn animated_entity_commands_rotate_y_by() {
3773 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3774 EaseFunction::QuadraticInOut,
3775 Duration::from_secs(1),
3776 DummyLens { start: 0., end: 1. },
3777 );
3778 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3779
3780 let entity = env
3781 .world
3782 .commands()
3783 .spawn(Transform::default())
3784 .rotate_y_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3785 .id();
3786 env.world.flush();
3787
3788 env.step_all(Duration::from_millis(1300)); // 130%
3789
3790 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3791 assert_eq!(tr.rotation, Quat::from_rotation_y(FRAC_PI_2)); // 100%
3792 }
3793
3794 #[test]
3795 fn animated_entity_commands_rotate_z_by() {
3796 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3797 EaseFunction::QuadraticInOut,
3798 Duration::from_secs(1),
3799 DummyLens { start: 0., end: 1. },
3800 );
3801 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3802
3803 let entity = env
3804 .world
3805 .commands()
3806 .spawn(Transform::default())
3807 .rotate_z_by(FRAC_PI_2, Duration::from_secs(1), EaseFunction::Linear)
3808 .id();
3809 env.world.flush();
3810
3811 env.step_all(Duration::from_millis(1300)); // 130%
3812
3813 let tr = env.world.entity(entity).get::<Transform>().unwrap();
3814 assert_eq!(tr.rotation, Quat::from_rotation_z(FRAC_PI_2)); // 100%
3815 }
3816
3817 #[test]
3818 fn resolver_resource() {
3819 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3820 EaseFunction::QuadraticInOut,
3821 Duration::from_secs(1),
3822 DummyLens { start: 0., end: 1. },
3823 );
3824 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3825
3826 // Register the resource and create a TweenAnim for it
3827 env.world.init_resource::<DummyResource>();
3828 let tween = Tween::new::<DummyResource, DummyLens>(
3829 EaseFunction::QuadraticInOut,
3830 Duration::from_secs(1),
3831 DummyLens { start: 0., end: 1. },
3832 );
3833 let entity = env.world.commands().spawn(TweenAnim::new(tween)).id();
3834
3835 // Ensure all commands are applied before starting the test
3836 env.world.flush();
3837
3838 let delta_time = Duration::from_millis(200);
3839 let resource_id = env.world.component_id::<DummyResource>().unwrap();
3840
3841 // Resource resolver not registered; fails
3842 env.world
3843 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3844 world.resource_scope(
3845 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3846 world.resource_scope(
3847 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3848 assert!(resolver
3849 .resolve_resource(
3850 world,
3851 &TypeId::of::<DummyResource>(),
3852 resource_id,
3853 entity,
3854 delta_time,
3855 cycle_events.reborrow(),
3856 anim_events.reborrow(),
3857 )
3858 .is_err());
3859 },
3860 );
3861 },
3862 );
3863 });
3864
3865 // Register the resource resolver
3866 env.world
3867 .resource_scope(|world, mut resolver: Mut<TweenResolver>| {
3868 resolver.register_resource_resolver_for::<DummyResource>(world.components());
3869 });
3870
3871 // Resource resolver registered; succeeds
3872 env.world
3873 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3874 world.resource_scope(
3875 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3876 world.resource_scope(
3877 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3878 assert!(resolver
3879 .resolve_resource(
3880 world,
3881 &TypeId::of::<DummyResource>(),
3882 resource_id,
3883 entity,
3884 delta_time,
3885 cycle_events.reborrow(),
3886 anim_events.reborrow(),
3887 )
3888 .unwrap());
3889 },
3890 );
3891 },
3892 );
3893 });
3894 }
3895
3896 #[test]
3897 fn resolver_asset() {
3898 let dummy_tween = Tween::new::<DummyComponent, DummyLens>(
3899 EaseFunction::QuadraticInOut,
3900 Duration::from_secs(1),
3901 DummyLens { start: 0., end: 1. },
3902 );
3903 let mut env = TestEnv::<DummyComponent>::new(dummy_tween);
3904
3905 // Register the asset and create a TweenAnim for it
3906 let mut assets = Assets::<DummyAsset>::default();
3907 let handle = assets.add(DummyAsset::default());
3908 let untyped_asset_id = handle.id().untyped();
3909 env.world.insert_resource(assets);
3910 let tween = Tween::new::<DummyAsset, DummyLens>(
3911 EaseFunction::QuadraticInOut,
3912 Duration::from_secs(1),
3913 DummyLens { start: 0., end: 1. },
3914 );
3915 let entity = env.world.commands().spawn(TweenAnim::new(tween)).id();
3916
3917 // Ensure all commands are applied before starting the test
3918 env.world.flush();
3919
3920 let delta_time = Duration::from_millis(200);
3921 let resource_id = env.world.component_id::<Assets<DummyAsset>>().unwrap();
3922
3923 // Asset resolver not registered; fails
3924 env.world
3925 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3926 world.resource_scope(
3927 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3928 world.resource_scope(
3929 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3930 assert!(resolver
3931 .resolve_asset(
3932 world,
3933 &TypeId::of::<DummyAsset>(),
3934 resource_id,
3935 untyped_asset_id,
3936 entity,
3937 delta_time,
3938 cycle_events.reborrow(),
3939 anim_events.reborrow(),
3940 )
3941 .is_err());
3942 },
3943 );
3944 },
3945 );
3946 });
3947
3948 // Register the asset resolver
3949 env.world
3950 .resource_scope(|world, mut resolver: Mut<TweenResolver>| {
3951 resolver.register_asset_resolver_for::<DummyAsset>(world.components());
3952 });
3953
3954 // Asset resolver registered; succeeds
3955 env.world
3956 .resource_scope(|world, resolver: Mut<TweenResolver>| {
3957 world.resource_scope(
3958 |world, mut cycle_events: Mut<Messages<CycleCompletedEvent>>| {
3959 world.resource_scope(
3960 |world, mut anim_events: Mut<Messages<AnimCompletedEvent>>| {
3961 assert!(resolver
3962 .resolve_asset(
3963 world,
3964 &TypeId::of::<DummyAsset>(),
3965 resource_id,
3966 untyped_asset_id,
3967 entity,
3968 delta_time,
3969 cycle_events.reborrow(),
3970 anim_events.reborrow(),
3971 )
3972 .unwrap());
3973 },
3974 );
3975 },
3976 );
3977 });
3978 }
3979}