Page view counter

Silverlight Tips of the Day - Blog by Mike Snow

Game Programming with Silverlight

Silverlight Tip of the Day #43: Making it Snow in Silverlight

I am not a big fan of winter, being that summer is my favorite season. However, this morning I woke up and heard it’s going to be snowing in the mountains (Cascades) so it inspired me to have a little premature fun by creating a snowy scene in Silverlight.

Demo:

 

Screenshot:

image 

To the right of the demo is a toolbar that allows you to:

  1. Adjust the volume of snow.
  2. Adjust the wind speed (left-right).

image

I noticed the application was a lot less performant on a beta 2 build then on an RTM build. So, if you are using beta 2 I would recommend caution when increasing the volume of the snow.

To make this demo follow these steps:

Step 1. Create a Silverlight Application Project using Visual Studio 2008. (See Tip of the Day #2 for details).

Step 2. Add the following images to your Silverlight Application project:

Step 3. Add a new Silverlight User Control to your Silverlight Application and call it SnowFlake.xaml. To do this, right click on your Silverlight application node in the Solution Explorer. Choose “Add->New Item…”. Select “Silverlight User Control” changing the Name to “Snowflake.xaml” and hit the OK button.

Step 4. Change the XAML of your Snowflake.xaml control to the following.

<UserControl x:Class="Snowflakes.SnowFlake"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Image x:Name="Flake" Source="snowflake.png">
        <Image.RenderTransform>
            <ScaleTransform x:Name="SnowScale" ScaleX="0.25" ScaleY="0.25"></ScaleTransform>
        </Image.RenderTransform>
    </Image>
</UserControl>

We are essentially adding a single image to represent a snowflake. Since the original snowflake image is bigger than I wanted, I have scaled it to 0.25 (1/4) its original size by using a ScaleTranform.

Step 5. In the code behind of the snowflake (Snowflake.xaml.cs) we will add the logic needed to make the snowflake fall. Essentially what we are doing is:

  1. Track the Left, Top coordinates of the snowflake. The snowflake has a random 1 in 50 chance of changing direction.
  2. When the snowflakes Top coordinate equals the floor value (764 in my images case), or when it goes off the screen we set Completed = true. Our main timer loop in Page.xaml.cs will go through and clean up all snowflakes with Completed set to true.
  3. When we create the snowflake we randomly scale it up or down a little to give an added depth perception.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
 
namespace Snowflakes
{
    public partial class SnowFlake : UserControl
    {
        double _posLeft = 0.0;
        double _posTop = 0.0;
        double _floorValue = 764;
        int seed = DateTime.Now.Millisecond;
        Random _rand = new Random(DateTime.Now.Millisecond);
        private bool _completed = false;
 
        double _horzInc = 0.0;
 
        public bool Completed
        {
            get { return _completed; }
        }
 
        public SnowFlake(double left, double top)
        {
            InitializeComponent();
 
            _posLeft = left;
            _posTop = top;
 
            this.SetValue(Canvas.LeftProperty, _posLeft);
            this.SetValue(Canvas.TopProperty, _posTop);
 
            Random rndSize = new Random(DateTime.Now.Millisecond);
            SnowScale.ScaleX = SnowScale.ScaleY = 0.05 * rndSize.Next(1, 6);
 
        }      
        
        public void Fall(double windValue)
        {
            int value = _rand.Next(50);
 
            if (value == 25) //  1 in 50 change to change direction
            {
                if (_horzInc >= 0.2)
                    _horzInc = 0;
                else if (_horzInc <= -0.2)
                    _horzInc = 0;
                else
                {
                    value = _rand.Next(3);
                    if (value == 1) _horzInc = 0.2;
                    else if (value == 2) _horzInc = -0.2;
                    else if (value > 0) _horzInc = 0.0;
                }
            }
            
            double horzValue = _horzInc + (windValue * 0.1);
            
            _posLeft += horzValue;
               
            this.SetValue(Canvas.LeftProperty, _posLeft);
            this.SetValue(Canvas.TopProperty, _posTop++);
 
            _completed = (_posLeft >= 1019 || _posLeft <= 0 || _posTop >= _floorValue);
        }
    }
}

Step 6: Back in Page.xaml.cs we add a timer to move and create the snowflakes. When moving the snowflakes, we check to see if the snowflake has Completed = true. If so, we add it to an array for removal. When creating the snowflakes, we create them along the top of the page. However, if there is wind we also need to create them to the left or to the right of the scene as well depending on the wind direction.

The following is the code for Page.xaml.cs including the code needed to handle the toolbar:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
 
namespace Snowflakes
{
    public partial class Page : UserControl
    {
        List<SnowFlake> _snowFlakes = new List<SnowFlake>();
        Storyboard _snowflakeTimer = new Storyboard();
        Random _rand = new Random(DateTime.Now.Millisecond);
        int _newFlakeCount = 2;
        int _wind = 0;
 
        public Page()
        {
            InitializeComponent();
 
            _snowflakeTimer.Duration = TimeSpan.FromMilliseconds(10);
            _snowflakeTimer.Completed += new EventHandler(SnowFlakeTimer);
            _snowflakeTimer.Begin();
        }
 
        private void SnowFlakeTimer(object sender, EventArgs e)
        {
            MoveSnowFlakes();
            CreateSnowFlakes();
        }
 
        private void MoveSnowFlakes()
        {
            List<SnowFlake> _flakesToRemove = new List<SnowFlake>();
         
            foreach (SnowFlake flake in _snowFlakes)
            {
                flake.Fall(_wind);
                if (true == flake.Completed)
                    _flakesToRemove.Add(flake);
            }
            foreach (SnowFlake flake in _flakesToRemove)
            {
                _snowFlakes.Remove(flake);
                SnowCanvas.Children.Remove(flake);
            }
            _snowflakeTimer.Begin();
        }    
 
        private void CreateSnowFlakes()
        {
            TotalCount.Text = "Total Snowflakes = " + _snowFlakes.Count;
          
            int count = _rand.Next(0, _newFlakeCount);
            for (int i = 0; i < count; i++)
            {
                SnowFlake flake = new SnowFlake(_rand.Next(0, 1020), 0.0);
                _snowFlakes.Add(flake);
                SnowCanvas.Children.Add(flake);
            }
            if (_wind < 0)
            {
                for (int i = 0; i < count; i++)
                {
                    SnowFlake flake = new SnowFlake(1010, _rand.Next(0, 700));
                    _snowFlakes.Add(flake);
                    SnowCanvas.Children.Add(flake);
                }
            }
            else if (_wind > 0)
            {
                for (int i = 0; i < count; i++)
                {
                    SnowFlake flake = new SnowFlake(0, _rand.Next(0, 700));
                    _snowFlakes.Add(flake);
                    SnowCanvas.Children.Add(flake);
                }
            }
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Host.Content.IsFullScreen = true;
        }
 
        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (null != Volume)
            {
                _newFlakeCount = (int)Volume.Value;
                VolumeValue.Text = _newFlakeCount.ToString();
            }
        }
 
        private void Wind_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (null != Wind)
            {
                _wind = (int)Wind.Value;
                WindValue.Text = _wind.ToString();
            }
        }
    }
}

