Falling Snow in Silverlight - Page 4
       by kirupa  |  19 December 2009

In the previous page, you added your behavior to your LayoutRoot and were able to see the falling snow effect for yourself. We started diving into the code to better understand how everything works, and let's continue where we left off on this page.


Next up is the ApplicationLoaded method (event handler to be precise) that is responsible for creating the animation:

void ApplicationLoaded(object sender, RoutedEventArgs e)
{
foreach (FrameworkElement element in this.AssociatedObject.Children)
{
FrameworkElement localCopy = element;
 
double yPosition = Canvas.GetTop(localCopy);
double xPosition = Canvas.GetLeft(localCopy);
 
double speed = 2*randomNumber.NextDouble();
double counter = 0;
double radius = 30 * speed * randomNumber.NextDouble();
 
localCopy.Opacity = .2 + randomNumber.NextDouble();
 
CompositionTarget.Rendering += delegate(object o, EventArgs arg)
{
counter += Math.PI / (180*speed);
 
if (yPosition < Application.Current.RootVisual.DesiredSize.Height)
{
yPosition += .2 + speed;
}
else
{
yPosition = -localCopy.Height;
}
 
Canvas.SetTop(localCopy, yPosition);
Canvas.SetLeft(localCopy, xPosition + radius * Math.Cos(counter));
};
}
}

As methods in this tutorial go, this is a bit on the large side so let's look at bits and pieces of it in greater detail starting with the loop that goes through each of the children:

foreach (FrameworkElement element in this.AssociatedObject.Children)

What this loop is doing is going through each of the children our AssociatedObject has. If you recall, our AssociatedObject is a Canvas, and all of your snowflakes are contained inside it:

[ the behavior is attached to LayoutRoot, or AssociatedObject ]

Each child, a snowflake, is referenced by the element variable that I declare as a FrameworkElement inside the loop itself. All of the code you will see will run once for each element, so if you have a lot of snowflakes, just be glad that you aren't the one having to do all of this heavy lifting.


FrameworkElement localCopy = element;

Inside the foreach loop, the first thing I do is create a new copy of the reference to child element. You'll see why this is important in a little bit.


double yPosition = Canvas.GetTop(localCopy);
double xPosition = Canvas.GetLeft(localCopy);
 
double speed = 2*randomNumber.NextDouble();
double counter = 0;
double radius = 30 * speed * randomNumber.NextDouble();
 
localCopy.Opacity = .2 + randomNumber.NextDouble();

In the next few lines of code, I am declaring and initializing some variables that will help set some properties on the snowflake as its falls.

In the first two lines, yPosition and xPosition get the current location of my snowflake using Canvas's GetTop and GetLeft methods.

The speed and radius variables use the randomNumber variable you initialized earlier to define the speed with which the snowflakes fall and how wide the radius of their oscillation will be.

In the last line, I set the Opacity of my snowflake to be something random. This is what gives your snowflakes a semi-transparent look when you are running it.


All of this sets us up for the next section of code that actually creates the animation loop:

CompositionTarget.Rendering += delegate(object o, EventArgs arg)
{
counter += Math.PI / (180*speed);
 
if (yPosition < Application.Current.RootVisual.DesiredSize.Height)
{
yPosition += .2 + speed;
}
else
{
yPosition = -localCopy.Height;
}
 
Canvas.SetTop(localCopy, yPosition);
Canvas.SetLeft(localCopy, xPosition + radius * Math.Cos(counter));
};

I spoke about CompositionTarget.Rendering in my earlier Creating Killer Animations in Code tutorial, but I will summarize the interesting details that you will need to understand how this fits in with the overall falling snow effect.

The Rendering event is the equivalent of a loop that just keeps going each time your screen refreshes. Becase this loop doesn't block the UI, you can safely specify any animation-related changes here and not have to worry about whether the resulting animation will look smooth and fluid.

There is one critical detail that I employ in this use of the Rendering event. Notice that my event handler is explicitly a delegate, and since it this is all inside my foreach loop, each snowflake will be getting a copy of this delegate so that you have individual control over each snowflake despite dealing with an event that exists application wide.

If you are familiar with Flash, this is as close as you can get to emulating the enterFrame/onEnterFrame event.


All of the code inside the Rendering event, like I mentioned earlier, will fire each time the screen refreshes. On most machines, that is 60 times a second, but your computer may have a higher or lower refresh that alters this a little bit. What I am trying to say is that any code will get called many times a second. Thererfore, any type of incrementing or decrementing I do needs to be sufficiently small so that the changes, in aggregate over a second, are reasonable.

Speaking of incremting, the first thing I do inside the delegate for the Rendering event is increment my counter value:

counter += Math.PI / (180*speed);

The counter value is incremented ever so slightly each tick. and you'll see shortly why it is being incremented as slowly as it is.


if (yPosition < Application.Current.RootVisual.DesiredSize.Height)
{
yPosition += .2 + speed;
}
else
{
yPosition = -localCopy.Height;
}

This chunk of code here is what is responsible for defining the current position of the snowflake as it falls. First, I check to see if the snowflake is still visible by comparing my current position with the total height of the application's viewing area.

If the snowflake is still visible, I incremeent the yPosition variable slightly:

yPosition += .2 + speed;

If the snowflake is about to hit the edge, I kick it back to the top where it can continue its looping:

yPosition = -localCopy.Height;

Changing just the variable that represents position doesn't actually do much, but we fix that right up....


Canvas.SetTop(localCopy, yPosition);
Canvas.SetLeft(localCopy, xPosition + radius * Math.Cos(counter));

All of the previous lines of code were basically the setup to the two lines you see here. In these two lines, the updated x and y positions for your snowflakes are used to actually change the position of your snowflake.

This should be pretty straightforward. I simply set the Top and Left properties on my Canvas to the variables I've been fiddling with. Just to call one thing out, the oscillation each snowflake experiences is caused by the Math.cos(counter) code that I pass in to the Canvas.SetLeft method.

Before we call it a day, notice what I am using as the target element the for SetTop and SetLeft method. It isn't the element variable  that you see as part of your foreach loop. Instead, it is is localCopy - which is basically the exact same thing as your element....except it isn't. When it comes to delegates and anonymous methods, variable scoping doesn't work as you would expect.

If I just passed in the element variable, all of the code would affect just the last snowflake. By creating another copy of the variable in the form of localCopy, each time the loop runs and another delegate is created, that delegate gets its own copy of the element variable. This ensures that each delegate affects the appropriate element instead of being overwritten everytime until the last element is left standing.


Conclusion
And with that, you are done with this tutorial on how to create falling snow. While I kinda call this the faling snow technique, there is nothing about this that limits its use only for simulating snow particles. You can make all the tweaks to the speed and radius that you want, and you can even change the eillipses to something else and the code will adapt well.

Anyway, below you will find the source files for the version of the falling snow example I described over the past four pages:

Download Source Files

Got a question or just want to chat? Comment below or drop by our forums (they are actually the same thing!) where a bunch of the friendliest people you'll ever run into will be happy to help you out!

When Kirupa isn’t busy writing about himself in 3rd person, he is practicing social distancing…even on his Twitter, Facebook, and LinkedIn profiles.

Hit Subscribe to get cool tips, tricks, selfies, and more personally hand-delivered to your inbox.

COMMENTS


 1 2 | 3 | 4




SUPPORTERS:

kirupa.com's fast and reliable hosting provided by Media Temple.