Page view counter

Silverlight Tips of the Day - Blog by Mike Snow

Game Programming with Silverlight

Silverlight Tip of the Day #30: Fast Sprite Animation in Silverlight

For this tutorial I will be demonstrating how to create a fast, optimized Sprite animation class. In my demo, you will be able to:

  1. Increase the speed of the sprites.
  2. Increase the count of the sprites.

To stress the sprites further, I have added random movement (bouncing off walls), rotational transforms and opacity/transparency shifting.

Run this application now by clicking on this link: http://silverlight.services.live.com/invoke/66033/Fast%20Sprites/iframe.html. If you increase the speed and the number of sprites, you should notice the animation continues to run smoothly despite the large number and speed.

Preview Screen Shot (left = 100 sprites, right = 1000 sprites)

image  image

Each sprite is encapsulated in its own class I have called Sprite. Let’s take a look at the complete class for a Sprite below. In summary:

  1. The Sprite class inherits from Control. This way you can directly add your Sprite to the Canvas tree. The XAML that represents the sprite is put into a template (_spriteTemplate) and which is applied to the class by calling ApplyTemplate();
  2. The Sprite constructor takes the width and height of the sprite. It also loads and applies the template.
  3. To set the image of the Sprite you must call SetImage() passing the file name of the image resource which must be included as part of your project.
  4. The rest of the routines in this class control the movement, position and rotation of the Sprite.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
 
namespace FastAnimation
{
    public class Sprite : Control
    {
        private Image _spriteImage;
        private RotateTransform _rotateTransform;
        private int _width;
        private int _height;
        private double _posX = 0;
        private double _posY = 0;
        private double _xInc = 0;
        private double _yInc = 0;
        private int _opacityDir = 1;
 
        private string _spriteTemplate =
          "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
          "                  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
          "<Image x:Name=\"SpriteImage\">" +
          "   <Image.RenderTransform>" +
          "      <RotateTransform x:Name=\"ImageTransform\">" +
          "      </RotateTransform>" +
          "   </Image.RenderTransform>" +
          "</Image>" +
          "</ControlTemplate>";
 
        public Sprite(int width, int height)
        {
            _width = width;
            _height = height;
 
            Template = (ControlTemplate)XamlReader.Load(_spriteTemplate);
            ApplyTemplate();
        }
 
        public override void OnApplyTemplate()
        {
            _spriteImage = (Image)GetTemplateChild("SpriteImage");
            _rotateTransform = (RotateTransform)GetTemplateChild("ImageTransform");
 
            _rotateTransform.CenterX = _width / 2;
            _rotateTransform.CenterY = _height / 2;
        }
 
        public void SetImage(string resource)
        {
            Uri uri = new Uri(resource, UriKind.Relative);
            ImageSource imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
            _spriteImage.Source = imgSrc;
        }
 
 
        public double YInc
        {
            set { _yInc = value; }
        }
        public double XInc
        {
            set { _xInc = value; }
        }
 
        public double PosX
        {
            get { return _posX; }
            set
            {
                _posX = value;
                this.SetValue(Canvas.LeftProperty, _posX);
            }
        }
 
        public double PosY
        {
            get { return _posY; }
            set
            {
                _posY = value;
                this.SetValue(Canvas.TopProperty, _posY);
            }
        }
     
        public bool Step(int speed)
        {
            if (_xInc * speed + _posX > 800 || _xInc * speed + _posX < 0)
                return false;
            if (_yInc * speed + PosY > 600 || _yInc * speed + PosY < 0)
                return false;
 
            PosX += _xInc * speed;
            PosY += _yInc * speed;
 
            if (_opacityDir == 1)
                _spriteImage.Opacity += 0.01;
            else
                _spriteImage.Opacity -= 0.01;
            if (_spriteImage.Opacity >= 1)
                _opacityDir = 0;
            else if (_spriteImage.Opacity <= 0)
                _opacityDir = 1;
 
            return true;
        }
 
        public void Rotate()
        {
            _rotateTransform.Angle += 1;
            _rotateTransform.Transform(new Point(32, 24));
        }
 
    }
}

In Page.xaml, we declare our UI elements that allow us to increase the speed and count of sprites:

<UserControl x:Class="FastAnimation.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="800" Height="600" Background="Black">
    <Grid x:Name="LayoutRoot" Background="Black">
        <Canvas Background="Black" x:Name="MyCanvas">
            <TextBlock Canvas.ZIndex="10000" Foreground="White">Speed</TextBlock>
            <Slider Canvas.ZIndex="10000" x:Name="SliderSpeed" Minimum="0" Maximum="50" Width="100" Canvas.Left="50"></Slider>
            <TextBlock Canvas.ZIndex="10000" x:Name="SpeedValue" Canvas.Left="150"  Foreground="White">1</TextBlock>
            <TextBlock  Canvas.ZIndex="10000" Canvas.Top="20" Foreground="White">Count</TextBlock>
            <Slider Canvas.ZIndex="10000" x:Name="SliderCount"  Canvas.Top="20" Minimum="0" Maximum="1000" Value="100" Width="100" Canvas.Left="50"></Slider>
            <TextBlock Canvas.ZIndex="10000" x:Name="CountValue" Canvas.Left="150" Canvas.Top="20"  Foreground="White">100</TextBlock>
        </Canvas>
    </Grid>
</UserControl>

In Page.xaml.cs we:

  1. Create an array of sprites (100 by default).
  2. Setup our game loop timer using the Storyboard discussed in Tip of the Day #16. In this loop, we move and rotate the sprites.
  3. Monitor events for the Speed and Sprite Count slider controls.
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 FastAnimation
{
    public partial class Page : UserControl
    {
        private Sprite[] _sprites;
        private Storyboard _gameLoop = new Storyboard();
        private int _spriteCount = 0;
        private int _speed = 1;
 
        public Page()
        {
            InitializeComponent();
 
            this.Loaded += new RoutedEventHandler(Page_Loaded);
         
        }
 
        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            SliderCount.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(SliderCount_ValueChanged);
            SliderSpeed.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(SliderSpeed_ValueChanged);
            CreateSprites(100);
 
            _gameLoop.Duration = TimeSpan.FromMilliseconds(0);
            _gameLoop.Completed += new EventHandler(MainGameLoop);
            _gameLoop.Begin();
        }
 
        private void CleanUp()
        {
            for (int i = 0; i < _spriteCount; i++)
            {
                MyCanvas.Children.Remove(_spritesIdea);
            }
        }
 
        private void CreateSprites(int spriteCount)
        {
            lock (this)
            {
                CleanUp();
                _spriteCount = spriteCount;
                _sprites = new Sprite[_spriteCount];
 
                Random rand = new Random();
                for (int i = 0; i < _spriteCount; i++)
                {
                    Sprite sprite = new Sprite(64, 48);
                    double x = (double)rand.Next(800);
                    double y = (double)rand.Next(600);
                    sprite.SetImage("fireballlogo.png");
                    sprite.PosX = x;
                    sprite.PosY = y;
                    ChangeDirections(sprite);
                    MyCanvas.Children.Add(sprite);
                    _spritesIdea = sprite;
                }
            }
        }
 
        private void ChangeDirections(Sprite sprite)
        {
            Random rand = new Random();
 
            double xi = rand.Next(1, 100);
            double yi = rand.Next(1, 100);
            xi *= 0.01;
            yi *= 0.01;
            int n1 = rand.Next(0, 2);
            int n2 = rand.Next(0, 2);
            if (n1 == 0)
                xi *= -1;
            if (n2 == 0)
                yi *= -1;
            sprite.XInc = xi;
            sprite.YInc = yi;
        }
 
        private void MoveSprites()
        {
            for (int i = 0; i < _spriteCount; i++)
            {
                Sprite sprite = _spritesIdea;
                double x = sprite.PosX;
                double y = sprite.PosY;
                if (false == sprite.Step(_speed))
                {
                    ChangeDirections(sprite);
                }
            }
        }
 
        private void RotateSprites()
        {
            for (int i = 0; i < _spriteCount; i++)
                _spritesIdea.Rotate();
        }
 
        private void MainGameLoop(object sender, EventArgs e)
        {
            MoveSprites();
            RotateSprites();
            _gameLoop.Begin();
        }
 
        private void SliderCount_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            int count = (int) SliderCount.Value;
            CountValue.Text = count.ToString();
            CreateSprites(count);
        }
 
        private void SliderSpeed_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            _speed = (int) SliderSpeed.Value;
            SpeedValue.Text = _speed.ToString();
        }
 
    }
}

