RIA Development Center
FeaturesTipsEventsVideosSilverlight GallerySilverlight Hosting Resources
Brad Abrams gives a brief overview of what Microsoft .NET RIA Services is and how it's going to make your life simpler. Read more
See more tips
Which platform do you use most often?
(Check one)
AIR
AJAX
Flash
JavaFX
Silverlight
Other

View Results
Get regular email alerts when we publish new features!
DevX RIA Development Update

More Newsletters
Leveraging Your Flash Development with Silverlight (cont'd)

Timer-less Animation
The first step is to replace the timer-based animation with a storyboard animation. The timer-based animation is a faithful frame-by-frame representation of the Flash clip, but we can come up with a very good alternative that will be easier to work with using storyboards.

We can throw out the timer and the geometry lists, keeping only the dynamic paths from the first frame. Then we'll animate both of the paths using a rotation transform. First, we define a storyboard for the tail rotor animation, which spins in place (Listing 2).

Listing 2. The storyboard animation, which starts automatically when the canvas is loaded.

<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
    <BeginStoryboard>
	<Storyboard x:Name="Motion">
	    <DoubleAnimation
		Storyboard.TargetName="tailRotorTransform"
		Storyboard.TargetProperty="(RotateTransform.Angle)"
		From="0.0" To="360" Duration="0:0:.05"
		RepeatBehavior="Forever"/>
	</Storyboard>
    </BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>

Listing 2 adds a trigger to the canvas to start up the storyboard animation on load. The animation spins the tail rotor by animating the angle of the rotate transform through 360 degrees.

The rotate transform is added under the tail rotor path element and looks like this:

<Path.RenderTransform>
    <RotateTransform x:Name="tailRotorTransform" Angle="0" CenterX="-192.6" 
CenterY="-26.1" /> </Path.RenderTransform>

The CenterX and CenterY attributes move the center of the rotation to the center of the tail rotor, keeping it spinning in place. We can "rock" the main rotor with an animation similar to the one we used for the tail rotor, but that goes through a smaller angle and auto-reverses after each cycle.

We've now got the copter clip looking pretty good, and it's now entirely in XAML, with no dependencies on C# code. But it's still on the main page, which isn't very much like the library-based movie clip in the Flash application. The next step is to move it into a Silverlight user control.

Converting to a User Control
In Flash, the movie clip lives in the library, and it can easily be pulled out and used in different contexts. That's not true of the Silverlight version, at least not of the way we've constructed it initially. It would be messy, for example, to create two helicopters instead of one. Wrapping the copter up in a user control gives us the kind of reusability we enjoyed with the Flash movie clip.

It's pretty easy to make the conversion to a user control with Microsoft® Silverlight™ Tools Beta for Visual Studio® 2008. You just pick the "Silverlight User Control" template off the list, and Visual Studio 2008 drops in a new control XAML file and control C# code. The template C# code takes care of control-specific requirements, which amounts to finding the XAML file in the assembly and creating the control using InitializeFromXaml.

Now that we have a template, we can migrate most of the XAML from our current Page.xaml over to the new control. All we've done in the main page to this point is work on the movie clip, so moving the copter movie clip over to the control XAML is a cut-and-paste operation. We now have a nice self-contained, XAML-based movie clip, which appears in its entirety in Listing 3.

Listing 3. The CopterControl.Xaml file.

<Canvas
	xmlns="http://schemas.microsoft.com/client/2007"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Width="100" Height="50"
	xmlns:CopterSL="clr-namespace:CopterSL;assembly=ClientBin/CopterSL.dll"
	>

  <Canvas.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded">
      <BeginStoryboard>
        <Storyboard x:Name="Motion">
          <DoubleAnimation
              Storyboard.TargetName="tailRotorTransform"
              Storyboard.TargetProperty="(RotateTransform.Angle)"
              From="0.0" To="360" Duration="0:0:.05"
              RepeatBehavior="Forever"/>
          <DoubleAnimation
              Storyboard.TargetName="mainRotorTransform"
              Storyboard.TargetProperty="(RotateTransform.Angle)"
              From="-2.5" To="2.5" Duration="0:0:2"
              RepeatBehavior="Forever"
              AutoReverse="True"/>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Canvas.Triggers>

  <Canvas Name="copterMc" RenderTransform="1,0,0,1,321,138">
    <Canvas>
      <Path Fill="#FFFF4E22" Data="M-195,-26Q-189.95,-29.2,-118,-27.95Q-81.3,-27.3,-38.5,
-26Q-34.65,-26,-33.25,-21.45Q-32.3,-18.35,-32.2,-11.1L-31.85,0.25Q-31.1,6,-28.45,7.7Q-26.75,
8.75,-22.8,12.25Q-19.35,15.2,-17.15,16.25Q-10.25,19.5,-1.15,11.5Q0.8,9.75,1.05,9.95Q1.3,
10.1,-0.05,11.65Q-3.75,15.8,-9.15,19.25Q-21.3,27,-38.5,27Q-55.7,27,-67.85,19.25Q-72.2,16.5,
-74.7,9.5Q-77.3,2.4,-80,0.5Q-96.1,-10.7,-151.05,-18.15L-184.4,-22.45Q-197.6,-24.35,-195,-26"/> <Path Fill="#FFFF4E22" Data="M-89.5,-23L-89.5,-29Q-89.5,-32.3,-87.15,-34.65Q-84.8,-37,-81.5,
-37L-35,-37Q-31.7,-37,-29.35,-34.65Q-27,-32.3,-27,-29L-31.1,-29L-32.7,-27Q-33.15,-26.1,-33,
-24.65Q-31.9,-15.2,-34,-15.2L-81.5,-15Q-84.8,-15,-87.15,-17.35Q-89.5,-19.7,-89.5,-23" /> <Path Fill="#FFFF4E22" Data="M-206.7,-58.55L-187.1,-24.65L-195.2,-24.7L-214.8,
-58.6L-206.7,-58.55" /> <Path Fill="#FFFF4E22" Data="M-187.65,-24.4L-193.65,-2L-200.7,-3.9L-194.7,-26.3L-187.65,-24.4" /> <Path Fill="#FF000000" Data="M-59.55,21.85L-66.9,34.55L-70.25,32.65L-62.9,19.95L-59.55,21.85" /> <Path Fill="#FF000000" Data="M-22.4,22.4L-15,35.25L-17.7,36.8L-25.1,23.95L-22.4,22.4" /> <Path Fill="#FF000000" Data="M7,36.5L-82.5,36.5L-82.5,32L7,32L7,36.5" /> <Path Fill="#B366CCFF" Data="M-26.1,-29.05Q-20.75,-28.6,-17.85,-27.4Q-13.4,-25.55,-11.05,
-23.3L-6.55,-19.25Q-3,-16.75,-0.15,-12.35Q2.4,-8.4,3.35,-4.5Q4.6,0.4,3.95,4.35Q3.25,8.45,
0.65,11Q-2.2,13.9,-5.5,15.65Q-9.2,17.6,-12.7,17.6Q-16.05,17.6,-22.2,13.6Q-27.3,10.3,-30.65,
6.95Q-32.9,4.7,-33.3,-2.55Q-33.5,-6.45,-33.1,-13.15Q-33.05,-13.95,-33.95,-21.45Q-34.55,
-26.4,-32.7,-28.4Q-31.65,-29.55,-26.1,-29.05" /> <Path Fill="#FFFFFF00" Data="M-174.25,-25.5L-89.5,-25.5L-89.5,-23L-174.25,-23L-174.25,-25.5" /> <Path x:Name="mainRotor" Fill="#FF000000" Data="M67.2,-41L-43.85,-41L-43.85,
-44.5L67.2,-44.5L67.2,-41 M-157.45,-44.5L-46.4,-44.5L-46.4,-41L-157.45,-41L-157.45,-44.5"> <Path.RenderTransform> <RotateTransform x:Name="mainRotorTransform" Angle="0" CenterX="-46" CenterY="-41" /> </Path.RenderTransform> </Path> <Path x:Name="tailRotor" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Data="M-193.9
,-53.5L-191.4,-26.7L-195.9,-25.45L-197.85,-51.95L-193.9,-53.5 M-190.9,-23.45L-188.4,3.35L-192.9,
4.6L-194.85,-21.9L-190.9,-23.45"> <Path.RenderTransform> <RotateTransform x:Name="tailRotorTransform" Angle="0" CenterX="-192.6" CenterY="-26.1" /> </Path.RenderTransform> </Path> </Canvas> </Canvas> </Canvas>

We can put the newly-packaged control back on the main page with just one line of XAML:

<CopterSL:CopterControl x:Name="Copter" Canvas.Left="0" Canvas.Top="0" />

That was so easy, we might as well take care of adding in the bitmap background now, too. We have the bitmap that we imported into the original Flash application, so we can just pull that directly into Silverlight:

<Canvas.Background>
        <ImageBrush ImageSource="images/scenery.jpg" Stretch="None" AlignmentY="Top"/>
    </Canvas.Background>

The Stretch and AlignmentY attributes override the defaults to put the image at the top left, where it was in the original Flash project.

Adding Buttons
Flash has built-in button components, but there's no such thing as a standard Silverlight button control at this time2. We'll need to create our own button controls to fill in for the Flash buttons on the Copter application.

We've seen how to make movie-clip types of user controls. Creating button controls is a little more difficult, first because we'll want to create them in an external assembly so that they can be used in other projects, and second because there is additional work involved in setting up all the graphic elements that make up a button.

I built a simple button control with an upstate, a down state, and a mouseover state that I used with this project. The button uses storyboard animations for the state change effects. Those buttons are included with the sample source code, but I won't go into detailed explanations here. You can find excellent resources for creating custom button controls in the Silverlight QuickStarts and on Tim Heuer's blog.

The sample button class I built is called MyButton and the assembly is mapped into the "my" namespace. We can now add the four buttons to the Silverlight version of copter, just as they appear in the Flash version:

<Canvas x:Name="controls">
<my:MyButton x:Name="UpButton" MouseLeftButtonUp="OnUpButton" Label="Up" 
Canvas.Left="212" Canvas.Top="312.4"/> <my:MyButton x:Name="DownButton" MouseLeftButtonUp="OnDownButton" Label="Down"
Canvas.Left="212" Canvas.Top="364.3"/> <my:MyButton x:Name="LeftButton" MouseLeftButtonUp="OnLeftButton" Label="Left"
Canvas.Left="131.3" Canvas.Top="338.0"/> <my:MyButton x:Name="RightButton" MouseLeftButtonUp="OnRightButton" Label="Right"
Canvas.Left="298.3" Canvas.Top="338.0"/> </Canvas>

As you can see, we've specified the button event handlers directly in the XAML. All that's left is to implement the button actions.

Final Steps
In Flash, I used Tween3 objects to create the animation when the control buttons are clicked. The Tween class allows you to specify an easing function that contributes to more natural-looking motion. I'd like to be able to do the same in the Silverlight port.

Microsoft Expression® Blend lets you specify easing parameters on keyframe animations. I used Expression Blend to generate animations with keyframe easing, and then applied these to the custom CopterControl to animate it. The up button storyboard, which is kicked off by a button event, is shown in Listing 4.

Listing 4. The AnimateUp storyboard animation

<Storyboard x:Name="AnimateUp">
    <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CopterCanvas" 
Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)"> <SplinePointKeyFrame KeyTime="00:00:01" Value="0.5,0.5" KeySpline="0,0,0.75,1"/> </PointAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CopterCanvas"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].
(TranslateTransform.Y)"> <SplineDoubleKeyFrame x:Name="AnimateUpKeyFrame" KeyTime="00:00:01"
Value="-100" KeySpline="0,0,0.75,1"/> </DoubleAnimationUsingKeyFrames> </Storyboard>

