#[cfg(feature = "bevy_asset")]
use bevy::asset::Asset;
use bevy::{ecs::component::Component, prelude::*};
#[cfg(feature = "bevy_asset")]
use crate::{tweenable::AssetTarget, AssetAnimator};
use crate::{tweenable::ComponentTarget, Animator, AnimatorState, TweenCompleted};
#[derive(Debug, Clone, Copy)]
pub struct TweeningPlugin;
impl Plugin for TweeningPlugin {
fn build(&self, app: &mut App) {
app.add_event::<TweenCompleted>().add_systems(
Update,
component_animator_system::<Transform>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(feature = "bevy_ui")]
app.add_systems(
Update,
component_animator_system::<Style>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(feature = "bevy_ui")]
app.add_systems(
Update,
component_animator_system::<BackgroundColor>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(feature = "bevy_sprite")]
app.add_systems(
Update,
component_animator_system::<Sprite>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(all(feature = "bevy_sprite", feature = "bevy_asset"))]
app.add_systems(
Update,
asset_animator_system::<ColorMaterial>.in_set(AnimationSystem::AnimationUpdate),
);
#[cfg(feature = "bevy_text")]
app.add_systems(
Update,
component_animator_system::<Text>.in_set(AnimationSystem::AnimationUpdate),
);
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, SystemSet)]
pub enum AnimationSystem {
AnimationUpdate,
}
pub fn component_animator_system<T: Component>(
time: Res<Time>,
mut query: Query<(Entity, &mut T, &mut Animator<T>)>,
events: ResMut<Events<TweenCompleted>>,
) {
let mut events: Mut<Events<TweenCompleted>> = events.into();
for (entity, target, mut animator) in query.iter_mut() {
if animator.state != AnimatorState::Paused {
let speed = animator.speed();
let mut target = ComponentTarget::new(target);
animator.tweenable_mut().tick(
time.delta().mul_f32(speed),
&mut target,
entity,
&mut events,
);
}
}
}
#[cfg(feature = "bevy_asset")]
pub fn asset_animator_system<T: Asset>(
time: Res<Time>,
assets: ResMut<Assets<T>>,
mut query: Query<(Entity, &Handle<T>, &mut AssetAnimator<T>)>,
events: ResMut<Events<TweenCompleted>>,
) {
let mut events: Mut<Events<TweenCompleted>> = events.into();
let mut target = AssetTarget::new(assets);
for (entity, handle, mut animator) in query.iter_mut() {
if animator.state != AnimatorState::Paused {
target.handle = handle.clone();
if !target.is_valid() {
continue;
}
let speed = animator.speed();
animator.tweenable_mut().tick(
time.delta().mul_f32(speed),
&mut target,
entity,
&mut events,
);
}
}
}
#[cfg(test)]
mod tests {
use bevy::prelude::{Events, IntoSystem, System, Transform, World};
use crate::{lens::TransformPositionLens, *};
struct TestEnv {
world: World,
entity: Entity,
}
impl TestEnv {
pub fn new<T: Component>(animator: T) -> Self {
let mut world = World::new();
world.init_resource::<Events<TweenCompleted>>();
world.init_resource::<Time>();
let entity = world.spawn((Transform::default(), animator)).id();
Self { world, entity }
}
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
pub fn tick(&mut self, duration: Duration, system: &mut dyn System<In = (), Out = ()>) {
{
let mut time = self.world.resource_mut::<Time>();
time.advance_by(duration);
}
self.world.clear_trackers();
assert!(!self.transform().is_changed());
system.run((), &mut self.world);
let mut events = self.world.resource_mut::<Events<TweenCompleted>>();
events.update();
}
pub fn animator(&self) -> &Animator<Transform> {
self.world
.entity(self.entity)
.get::<Animator<Transform>>()
.unwrap()
}
pub fn transform(&mut self) -> Mut<Transform> {
self.world.get_mut::<Transform>(self.entity).unwrap()
}
pub fn event_count(&self) -> usize {
let events = self.world.resource::<Events<TweenCompleted>>();
events.get_reader().len(events)
}
}
#[test]
fn change_detect_component() {
let tween = Tween::new(
EaseMethod::Linear,
Duration::from_secs(1),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
)
.with_completed_event(0);
let mut env = TestEnv::new(Animator::new(tween));
let transform = env.transform();
assert!(transform.is_changed());
let mut system = IntoSystem::into_system(component_animator_system::<Transform>);
system.initialize(env.world_mut());
env.tick(Duration::ZERO, &mut system);
let animator = env.animator();
assert_eq!(animator.state, AnimatorState::Playing);
assert_eq!(animator.tweenable().times_completed(), 0);
let transform = env.transform();
assert!(transform.is_changed());
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
env.tick(Duration::from_millis(500), &mut system);
assert_eq!(env.event_count(), 0);
let animator = env.animator();
assert_eq!(animator.state, AnimatorState::Playing);
assert_eq!(animator.tweenable().times_completed(), 0);
let transform = env.transform();
assert!(transform.is_changed());
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5));
env.tick(Duration::from_millis(500), &mut system);
assert_eq!(env.event_count(), 1);
let animator = env.animator();
assert_eq!(animator.state, AnimatorState::Playing);
assert_eq!(animator.tweenable().times_completed(), 1);
let transform = env.transform();
assert!(transform.is_changed());
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
env.tick(Duration::from_millis(100), &mut system);
assert_eq!(env.event_count(), 0);
let animator = env.animator();
assert_eq!(animator.state, AnimatorState::Playing);
assert_eq!(animator.tweenable().times_completed(), 1);
let transform = env.transform();
assert!(!transform.is_changed());
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
}
}