Step 7: The following is the toolbar code we put in Page.xaml:

<UserControl x:Class="Snowflakes.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="1020" Height="768">
    <Canvas x:Name="SnowCanvas" Width="1020" Height="768" Background="Black">
        <Image Width="1020"  Source="Scene.jpg"></Image>
        <TextBlock Canvas.Left="1022" x:Name="TotalCount" Foreground="White">Total Snowflakes</TextBlock>
        <TextBlock Canvas.Left="1022" Canvas.Top="30" Foreground="White">Volume</TextBlock>
        <Slider x:Name="Volume" Value="3" ValueChanged="Slider_ValueChanged" Canvas.Left="1110" Canvas.Top="30"  Minimum="0" Maximum="10" Width="100"></Slider>        
        <TextBlock  Canvas.Left="1210" Canvas.Top="30" Foreground="White" x:Name="VolumeValue">2</TextBlock>
       
        <TextBlock Canvas.Left="1022" Canvas.Top="60" Foreground="White">Wind</TextBlock>
        <Slider x:Name="Wind" Value="0" ValueChanged="Wind_ValueChanged" Canvas.Left="1110" Canvas.Top="60"  Minimum="-50" Maximum="50" Width="100"></Slider>
        <TextBlock  Canvas.Left="1210" Canvas.Top="60" Foreground="White" x:Name="WindValue">0</TextBlock>
        <Button Canvas.Left="1022" Canvas.Top="100" Content="Full Screen" Click="Button_Click"></Button>
    </Canvas>
