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