Vectors and framebased animations
In Chapter 4, we talked about different ways to do frame-based animation in Silverlight. Regardless of which method you choose, objects that are animated with a series of frames can use vector motion, too. For instance, one of the examples we discussed was a duck flapping its wings. Once you have the flapping animation going, you still need a way to move the object on the screen. This would be done via vectors.
In this example, we'll take a look at a frame-based animation that is a little more complex than the duck example. We will be working with a monkey walk cycle that spans 12 frames, each of which is shown in the following series of illustrations.

For this example, the monkey was animated via the Visual State Manager in Blend and a storyboard timer that switches frames every 5 milliseconds. Slower frame switching still preserves the illusion of animation, but you will find that you need to balance between the speed of the animation and the speed the object travels across the screen.
For an object such as a duck that is flying, you won't notice it as much, but with an object such as our monkey, the figure may have a tendency to skate across the screen while the animation plays. This is why the storyboard timing and x velocity of the object need to be managed.
1. To code along with this example, open the monkeyWalk project. This project contains a bit of code to get things going. In the MainPage.xaml.es file, an instance of the monkey user control called myMonkey is declared. It is then positioned, added to the LayoutRoot Canvas, and made to start walking via the storyboard timer walkTimer, which is located in the monkey user control file:
public partial class MainPage : UserControl {
private monkey myMonkey = new monkeyQ;
public MainPage() {
InitializeComponent();
Canvas.SetTop(myMonkey, 488); Canvas.SetLeft(myMonkey, 150); LayoutRoot.Children.Add(myMonkey);
myMonkey.walkTimer.Begin();
2. To augment this application for user input, we'll need to add a bit of code to the monkey.xaml. cs code-behind file. Start by declaring publicly accessible variables to store the x velocity of the monkey, a Boolean flag that will be tested to see if the monkey is in the process of walking, and a double to store the application width. In addition, add a private variable to store the monkey's current position.
public int velocityX = 1; public bool walking = false; public double appWidth;
private double monkeyPosition;
3. In the MainPage.xaml.es file, add the following bold code shown to the MainPageQ constructor. This code sets the appWidth property of the myMonkey user control for bounds checking.
Canvas.SetTop(myMonkey, 488); Canvas.SetLeft(myMonkey, 150); myMonkey.appWidth = LayoutRoot.Widthj LayoutRoot.Children.Add(myMonkey);
4. At the top of the walkTimer_Completed() event handler, add code to change the walking flag to true, and update the value of the monkeyPosition variable:
this.walking = true;
monkeyPosition = Canvas.GetLeft(this);
5. After the closing curly brace of the switch() statement, add a line of code to update the location of the monkey object on the screen:
Canvas.SetLeft(thiSj monkeyPosition += velocityX);
6. If you run the project at this point, the monkey will be drawn on the screen and animated and will move very slowly to the right. We want the monkey to walk based on user input, so let's modify the MainPage.xaml.es file to add this capability. Start by removing the line of code in the MainPageQ method that causes the monkey animation to begin:
myMonkey.walkTimer.BeginQ;
7. Instead of beginning on its own, we will make the animation begin when a key is pressed. To do this, add a KeyDown event to the MainPageQ constructor:
this.KeyDown += new KeyEventHandler(MainPage_KeyDown);
8. Create the Page_KeyDown() event handler function:
private void MainPage_KeyDown(object sender, KeyEventArgs e) {
9. When the left or right arrow keys are pressed, the monkey will be made to move in the direction of the arrow key that was pressed. The walking flag you just added to the monkey code-behind will be used to determine if the monkey is already moving. If it is not, the timer that moves the monkey through its frame poses is started. Of note is the way the monkey is turned if the left arrow key is pressed—it is scaled to -1 along the x axis, effectively flipping the object. Because the scaling is applied to the canvas that contains all of the poses for the monkey, all of the poses inside that canvas are reversed as well. All of this functionality is handled by placing the following switchQ statement inside the MainPage_KeyDown() event handler function:
case Key.Left:
if (ImyMonkey.walking) myMonkey.walkTimer.BeginQ;
myMonkey.monkeyScale.ScaleX = -1;
break;
case Key.Right:
if (ImyMonkey.walking) myMonkey.walkTimer.BeginQ;
myMonkey.monkeyScale.ScaleX = 1;
break;
10. Run the application. After clicking the canvas area, you will be able to use the arrow keys to control the direction in which the monkey faces but not the direction in which it moves. To add this functionality, we're going to add a variable called strideLength to the MainPage.xaml.es file:
private int strideLength = 8;
11. The reason why a variable is being used is because movements to the left and right both need access to the value, and based on the object being animated, it may take a little bit of tweaking to get the value set just right. It's much easier to change a single value than to hunt through code for multiple instances of a number. The strideLength value will be assigned to the x velocity value of the monkey so that the monkey will move 8 pixels with each completion cycle of the storyboard timer. To change direction and move the monkey to the left, a negative strideLength value is assigned to the x velocity vector of the object. To move to the right, the strideLength value is assigned to the x velocity of the monkey as is. Update the switchQ statement with the two bold lines in the following code:
case Key.Left:
if (ImyMonkey.walking) myMonkey.walkTimer.Begin(); myMonkey.monkeyScale.ScaleX = -1; myMonkey.velocityX = -strideLength; break;
case Key.Right:
if (ImyMonkey.walking) myMonkey.walkTimer.Begin(); myMonkey.monkeyScale.ScaleX = 1; myMonkey.velocityX = strideLengthj break;
In the code, each time the storyboard completes, the monkey is moved the distance defined by the strideLength variable. Remember that depending on your object's motion, you may end up needing to use a larger or smaller value. The idea is to get a number that works well in order to keep the object from looking as though it is skating across the screen.
After clicking the application to get focus, you will be able to control the monkey with the left and right arrows, as shown in Figure 5-10.
Figure 5-10. Scaling an object to -1 along the x axis makes it possible to reverse a frame-based object's direction.
The problem is that given the chance, the monkey will walk right off the edge of the screen. Once again, we will need to add a bit of code to control where our object is allowed to travel. In this case, if the left or right edge is encountered, we want to stop the walk cycle timer to stop the monkey from advancing. This code is added in the monkey.xaml.es code-behind file, near the bottom of the walkTimer_Completed() event handler code.
12. Remove the code that updates the monkey's position: Canvas.SetLeft(this, monkeyPosition += velocityX);
13. Add an if...else statement to test for the left and right sides of the application. In this example, we're modifying the edge values by 1 to account for the border around the main canvas.
Canvas.SetLeft(this, l); walkTimer.Stop();
else if (Canvas.GetLeft(this) + this.Width > appWidth - l) {
Canvas.SetLeft(thiSj appWidth - this.Width); walkTimer.Stop();
14. Next, wrap the existing Visual State Manager code in an else statement:
VisualStateManager.GoToState(this, whichState, false); frame += 1;
15. Finish up by adding the following line of code shown in bold back in to update the monkey's position on the screen. This code will be executed if no boundary is encountered.
Canvas.SetLeft(this, monkeyPosition += velocityX);
VisualStateManager.GoToState(this, whichState, false); frame += 1;
Finally, the last line of the function is the existing code that restarted the walkTimer. Notice that the code to restart the timer is going to run even if the code to stop the timer due to a boundary is encountered. This is deliberate, because the application is still live and awaiting input. If the walkTimer were started inside the else clause in the preceding code, when a boundary was reached, pressing the opposite key would turn the object, but it would stick to the wall since the boundary checks are still valid conditions. The final code for the function is shown in the following listing:
private void walkTimer_Completed(object sender, EventArgs e) {
this.walking = true;
monkeyPosition = Canvas.GetLeft(this);
case l:
whichState = "pose_l"; break; case 2:
whichState = "pose_2"; break; case 3:
whichState = "pose_3"; break; case 4:
whichState = "pose_4"; break; case 5:
whichState = "pose_5"; break; case 6:
whichState = "pose_6"; break; case 7:
whichState = "pose_7"; break; case 8:
whichState = "pose_8"; break; case 9:
whichState = "pose_9"; break; case 10:
whichState = "pose_10"; break; case 11:
whichState = "pose_ll"; break; case 12:
whichState = "pose_12"; break;
Canvas.SetLeft(this, monkeyPosition += velocityX);
Canvas.SetLeft(thiSj l); walkTimer.StopO;
else if (Canvas.GetLeft(this) + this.Width > appWidth - l) {
Canvas.SetLeft(thiSj appWidth - this.Width); walkTimer.StopO;
VisualStateManager.GoToState(this, whichState, false); frame += 1;
this.walkTimer.BeginQ;
Now you can run the final version of the monkeyWalk project. Click the application so it receives focus for input, and then use the arrow keys. When the right arrow key is pressed, the monkey will walk until the right side of the screen is reached, at which time it will stop.
If the left arrow key is pressed, the monkey will continue to the left until the edge of the application is encountered. All of the code covered here is available in the monkeyWalkCompleted project.
Average user rating: 3.7 stars out of 3 votes
Post a comment