</UserControl>
 

With that, you have a beautiful snowy scene to enjoy!

Thank you,
--Mike Snow

 Subscribe in a reader

Comments

Microsoft Weblogs said:

I am not a big fan of winter, being that summer is my favorite season. However, this morning I woke up

# September 22, 2008 3:33 PM

jkyoutsey said:

Odd.  Doesn't work in Chrome at all.  FF and IE render this without issue.  Chrome seems to be slower in general when running Silverlight apps.  But yours won't run at all in Chrome.  And the code is straightforward enough.

# September 22, 2008 3:35 PM

mike.snow said:

That's the same with most Silverlight apps in Chrome. Chrome just isn't at the stage yet to support SL. I am not certain what their plans are.

# September 22, 2008 3:49 PM

Michael Paulus said:

"I noticed the application was a lot less performant on a beta 2 build then on an RTM build" is RTM out?

# September 22, 2008 4:24 PM

wisecarver said:

Nice job Mike, really appreciate it. ;-)

# September 22, 2008 5:47 PM

22khz said:

seems so Michael. Wondering when it will be available for download :)

# September 23, 2008 2:15 AM

2008 September 23 - Links for today « My (almost) Daily Links said:

Pingback from  2008 September 23 - Links for today &laquo; My (almost) Daily Links

# September 23, 2008 7:37 AM

Silverlight news for September 23, 2008 said:

Pingback from  Silverlight news for September 23, 2008

# September 23, 2008 9:24 AM

mike.snow said:

RTM is not out yet, as soon as it is, I will post :)

# September 23, 2008 10:58 AM

Odegaard said:

"I noticed the application was a lot less performant on a beta 2 build then on an RTM build"

So are you hinting at that Silverlight is almost completed?

# September 23, 2008 12:32 PM

Dew Drop - September 23, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - September 23, 2008 | Alvin Ashcraft's Morning Dew

# September 23, 2008 3:24 PM

Silverlight news for September 24, 2008 said:

Pingback from  Silverlight news for September 24, 2008

# September 24, 2008 4:08 AM

avbersSL said:

Sign me up for the intermediate RTM build...

# September 24, 2008 7:59 AM

Mike Snows Tipp des Tages #43, #44 und #45 at Blog von J??rgen Ebner said:

Pingback from  Mike Snows Tipp des Tages #43, #44 und #45 at Blog von J??rgen Ebner

# September 25, 2008 3:24 AM

Visual Web Developer Team Blog said:

Silverlight Tip of the Day #46 Title: Font Support in Silverlight Silverlight Tip of the Day #45: Title:

# September 25, 2008 1:55 PM

Silverlight Tips of the Day - Blog by Mike Snow said:

In the recent release of Silverlight 2 RC0 there is a new event that fires once before the rendering

# September 29, 2008 8:00 PM

anas said:

Its eating the Processor ! why it takes 100% of  CPU ?

# October 9, 2008 8:19 AM

Silverlight Tips of the Day - Blog by Mike Snow said:

In some of my older posts I was creating a separate random generator for each object. However, the documentation

# December 1, 2008 3:41 PM

Блог Константина Кичинского said:

Открытки, как изветно, бывают самые-самые разные, поэтому сразу от слов к делу. Открытка будет вот такая:

# December 29, 2008 11:27 AM

Блог Константина Кичинского said:

Открытки, как извеcтно, бывают самые-самые разные, поэтому сразу от слов к делу. Открытка будет вот такая:

# December 29, 2008 3:54 PM

Silverlight Tips of the Day - Blog by Mike Snow said:

The purpose of this post is to create an outline summary all the blogs from my Silverlight tips of the

# January 2, 2009 5:56 PM

o UAU nosso de cada dia said:

essa lista eu copiei desse blog bárbaro (acompanhe por RSS você também): uma lista de dicas super úteis

# January 3, 2009 6:25 AM

Блог Константина Кичинского said:

Открытки, как известно, бывают самые-самые разные, поэтому сразу от слов к делу. Падающий снег, мигающая звездочка, встроенные DeepZoom, несколько ссылок и открывающееся окошко с благодарностями. Исходники – в самом конце (их можно свободно использовать

# February 8, 2009 8:11 AM