Skip to main content

bevy_tweening/
plugin.rs

1use bevy::prelude::*;
2
3use crate::{AnimCompletedEvent, CycleCompletedEvent, TweenAnim, TweenResolver};
4
5/// Plugin to register the 🍃 Bevy Tweening animation framework.
6///
7/// This plugin registers the common resources and events used by 🍃 Bevy
8/// Tweening as well as the core animation system which steps all pending
9/// tweenable animations. That system runs in the
10/// [`AnimationSystem::AnimationUpdate`] system set, during the [`Update`]
11/// schedule.
12///
13/// ```no_run
14/// use bevy::prelude::*;
15/// use bevy_tweening::*;
16///
17/// App::default()
18///     .add_plugins(DefaultPlugins)
19///     .add_plugins(TweeningPlugin)
20///     .run();
21/// ```
22#[derive(Debug, Clone, Copy)]
23pub struct TweeningPlugin;
24
25impl Plugin for TweeningPlugin {
26    fn build(&self, app: &mut App) {
27        app.init_resource::<TweenResolver>()
28            .add_message::<CycleCompletedEvent>()
29            .add_message::<AnimCompletedEvent>()
30            .add_systems(
31                Update,
32                animator_system.in_set(AnimationSystem::AnimationUpdate),
33            );
34    }
35}
36
37/// Label enum for the systems relating to animations
38#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, SystemSet)]
39#[non_exhaustive]
40pub enum AnimationSystem {
41    /// Steps all animations. This executes during the [`Update`] schedule.
42    AnimationUpdate,
43}
44
45/// Core animation system ticking all queued animations.
46///
47/// This calls [`TweenAnim::step_all()`] using a value of the animation timestep
48/// `delta_time` equal to [`Time::delta()`].
49pub(crate) fn animator_system(world: &mut World) {
50    let delta_time = world.resource::<Time>().delta();
51    TweenAnim::step_all(world, delta_time);
52}
53
54#[cfg(test)]
55mod tests {
56    use std::{
57        ops::DerefMut,
58        sync::{
59            atomic::{AtomicBool, Ordering},
60            Arc,
61        },
62    };
63
64    use bevy::time::TimePlugin;
65
66    use crate::{lens::TransformPositionLens, test_utils::TestEnv, *};
67
68    #[test]
69    fn app() {
70        let mut app = App::default();
71        app.add_plugins((TimePlugin, TweeningPlugin));
72        app.finish();
73        app.update();
74    }
75
76    #[test]
77    fn custom_target_entity() {
78        let tween = Tween::new(
79            EaseMethod::EaseFunction(EaseFunction::Linear),
80            Duration::from_secs(1),
81            TransformPositionLens {
82                start: Vec3::ZERO,
83                end: Vec3::ONE,
84            },
85        )
86        .with_cycle_completed_event(true);
87        let mut env = TestEnv::<Transform>::new(tween);
88
89        env.step_all(Duration::ZERO);
90        let transform = env.component_mut();
91        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
92
93        env.step_all(Duration::from_millis(500));
94        let transform = env.component_mut();
95        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5));
96    }
97
98    #[test]
99    fn change_detect_component() {
100        let tween = Tween::new(
101            EaseMethod::default(),
102            Duration::from_secs(1),
103            TransformPositionLens {
104                start: Vec3::ZERO,
105                end: Vec3::ONE,
106            },
107        )
108        .with_cycle_completed_event(true);
109
110        let mut env = TestEnv::<Transform>::new(tween);
111
112        // After being inserted, components are always considered changed
113        let transform = env.component_mut();
114        assert!(transform.is_changed());
115
116        env.step_all(Duration::ZERO);
117
118        let anim = env.anim().unwrap();
119        assert_eq!(anim.playback_state, PlaybackState::Playing);
120        assert_eq!(anim.tweenable.cycles_completed(), 0);
121        let transform = env.component_mut();
122        assert!(transform.is_changed());
123        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
124
125        env.step_all(Duration::from_millis(500));
126
127        assert_eq!(env.event_count::<CycleCompletedEvent>(), 0);
128        let anim = env.anim().unwrap();
129        assert_eq!(anim.playback_state, PlaybackState::Playing);
130        assert_eq!(anim.tweenable.cycles_completed(), 0);
131        let transform = env.component_mut();
132        assert!(transform.is_changed());
133        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5));
134
135        env.step_all(Duration::from_millis(500));
136
137        // The animation is done now, and was deleted from the animator queue.
138        // The final state was still applied before deleting the animation,
139        // so the component is changed.
140
141        assert_eq!(env.event_count::<CycleCompletedEvent>(), 1);
142        let anim = env.anim();
143        assert!(anim.is_none()); // done and deleted
144        let transform = env.component_mut();
145        assert!(transform.is_changed());
146        assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
147
148        // We can continue to tick as much as we want, this doesn't change anything
149        env.step_all(Duration::from_millis(100));
150
151        assert_eq!(env.event_count::<CycleCompletedEvent>(), 0);
152        let anim = env.anim();
153        assert!(anim.is_none()); // done and deleted
154        let transform = env.component_mut();
155        assert!(!transform.is_changed());
156        assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
157    }
158
159    #[derive(Debug, Default, Clone, Copy, Component)]
160    struct DummyComponent {
161        value: f32,
162    }
163
164    /// Test [`Lens`] which only access mutably the target component if `defer`
165    /// is `true`.
166    struct ConditionalDeferLens {
167        pub defer: Arc<AtomicBool>,
168    }
169
170    impl Lens<DummyComponent> for ConditionalDeferLens {
171        fn lerp(&mut self, mut target: Mut<DummyComponent>, ratio: f32) {
172            if self.defer.load(Ordering::SeqCst) {
173                target.deref_mut().value += ratio;
174            }
175        }
176    }
177
178    #[test]
179    fn change_detect_component_conditional() {
180        let defer = Arc::new(AtomicBool::new(false));
181        let tween = Tween::new(
182            EaseMethod::default(),
183            Duration::from_secs(1),
184            ConditionalDeferLens {
185                defer: Arc::clone(&defer),
186            },
187        )
188        .with_cycle_completed_event(true);
189
190        let mut env = TestEnv::<DummyComponent>::new(tween);
191
192        // After being inserted, components are always considered changed
193        let component = env.component_mut();
194        assert!(component.is_changed());
195
196        assert!(!defer.load(Ordering::SeqCst));
197
198        // Mutation disabled
199        env.step_all(Duration::ZERO);
200
201        let anim = env.anim().unwrap();
202        assert_eq!(anim.playback_state, PlaybackState::Playing);
203        assert_eq!(anim.tweenable.cycles_completed(), 0);
204        let component = env.component_mut();
205        assert!(!component.is_changed());
206        assert!(((*component).value - 0.).abs() <= 1e-5);
207
208        // Zero-length tick should not change the component
209        env.step_all(Duration::ZERO);
210
211        let anim = env.anim().unwrap();
212        assert_eq!(anim.playback_state, PlaybackState::Playing);
213        assert_eq!(anim.tweenable.cycles_completed(), 0);
214        let component = env.component_mut();
215        assert!(!component.is_changed());
216        assert!(((*component).value - 0.).abs() <= 1e-5);
217
218        // New tick, but lens mutation still disabled
219        env.step_all(Duration::from_millis(200));
220
221        let anim = env.anim().unwrap();
222        assert_eq!(anim.playback_state, PlaybackState::Playing);
223        assert_eq!(anim.tweenable.cycles_completed(), 0);
224        let component = env.component_mut();
225        assert!(!component.is_changed());
226        assert!(((*component).value - 0.).abs() <= 1e-5);
227
228        // Enable lens mutation
229        defer.store(true, Ordering::SeqCst);
230
231        // The current time is already at t=0.2s, so even if we don't increment it, for
232        // a tween duration of 1s the ratio is t=0.2, so the lens will actually
233        // increment the component's value.
234        env.step_all(Duration::ZERO);
235
236        let anim = env.anim().unwrap();
237        assert_eq!(anim.playback_state, PlaybackState::Playing);
238        assert_eq!(anim.tweenable.cycles_completed(), 0);
239        let component = env.component_mut();
240        assert!(component.is_changed());
241        assert!(((*component).value - 0.2).abs() <= 1e-5);
242
243        // 0.2s + 0.3s = 0.5s
244        // t = 0.5s / 1s = 0.5
245        // value += 0.5
246        // value == 0.7
247        env.step_all(Duration::from_millis(300));
248
249        let anim = env.anim().unwrap();
250        assert_eq!(anim.playback_state, PlaybackState::Playing);
251        assert_eq!(anim.tweenable.cycles_completed(), 0);
252        let component = env.component_mut();
253        assert!(component.is_changed());
254        assert!(((*component).value - 0.7).abs() <= 1e-5);
255    }
256}