Animations are great...almost

Oct 24, 2011 at 2:25 AM

TweenAnimation looks perfectly suited to what I need to do: moving  objects around from one place to another smoothly, adding visual effects, and so on. Having spent some time playing with the animation system I feel I know it quite well, and I'm having problems.

  1. For my purposes, I would like to create an animation in stopped state at the midpoint of its range, and the code is a bit awkward.
    var anim = new TweenAnimation<float>() {
      Target = Camera,
      TargetProperty = "X",
      Duration = TimeSpan.FromSeconds(RangeTime),
      From = Camera.ScaleRange.X,
      To = Camera.ScaleRange.Y,
      Curve = Curves.Linear,
      Repeat = 1,
      AutoReverse = false,
    anim.Seek(TimeSpan.FromSeconds(anim.Duration.TotalSeconds / 2));
  2. I would like to be able to reverse an animation, but to do that is awkward too. I would like a settable property for Direction.
    var chan = Camera.Animations[name];
    var anim = chan.Current as TweenAnimation<float>;
    if (direction == anim.Direction) { chan.Resume(); } else { var position = anim.Position; chan.Stop(); anim.StartupDirection = (AnimationDirection)direction; chan.Play(); anim.Seek(position); }
  3. I found that calling Stop() on an animation leaves the channel in Play state, so that calling Play() on the animation plays it again. However if the animation runs to completion it sets the channel to Stop state, and callng Play() on the animation then does nothing. You have to call Play() on the channel instead. That's pretty confusing in practice. I think that if an animation can Stop its channel, than playing the animation should ensure the the channel is in Play state too.
  4. Finally, I have a problem I have not been able to solve. The following is the sequence of events.
  • Create a TweenAnimation in forward direction
  • Seek or play to somewhere in the middle of the range.
  • Stop, set direction to reverse, then Play.
  • => The animation plays in reverse. On completion, it resets to Max value (it should be at Min value) and stops.
  • Stop it, set direction to forward, then Play.
  • => Nothing happens. The animation does not play.
  • Stop it, set direction to reverse, then Play.
  • => The animation plays in reverse. On completion, it stops at Min value (which is correct).

In other words, an animation played in reverse starting other than at the extreme high point does not complete correctly, but enters an invalid state.

I'm currently trying to decide whether I can find and fix the problem or whether I should look for a different solution. Any thoughts?

Oct 26, 2011 at 10:27 AM

[Things seem to have gone a bit quiet...]

I've more or less rewritten TimelineAutomation and made minor changes to 7 related files, which has solved most of the problems. The samples still work unchanged, but I don't know if there are any unit tests to try. There are still anomalies in the relationship between animations and channels, but enough is enough.

I also made PropertyExpression public. That's way too useful to keep to yourself!

I can upload a patch if you like.

Oct 26, 2011 at 12:16 PM
Hi, I just came back from vacation so pretty quiet recently.

Let me try to figure out what you're trying to solve by reversing an existing animation, are you playing a tween animation in the middle way and an event occurs and you want the animation to be reversed? What I was doing is to create a new TweenAnimation and set From to null and To to the original position.

I choose not the allow changing TimelineAnimation.Direction when the animation is playing, because it would be complicated if you want to make sure Repeat/AutoReverse/StartupDirection/Completed/Repeated/ work correctly when you can reset the playing direction at anytime. But if you can manage to handle that, I would be more than happy to adopt your changes.

There are some unit tests under Nine.Test\Animations\
Oct 26, 2011 at 12:29 PM
The animation itself is not aware of the parent animation player, so if you choose to use animation player to control the playing of an animation, do not call the play/stop/pause/resume method of child
animations. The same is also true for LayeredAnimation/SequentialAnimation that allows you to merge multiple animations into a single take.

There's no seek method in AnimationPlayer/LayeredAnimation/SequentialAnimation because these classes works with IAnimation interface, and that interface does not enforce the animation to be either seekable or having a fixed duration. So IAnimation represents a broader ranges of animations, e.g., a camera traveling from its current position to a target entity along a path is an animation that would take variant amount of time to finish depending on the distance between them.

Oct 27, 2011 at 12:42 AM

[Hope you came back refreshed and reinvigorated. :-)]

