duck-tween
duck-tween copied to clipboard
FastForward ignores IsReversed, & The api's usage and use cases are not clear
Functional Problem 1 (IsReverse is not respected)
The code
public override void FastForward()
{
// Set the animation state to its final form
CurrentTime = Duration;
Refresh(Progress);
if (IsPlaying)
{
AnimationDriver.Remove(Update);
}
base.FastForward();
}
In a TimedAnimation
:
FastForward is achieved by setting CurrentTime
to Duration
of course if you have reversed it the theoretical "End" is actually Progress == 0
and CurrentTime == 0
.
You can also see that Refresh()
is called regardless of it being played or not.
Use cases
It can occur in multiple use cases but a common one might be reversing, then fastforwarding, in order to put an animation to it's intial state.
Solutions
- It's an easy fix to make it respect the
IsReversed
Property - Perhaps a new API for
Rewind
to hit the first frame? - Expose
Refresh(float progress)
so we can put our own time in, then you can hit any frame of the animation 50% 72% or whatever you want
Functional Problem 2 (FastForward does not hit the final frame in some situations)
In a TimedAnimation
it always calls Refresh(1)
no matter what state it's in (Playing, Completed, Not-started).
In an AnimationCollection
however it's behaviour is actually It fast-forwards any uncompleted child animation
.
Theres 3 cases:
- If the animation has never been started: it will fast-forward each child.
- If the animation is playing: it will fast-forward all uncompleted child (meaning each one will be on it's last frame)
- If the animation was completed: it will just complete instantly because all children as "completed".
This is due to an implementation detail.
Solutions
- it's quite an easy fix to force it to "reset" when
FastForward
is called - An in project work around is to call
Play
beforeFastForward
. It brings up bigger API design questions (see below)
API Design & Philosophy
Right now it's not quite clear if we need to call .Play()
before .FastForward()
. Before reading the implementations in depth whenever I used it I was never sure if I needed to set it playing before FastForwarding. It feels like it should be needed.
In both problems 1 and 2 there is no clarity about whether or not the FastForward
will hit the final frame.
I believe originally it was designed as a play-time fast forward, but due to implementation it works (in most cases) without starting, and that's turned out to be very useful. We can create an animation which is a virtual timeline, and then "hit" the final frame, and now we are seeing it would be useful to hit the first or indeed any frame.
Our Options
Theres a few options for the future of this:
- Make
FastForward
a no-op for animations that are not playing, with a throw. This would be very explicit and it would become obvious that the API is designed that way. That would mean a lot of
// Get it to the end...
tween.Play();
tween.FastForward();
- Fix it up where needed and allow it to FastForward any time. This would still be ambigious in my opinion.
Other enhancements
Regardless of our choice above there are some new APIs that would benefit us:
-
Add public
.Refresh(float progress)
to force a refresh. We could allow this without needing to play. - I thought about calling itGoto(0.25)
- so you could get an animation to start part way through, but this would not work at all for collections... so let's accept that it won't change the progress or time elapsed. If we wanted that we could implement it in a different way. Update them with large delta times, or iteratively do it. -
Add
.Rewind
to do the opposite of.FastForward
- This would need to have the same rules as FastForward regarding whether or not we can call it when not playing and also respect forIsReversed