Figure 3. The Silverlight Version

If you look carefully at Listing 4, you'll notice that the target is not the CustomCopter itself, but a canvas. The animation that Blend creates expects the target to have a RenderTransform of a certain type, which the CustomCopter lacks. One generic solution to this is to wrap the control in a canvas which matches Blend's requirements.

I also resorted to a second trick to get the kind of relative animation that we had in response to the Flash buttons. Since Silverlight's DoubleAnimationUsingKeyFrames doesn't allow relative value changes, I instead track the coordinates in C# code and update the Value property of the SplineDoubleKeyFrame before starting each animation.

Figure 3 shows the completed port. Moving to Silverlight not only gives us a new deployment platform, but gives us the .NET framework as a new development platform as well. That brings with it some terrific advantages, including flexible development with CLR languages, manipulating the DOM from managed code, a significant portion of the .NET library, and Visual Studio for an IDE.

Every project will be a little different, but some of the tasks we walked through on this sample, such as:

  • Translating vector graphics (we've considered SWF elements only, but there are also tools for converting other kinds of vector assets)
  • Building self-contained movie clips
  • Importing original art, like the bitmap background
  • Implementing buttons and button actions

will probably be on your to-do list as you get your Flash application running under Silverlight.

More Resources


2 Scott Guthrie says that Silverlight controls will be included in an upcoming release.
3 Flash's Tween class supports easing and allows you to animate movie clip properties. The class is documented here.

* This article was commissioned by and prepared for Microsoft Corporation. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.


Previous Page: Introducing Copter  
Steve Apiki is senior developer at Appropriate Solutions, Inc., a Peterborough, NH consulting firm that builds server-based software solutions for a wide variety of platforms using an equally wide variety of tools. Steve has been writing about software and technology for over 15 years.
Page 1: Introducing CopterPage 2: Timer-less Animation
Rate This Content:
Low     High
4 after 3 ratings