djeedai / bevy_tweening

Tweening animation plugin for the Bevy game engine.
Other
399 stars 64 forks source link

Extend Tracks and Sequence functionality #8

Open Shatur opened 2 years ago

Shatur commented 2 years ago

To combine multiply tweens and loop them I have to create custom lens which is not very ergonomic. We currently have Tracks and Sequence, but they aren't loopable. I would like to request to extend their functionality. It would be great if we could combine multiply tweens with different tweening type. For example, to animate healing pickup like in Owerwatch I need rotation tween with Loop and translation tween with PingPong. Video of how such pickups look.

djeedai commented 2 years ago

Thanks for logging this and for the video. It's definitely something I want to add.

djeedai commented 2 years ago

Note: I've looked into looping Tracks and it's a minefield of corner cases that I'm not sure how to handle. For example with one tweenable of 0.8 second and another one of 1 second, what does looping look like in the [0.8:1.0] time frame for the first tweenable (since the Tracks will be 1 second long, the max of all its children)? What about ping-pong looping (reverse playback) in that time frame? etc.

Shatur commented 2 years ago

@djeedai Makes sense... Maybe we should keep only Tween objects loopable, but turn Tracks into a simple array to allow users to apply multiply Tweens to the same object?

djeedai commented 2 years ago

@Shatur I'm not sure I follow, that sounds like what Tracks is already doing. What is the difference between what you describe and what Tracks currently does, which is applying a collection of Tweenable in parallel to the same object. Granted, it doesn't have introspection so you can't index like an array to inspect its content, but internally it is an array.

Shatur commented 2 years ago

@djeedai sorry for confusion, I didn't mean indexing. If I understand correctly, I can't execute multiply loopable tweens in parallel with Tracks. Is it correct? I would expect Tracks to have infinity duration if at least one Tween is loopable.

djeedai commented 2 years ago

If I understand correctly, I can't execute multiply loopable tweens in parallel with Tracks. Is it correct?

Correct.

I would expect Tracks to have infinity duration if at least one Tween is loopable.

Yes. But that's the easy part of making Tracks loopable. Are you saying you want each single tweenable to loop completely independently of each other, and so Tracks wouldn't really have a defined duration anymore? So Tracks wouldn't implement Tweenable.

Shatur commented 2 years ago

So Tracks wouldn't implement Tweenable.

Oh, I see. Hm... Maybe instead of having tracks, we allow could to store multiply tweenable objects inside the Animator?

azarmadr commented 2 years ago

So far animator allows us to mutate various components in a seq or track. What if we wanted to add another seq/track to the same animator? I might be out of my depth, but can we achieve this by:

Shatur commented 2 years ago

@azarmadr Makes sense to me. So we will be able to store multiply Tweenable objects inside the Animator object?

djeedai commented 2 years ago

I don't really like the Animator having to manage multiple Tweenable, first because a lot of use cases only need a single one, and second because the entire purpose of abstracting things behind a Tweenable trait is for the Animator not to have to care about any extra complexity. Especially since code is mostly duplicated between the Animator and AssetAnimator, so the simpler they remain and the more code moved into a shared Tweenable the better.

I'm open to discuss modifying Tracks, or even adding a new Tweenable type, if there's a use case that's not currently covered. As stated above, making Sequence loopable seems doable (didn't try yet) but Tracks as it is can't really be made loopable unless we solve all the corner cases it has when doing so. If someone has an idea to solve those, or an idea of a different type of Tweenable that would allow looping parallel tweens, then that would be I think a better approach.

Shatur commented 2 years ago

I'm open to discuss modifying Tracks, or even adding a new Tweenable type, if there's a use case that's not currently covered

@djeedai I mentioned the use-case in the first message, but I don't know what to suggest for architecture to provide such functionality :(

djeedai commented 2 years ago

Opened #16 for the case of making Sequence loopable, which is well-defined and "just" need to be done. The case of Tracks is more complicated and I don't think there's a good proposal for it yet.

azarmadr commented 2 years ago

I have been playing around with lens and settled on this.

https://user-images.githubusercontent.com/48410397/166091492-36199651-04fb-46a9-b684-8e4cc3e7da7b.mp4

azarmadr commented 2 years ago

I observed that Animator is constrained to Component. Unlike AssetAnimator, where it needs to be constrained to Asset, the Animator type could be anything. We can just constrain the system using Animator to be Component, which is already happening.

Yesterday I was trying to make the animator with WorldQuery type, which might be impossible because of timing constraints. Then I finally settled on a Tuple of components. if Animator is without any constraints, I tried this

/// trying to create a bundled animator
pub fn component_animator2_system<T:Component+Clone,R:Component+Clone>(
    time: Res<Time>,
    mut query: Query<(Entity, (&mut T,&mut R), &mut Animator<(T,R)>)>,
    mut event_writer: EventWriter<TweenCompleted>,
)
{
    for (entity, ref mut target, ref mut animator) in query.iter_mut() {
        if animator.state != AnimatorState::Paused {
            if let Some(tweenable) = animator.tweenable_mut() {
                let nt = &mut(target.0.clone(),target.1.clone());
                tweenable.tick(time.delta(), nt, entity, &mut event_writer);
                let (ref a,ref b) = nt;
                *target.0 = a.clone();
                *target.1 = b.clone();
            }
        }
    }
}

You can find the above system in my fork

https://user-images.githubusercontent.com/48410397/166138729-5596bb3a-c085-4703-97ae-8bff775009c4.mp4

musjj commented 8 months ago

I'm also facing difficulties with the lack of flexibility in the current API. So I'm thinking of a different approach: in addition to entity-level animators, we can also provide system-level animators.

A sketch of what the API might look like:

let tween = SystemTween::new(EaseFunction::QuadraticInOut, Duration::from_secs(1))
    .with_repeat_count(RepeatCount::Finite(2))
    .with_repeat_strategy(RepeatStrategy::MirroredRepeat);

// a `SystemAnimator` associated with this system will be passed
let system = |In(mut animator): In<&mut SystemAnimator>,
              mut foo_query: Query<&mut Transform, With<Foo>>,
              mut bar_query: Query<&mut Transform, With<Bar>>| {
    let ratio = animator.get_ratio();
    let mut transform = foo_query.get_single().unwrap();
    transform.x = 50.0_f32.lerp(80.0, ratio);
    animator.set_speed(0.5);
    // do some other stuff
};

app.add_animation(SystemAnimator::new(tween), system);

This gives the user a lot of flexibility to basically do anything they want.

But after a quick look through the library, it looks like that Lens is coupled with many parts of the library, which might make it hard to implement. Also, I guess Track wouldn't make sense under this API.

What do you think?

PraxTube commented 1 month ago

I also expected to be able to have different Lens types in one Track but that doesn't work either. Not sure if there is a way and I was just too blind to see it though.

let seq = Tracks::new([
    Tween::new(
        EaseFunction::QuarticOut,
        Duration::from_secs_f32(0.2),
        TransformPositionLens {
            start: Vec3::ZERO,
            end: Vec3::ONE,
        },
    ),
    Tween::new(
        EaseFunction::QuarticOut,
        Duration::from_secs_f32(0.2),
        SpriteColorLens {
            start: Color::WHITE,
            end: Color::BLACK,
        },
    ),
]);