Saturday, August 22, 2009

[2009.08.22] Basic Per Frame Animation in WPF

As I was going through the WPF Animation Documentation, I noticed something about Per Frame animation but there is little written on it. From what I saw, I was reminded of Jeff Paries's Foundation Silverlight 2 Animation. The book did a great job of showing the power of animating in Silverlight and I wanted to port those concepts to WPF. However, it did not go as smoothly as I wanted. But after seeing how per frame animation works, I think I am on the right track.

While custom and key frame animations gives the user a lot of power to animate objects, for some scenarios, a closer grain control over the animation may be needed. Basically, you have a container (Canvas) of objects you wan to animate (Ellipse). What you do is call the static Rendering event of the CompositionTarget class on the container. This class represents the display surface that the application is being drawn on. The event is called once per frame. Each time that WPF marshals the persisted rendering data in the visual tree across to the composition tree, your event handler method is called (from documentation).

You can think of this as calling the Completed event of a repeating Storyboard. Inside of the Rendering method, you update the properties of the object you want animated and when the event is fired again, your changes will be reflected as such.

Currently, I don't know a whole lot about the CompositionTarget class. It is small but it uses another class that is not documented called MediaContext. Actually, there isn't a whole lot of documentation to go around anyway. This type of animation puts you more into visual layer animations.

I have added some basic code that bounces a ball on the screen using Per Frame animation. It is pretty straight forward and I will build on this in the future.

  1: <Window x:Class="Wpf_OnRenderAni03.Window1"
  2:     xmlns=""
  3:     xmlns:x=""
  4:     Title="Window1" Height="300" Width="300">
  5:     <Canvas Name="layoutRoot" >
  6:         <Ellipse Name="myBall" Width="30" Height="30"
  7:                  Canvas.Left="10" Canvas.Top="10">
  8:             <Ellipse.Fill>
  9:                 <RadialGradientBrush GradientOrigin="0.666,0.093" 
 10:                                      RadiusX="0.547" RadiusY="0.547" 
 11:                                      Center="0.5,0.47">
 12:                     <GradientStop Color="#DBFCF8F8"/>
 13:                     <GradientStop Color="#E40C0C0C" Offset="0.87"/>
 14:                 </RadialGradientBrush>
 15:             </Ellipse.Fill>
 16:         </Ellipse>
 17:     </Canvas>
 18: </Window>

  1: using System;
  2: using System.Windows;
  3: using System.Windows.Controls;
  4: using System.Windows.Media;
  6: namespace Wpf_OnRenderAni03
  7: {
  8:     public partial class Window1 : Window
  9:     {
 10:         // vector to add 'speed' to the X,Y 
 11:         //  component of the position of the ball
 12:         private Vector speed = new Vector(2, 3);
 13:         private Vector ballPosition = new Vector();
 15:         public Window1()
 16:         {
 17:             InitializeComponent();
 19:             // handle the motion and update the
 20:             //  position of the ball here
 21:             CompositionTarget.Rendering += MoveBall;
 22:         }
 24:         void MoveBall(object sender, EventArgs e)
 25:         {
 27:             // get the position of the ball
 28:             this.ballPosition.X = (Double)(this.myBall.GetValue(Canvas.LeftProperty));
 29:             this.ballPosition.Y = (Double)(this.myBall.GetValue(Canvas.TopProperty));
 31:             // test to see if the ball has gone beyond the
 32:             //  bounds of the application
 33:             // as soon as it hits one of the edges, reverse
 34:             //  the speed component to basically bounce the
 35:             //  ball off the edges
 36:             // NOTE: no test has been done if the ball hit
 37:             //  any of the 4 corners where the ball is touching
 38:             //  both the horizontal and vertical edges of the app
 39:             // test left and right
 40:             if (this.ballPosition.X <= 0 || this.ballPosition.X > 
 41:                 this.layoutRoot.ActualWidth - this.myBall.Width)
 42:                 this.speed.X *= -1;
 43:             // test top and bottom
 44:             if (this.ballPosition.Y <=0 || this.ballPosition.Y > 
 45:                 this.layoutRoot.ActualHeight - this.myBall.Height)
 46:             {
 47:                 this.speed.Y *= -1;
 48:             }
 50:             // update the ball position using the speed
 51:             Canvas.SetLeft(this.myBall, ballPosition.X + speed.X);
 52:             Canvas.SetTop(this.myBall, ballPosition.Y + speed.Y);
 53:         }
 54:     }
 55: }

Fig. 1 shows a gif animation of the bouncing ball. Pardon the quality of it all :=)


Fig. 1. Basic bouncing ball using per frame animation

No comments: