Mike Snows Silverlight Blog

Game Programming with Silverlight 2.0
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();
        }
 
    }
}
Tip of the Day #29: Creating a Transparent, Draggable Dialog with Rounded Corners

For this tutorial we will step you through the basics of what you need to do to create a dialog that you can:

  1. Drag/drop.
  2. Add transparency (opacity) to.
  3. Round the corners.

To preview and run this application please visit this link: http://silverlight.services.live.com/invoke/66033/Border%20Demo/iframe.html. Move any of the sliders as well as mouse left click, hold and drag/drop the dialog.

Step 1: Creating a Transparent Dialog with Rounded Corners

For the background of the dialog we will use a Border control. Border controls allow you to set a CornerRadius which is used to round the corners of the border. You can also set the Opacity level which is used to indicate the level of transparency you want for the control.

This is the XAML for our Border: