Tuesday, May 20, 2008

Manually controlling storyboard animation

For an animation I did for a customer I wanted it to start the storyboard when the mouse enters and to play backwards, until the state it started in, when the mouse leaves the object.

At this time, in Silverlight 2 beta 1, there is no method I found to play a storyboard backwards from a custom position in the timeline. There is an option to AutoReverse the storyboard but this only occurs when the Storyboard is finished.

So I needed to create a custom method to play a storyboard backwards. The only way we found to do this (thanks again Martijn ;) ) is to manually play the stoyboard. Forward as well as Reverse. By using this method you'll gain total control over the animation.
You can start wherever you want, reverse playback, play faster or slower, etc.

First of all I created a Gameloop (neverending loop) which comes down to a Storyboard which, in the Completed event, restarts the Storyboard animation so you get a neverending loop. There are a lot of posts from fellow Silverlight developers who describe this very well so I won't go any deeper into this. In the sourcecode download is the gameloop.cs file which I always use.

Setting variables and eventhandlers

Second, we need a couple of variables for remembering the state of the animation (is it in Reverse mode or in Forward mode, what's the progress, etc):

 private bool isMouseOver;
private enum PlayDirectionEnum { Forward, Reverse }
private PlayDirectionEnum playDirection = PlayDirectionEnum.Forward;
private TimeSpan progressTime = new TimeSpan(0);
private TimeSpan totalPlayTime;
private int frameRate = 60;

Next step is to create all of the eventhandlers in the constructor of your application for the mouseenter, mouseleave, page_loaded:


        public Page()
{
InitializeComponent();

this.MouseEnter += new MouseEventHandler(Page_MouseEnter);
this.MouseLeave += new MouseEventHandler(Page_MouseLeave);
this.Loaded += new RoutedEventHandler(Page_Loaded);

gameloop.GameloopDuration = new Duration(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
gameloop.Update += new Amercom.Silverlight.Game.GameLoop.UpdateDelegate(gameloop_Update);
gameloop.Attach(this);
}

MouseEnter and MouseLeave


Now the real coding begins ;)

In the MouseEnter event we need to do a couple of things to start the animation:


            
if (gameloop.IsAttached && progressTime == new TimeSpan(0))
{
gameloop.Start();
MoveBall.Begin();
MoveBall.Pause();
}
playDirection = PlayDirectionEnum.Forward;
isMouseOver = true;

Because we chose to use a gameloop and maintain the state of the animation using variables, the actions in the MouseLeave are minimal:


            isMouseOver = false;
playDirection = PlayDirectionEnum.Reverse;

Animating


Finally, we need to create the gameloop_Update method to play the animation:


        
void gameloop_Update(TimeSpan ElapsedTime)
{
if (isMouseOver || progressTime > new TimeSpan(0))
{
switch (playDirection)
{
case PlayDirectionEnum.Forward:
progressTime = progressTime.Add(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
break;
case PlayDirectionEnum.Reverse:
progressTime = progressTime.Subtract(new TimeSpan(0, 0, 0, 0, 1000 / frameRate));
if (progressTime == new TimeSpan(0))
resetAnimation();
break;
}
}

if (progressTime >= totalPlayTime)
{
progressTime = totalPlayTime;
}
MoveBall.Seek(progressTime);
}


As you can see is that the main 2 things that are done in this method are:


  1. 1. Set the progressTime (in the switch)

  2. 2. Tell the storyboard to jump to the position (progressTime) using the Seek method


Some of the other things that happen in this method is that we need to reset the animation back to default state (back to the beginning, Forward play instead of Reverse, etc.) when it's reached the beginning of the Storyboard (when playing backwards).

Furthermore I "pause" the animation when it reaches the end. You could also change this behaviour so it autoreverses when it reaches the end of the Storyboard.


Hope this is usefull!


Click here to view the demo
Click here to download the source

2 comments:

  1. Hi Thanks for the Tutorial!
    I tried to compile your source code but get errors.

    This seems to be the source:

    targetElement.Resources.Add(storyboard);

    Any ideas?

    ReplyDelete
  2. Changing the syntax at several lines fixed the problem, e.g.:

    targetElement.Resources.Add("storyboard", 1);

    ReplyDelete