I'm trying to set up an animation for a camera, so it can be rotated, tracked and zoomed in response to key presses. A new animation setting From to null will run at the wrong speed unless you also recalculate the duration, so I prefer to set it up once and then seek and reverse as needed. I've got it working quite nicely.

The problem is that when the channel runs to completion it stops the parent player, but playing or stopping the channel do not affect the parent. This is asymmetrical. IMHO it would probably be more logical that if you play a channel and the parent player is stopped, then it also sets the player to play state. Yes, I understand the other kinds of animation -- no problem.

I'll upload the changes I've made shortly, so you can see what you think. AFAIK all the functionality is in there as before, but without running a detailed set of tests it's hard to be sure.

Oct 27, 2011 at 6:01 AM
I've run the animation test cases, most of them failed for your patch. Can you take a look at the cases under Framework\Nine.Test\Animations ? Also make sure to register your own assemblies before running these tests.
Oct 27, 2011 at 2:17 PM

Oops! Yes, that wasn't according to plan. Thanks for pointing out the tests -- exactly what I needed.

Question: If an Update() carries an animation exactly to the end point, should it stop? If Repeat>1, should it set Position to zero? If AutoReverse, should it reverse direction?

This seems to be a piece of undefined behaviour: I chose one way, the tests assume the other. IMHO the cleanest behaviour is if the answer to all of these is no: the animation does not stop or repeat or reverse until the first tick past the end point. This is what I implemented. It is not what you tested.

If it is important to stop right on the final tick, then that can be added as a special case. I'll try the tests both ways and see what they say.

Oct 28, 2011 at 2:49 AM
This is what I'm doing for some corner cases.
- If the animation is updated to the extact end point, and Repeat is exactly 1, it will stop at the end point and trigger the completed event. Position will be equal to Duration at this moment. The animation won't change it's direction even if auto reverse is turned on, and the repeat event is not raised.

- If startup direction is backward, the position will be exactly 0 when the animation stops.

- If repeat is slightly above 1, repeat event is raised when the animation reaches the end, the animation will play a bit longer from either the end or the start depending on whether auto reverse is turned on or off. When it reaches the end specified by repeat, complete will be triggered.

- Repeat is a floating point value, so if repeat is 1.1, duration is 10 and auto reverse is off, the position of the animation will be exactly 1 when the animation stops. When auto reverse is on, it will stop at 9. The stop position has to be precise because anything affected by TweenAnimation has to be placed at the extact To position when the animation stops.
Oct 28, 2011 at 6:01 AM

Well, I've got all the tests to pass now and it was a bit harder than I expected. My basic strategy was OK, but there were a few cases I overlooked. Kind of.

My compliments on the unit tests: I'm a big fan and they certainly flushed out some wooly thinking on my part.

I agree with your corner cases. Please note that if Repeat > 1 and an Update finishes exactly at the end point, then it will wait until the next Update before reversing or repeating. Therefore the animation will stop (and seek) to Position==Duration, and on next Update seek to Position==0 and seek again to Position>0. I'm not sure whether 2 seeks for one update is expected or not. Seems OK to me, but is not tested.

One more special case. It seems pretty obvious that if you seek on an animation you would still like it to complete on time (and you have a test case for that). What is not so obvious is what to do if the Repeat count is greater than 1. What I finished up doing is adjusting the ElapsedTime up or down in Seek, so the animation will always finish at precisely the same end point, but the elapsed time may under-estimate how long the animation was actually running. I think that's an OK solution, but tell me if I'm missing something.

I'll update the patch soon, after a little bit of housekeeping.

Nov 3, 2011 at 10:34 AM

Patch is updated. If you're happy with that I'll upload my animated camera and a sample.

Nov 3, 2011 at 10:44 AM
I'll review your patch soon. Thanks :D