Skip to content

Commit b0bae2a

Browse files
authored
Allow Animator to target other entities (#136)
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).
1 parent b99332f commit b0bae2a

File tree

2 files changed

+71
-8
lines changed

2 files changed

+71
-8
lines changed

src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,15 @@ macro_rules! animator_impl {
499499

500500
/// Component to control the animation of another component.
501501
///
502-
/// The animated component is the component located on the same entity as the
503-
/// [`Animator<T>`] itself.
502+
/// By default, the animated component is the component located on the same
503+
/// entity as the [`Animator<T>`] itself. But if [`Animator::target`] is set,
504+
/// that entity will be used instead.
504505
#[derive(Component)]
505506
pub struct Animator<T: Component> {
506507
/// Control if this animation is played or not.
507508
pub state: AnimatorState,
509+
/// When set, the animated component will be the one located on this entity.
510+
pub target: Option<Entity>,
508511
tweenable: BoxedTweenable<T>,
509512
speed: f32,
510513
}
@@ -524,10 +527,17 @@ impl<T: Component> Animator<T> {
524527
Self {
525528
state: default(),
526529
tweenable: Box::new(tween),
530+
target: None,
527531
speed: 1.,
528532
}
529533
}
530534

535+
/// Create a new version of this animator with the `target` set to the given entity.
536+
pub fn with_target(mut self, entity: Entity) -> Self {
537+
self.target = Some(entity);
538+
self
539+
}
540+
531541
animator_impl!();
532542
}
533543

src/plugin.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,19 @@ pub enum AnimationSystem {
8787
/// attached to the same entity, and tick the animator to animate the component.
8888
pub fn component_animator_system<T: Component>(
8989
time: Res<Time>,
90-
mut query: Query<(Entity, &mut T, &mut Animator<T>)>,
90+
mut animator_query: Query<(Entity, &mut Animator<T>)>,
91+
mut target_query: Query<&mut T>,
9192
events: ResMut<Events<TweenCompleted>>,
9293
mut commands: Commands,
9394
) {
9495
let mut events: Mut<Events<TweenCompleted>> = events.into();
95-
for (entity, target, mut animator) in query.iter_mut() {
96+
for (animator_entity, mut animator) in animator_query.iter_mut() {
9697
if animator.state != AnimatorState::Paused {
9798
let speed = animator.speed();
99+
let entity = animator.target.unwrap_or(animator_entity);
100+
let Ok(target) = target_query.get_mut(entity) else {
101+
continue;
102+
};
98103
let mut target = ComponentTarget::new(target);
99104
animator.tweenable_mut().tick(
100105
time.delta().mul_f32(speed),
@@ -164,7 +169,8 @@ mod tests {
164169
/// [`Entity`] in it.
165170
struct TestEnv<T: Component> {
166171
world: World,
167-
entity: Entity,
172+
animator_entity: Entity,
173+
target_entity: Option<Entity>,
168174
_phantom: PhantomData<T>,
169175
}
170176

@@ -180,7 +186,25 @@ mod tests {
180186

181187
Self {
182188
world,
183-
entity,
189+
animator_entity: entity,
190+
target_entity: None,
191+
_phantom: PhantomData,
192+
}
193+
}
194+
195+
/// Like [`TestEnv::new`], but the component is placed on a separate entity.
196+
pub fn new_separated(animator: Animator<T>) -> Self {
197+
let mut world = World::new();
198+
world.init_resource::<Events<TweenCompleted>>();
199+
world.init_resource::<Time>();
200+
201+
let target = world.spawn(T::default()).id();
202+
let entity = world.spawn(animator.with_target(target)).id();
203+
204+
Self {
205+
world,
206+
animator_entity: entity,
207+
target_entity: Some(target),
184208
_phantom: PhantomData,
185209
}
186210
}
@@ -215,12 +239,17 @@ mod tests {
215239

216240
/// Get the animator for the component.
217241
pub fn animator(&self) -> &Animator<T> {
218-
self.world.entity(self.entity).get::<Animator<T>>().unwrap()
242+
self.world
243+
.entity(self.animator_entity)
244+
.get::<Animator<T>>()
245+
.unwrap()
219246
}
220247

221248
/// Get the component.
222249
pub fn component_mut(&mut self) -> Mut<T> {
223-
self.world.get_mut::<T>(self.entity).unwrap()
250+
self.world
251+
.get_mut::<T>(self.target_entity.unwrap_or(self.animator_entity))
252+
.unwrap()
224253
}
225254

226255
/// Get the emitted event count since last tick.
@@ -230,6 +259,30 @@ mod tests {
230259
}
231260
}
232261

262+
#[test]
263+
fn custom_target_entity() {
264+
let tween = Tween::new(
265+
EaseMethod::Linear,
266+
Duration::from_secs(1),
267+
TransformPositionLens {
268+
start: Vec3::ZERO,
269+
end: Vec3::ONE,
270+
},
271+
)
272+
.with_completed_event(0);
273+
let mut env = TestEnv::new_separated(Animator::new(tween));
274+
let mut system = IntoSystem::into_system(component_animator_system::<Transform>);
275+
system.initialize(env.world_mut());
276+
277+
env.tick(Duration::ZERO, &mut system);
278+
let transform = env.component_mut();
279+
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
280+
281+
env.tick(Duration::from_millis(500), &mut system);
282+
let transform = env.component_mut();
283+
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5));
284+
}
285+
233286
#[test]
234287
fn change_detect_component() {
235288
let tween = Tween::new(

0 commit comments

Comments
 (0)