Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Animator to target other entities #136

Merged
merged 5 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,12 +504,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 @@ -529,10 +532,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 @@ -86,14 +86,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 @@ -157,7 +162,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 @@ -173,7 +179,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 @@ -208,12 +232,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 @@ -223,6 +252,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
Loading