Comments

Microsoft Weblogs said:

For this tutorial I will be demonstrating how to create a fast, optimized Sprite animation class. In

# August 20, 2008 8:41 PM

2008 August 21 - Links for today « My (almost) Daily Links said:

Pingback from  2008 August 21 - Links for today &laquo; My (almost) Daily Links

# August 21, 2008 1:06 AM

syntax42 said:

If the rotation of the sprites are all the same, as in this demo, the they could share a common RotationTransform. This way the loop that updates the rotation of the sprites wont be necessary. Right?

# August 21, 2008 4:34 AM

BabbaBlog said:

Silverlight tip #4: una classe sprite

# August 21, 2008 8:09 AM

Dew Drop - August 21, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - August 21, 2008 | Alvin Ashcraft's Morning Dew

# August 21, 2008 10:11 AM

chrisaswain said:

I'm not sure I understand the benefit of the sprite inheriting from Control instead of the typical UserControl.  You seem to imply it's for performance.  Could you elaborate on that?

# August 21, 2008 10:30 AM

Silverlight news for August 21, 2008 said:

Pingback from  Silverlight news for August 21, 2008

# August 21, 2008 10:55 AM

Mirrored Blogs said:

Post: Approved at: Aug-21-2008 Linux &amp; Silverlight Microsoft and Novell expanding their partnership

# August 21, 2008 12:51 PM

mike.snow said:

syntax42 - Each image must have its own render transform, in Silverlight you can not share this object. Also, it would be needed if you want the images to rotate independently of each other at different angles.

chrisaswain - I went back and tried the Sprite class as a UserControl (Sprite.xaml + Sprite.cs) and as a custom Control (just Sprite.cs with template) and I found the FPS to be the same for both.

# August 21, 2008 2:07 PM

syntax42 said:

I dont have my code right here, but im pretty sure i have several images that share the same ScaleTransform. That way i can scale them all uniformly by setting the ScaleX and ScaleY of that one ScaleTransform. I would suspect the same would be true for the RotateTransform?

# August 21, 2008 4:47 PM

mike.snow said:

As soon as I set more than one Image to the same RotateTransform the images failed to rotate.

Let me know if you see otherwise with RotateTransform.

Here is my code:

public partial class Page : UserControl

   {

       RotateTransform rt = new RotateTransform();

       private Storyboard _gameLoop = new Storyboard();

       public Page()

       {

           InitializeComponent();

           rt.CenterX = 32;

           rt.CenterY = 24;

           Image1.RenderTransform = rt;

           Image2.RenderTransform = rt;

           this.Loaded += new RoutedEventHandler(Page_Loaded);

       }

       void Page_Loaded(object sender, RoutedEventArgs e)

       {

           _gameLoop.Duration = TimeSpan.FromMilliseconds(0);

           _gameLoop.Completed += new EventHandler(MainGameLoop);

           _gameLoop.Begin();

       }

       private void MainGameLoop(object sender, EventArgs e)

       {

           rt.Angle += 5;

           rt.Transform(new Point(32, 24));

           _gameLoop.Begin();

       }

   }

# August 21, 2008 6:01 PM

Community Blogs said:

Page Brooks with CoolMenu and Custom Collections for Controls, Bill Reiss on Calling WCF on local server

# August 22, 2008 2:43 AM

Visual Web Developer Team Blog said:

Silverlight Tip of the Day #32 &#160; Title : How to Declare a Custom User Control from a XAML Page.

# August 26, 2008 1:13 PM

News said:

Silverlight Tip of the Day #32 Title : How to Declare a Custom User Control from a XAML Page. Demo :

# August 26, 2008 2:57 PM

Recent Links Tagged With "fast" - JabberTags said:

Pingback from  Recent Links Tagged With "fast" - JabberTags

# September 12, 2008 3:34 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:24 AM

jin_u as blog » Blog Archive » ??????????????? ??? ?????? said:

Pingback from  jin_u as blog  &raquo; Blog Archive   &raquo; ??????????????? ??? ??????

# January 14, 2009 8:57 PM

br1 said:

There are emoticons in the code.

# June 11, 2009 1:35 PM

mike.snow said:

Yes, replace them with single quotes. Bug in the blog tool.

# June 11, 2009 1:41 PM