Skip to content

Commit

Permalink
Allow Animator to target other entities (#136)
Browse files Browse the repository at this point in the history
This adds an optional property to `Animator` that stores an entity. If
the property contains a value, it will be used as the target of the
animation. Otherwise, the `Animator`'s entity will be used (default
behavior).
  • Loading branch information
patrickariel authored Dec 10, 2024
1 parent b99332f commit b0bae2a
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 8 deletions.
14 changes: 12 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,15 @@ macro_rules! animator_impl {

/// Component to control the animation of another component.
///
/// The animated component is the component located on the same entity as the
/// [`Animator<T>`] itself.
/// By default, the animated component is the component located on the same
/// entity as the [`Animator<T>`] itself. But if [`Animator::target`] is set,
/// that entity will be used instead.
#[derive(Component)]
pub struct Animator<T: Component> {
/// Control if this animation is played or not.
pub state: AnimatorState,
/// When set, the animated component will be the one located on this entity.
pub target: Option<Entity>,
tweenable: BoxedTweenable<T>,
speed: f32,
}
Expand All @@ -524,10 +527,17 @@ impl<T: Component> Animator<T> {
Self {
state: default(),
tweenable: Box::new(tween),
target: None,
speed: 1.,
}
}

/// Create a new version of this animator with the `target` set to the given entity.
pub fn with_target(mut self, entity: Entity) -> Self {
self.target = Some(entity);
self
}

animator_impl!();
}

Expand Down
65 changes: 59 additions & 6 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,19 @@ pub enum AnimationSystem {
/// attached to the same entity, and tick the animator to animate the component.
pub fn component_animator_system<T: Component>(
time: Res<Time>,
mut query: Query<(Entity, &mut T, &mut Animator<T>)>,
mut animator_query: Query<(Entity, &mut Animator<T>)>,
mut target_query: Query<&mut T>,
events: ResMut<Events<TweenCompleted>>,
mut commands: Commands,
) {
let mut events: Mut<Events<TweenCompleted>> = events.into();
for (entity, target, mut animator) in query.iter_mut() {
for (animator_entity, mut animator) in animator_query.iter_mut() {
if animator.state != AnimatorState::Paused {
let speed = animator.speed();
let entity = animator.target.unwrap_or(animator_entity);
let Ok(target) = target_query.get_mut(entity) else {
continue;
};
let mut target = ComponentTarget::new(target);
animator.tweenable_mut().tick(
time.delta().mul_f32(speed),
Expand Down Expand Up @@ -164,7 +169,8 @@ mod tests {
/// [`Entity`] in it.
struct TestEnv<T: Component> {
world: World,
entity: Entity,
animator_entity: Entity,
target_entity: Option<Entity>,
_phantom: PhantomData<T>,
}

Expand All @@ -180,7 +186,25 @@ mod tests {

Self {
world,
entity,
animator_entity: entity,
target_entity: None,
_phantom: PhantomData,
}
}

/// Like [`TestEnv::new`], but the component is placed on a separate entity.
pub fn new_separated(animator: Animator<T>) -> Self {
let mut world = World::new();
world.init_resource::<Events<TweenCompleted>>();
world.init_resource::<Time>();

let target = world.spawn(T::default()).id();
let entity = world.spawn(animator.with_target(target)).id();

Self {
world,
animator_entity: entity,
target_entity: Some(target),
_phantom: PhantomData,
}
}
Expand Down Expand Up @@ -215,12 +239,17 @@ mod tests {

/// Get the animator for the component.
pub fn animator(&self) -> &Animator<T> {
self.world.entity(self.entity).get::<Animator<T>>().unwrap()
self.world
.entity(self.animator_entity)
.get::<Animator<T>>()
.unwrap()
}

/// Get the component.
pub fn component_mut(&mut self) -> Mut<T> {
self.world.get_mut::<T>(self.entity).unwrap()
self.world
.get_mut::<T>(self.target_entity.unwrap_or(self.animator_entity))
.unwrap()
}

/// Get the emitted event count since last tick.
Expand All @@ -230,6 +259,30 @@ mod tests {
}
}

#[test]
fn custom_target_entity() {
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_separated(Animator::new(tween));
let mut system = IntoSystem::into_system(component_animator_system::<Transform>);
system.initialize(env.world_mut());

env.tick(Duration::ZERO, &mut system);
let transform = env.component_mut();
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));

env.tick(Duration::from_millis(500), &mut system);
let transform = env.component_mut();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5));
}

#[test]
fn change_detect_component() {
let tween = Tween::new(
Expand Down

0 comments on commit b0bae2a

Please sign in to comment.