Nimble Coder

Adventures in Nimble Coding
posts - 76, comments - 39, trackbacks - 0

Bouncing Balls in Silverlight Part 1

BounceTest

Recently I wanted to make a very simple sample in Silverlight that used a little code to animate bouncing balls. The overall effect is fairly simple, but getting the sample down to the basics took a little time. As part of my research, I looked at several old bouncing ball demos using JavaScript and it was an eye-opening reminder of the dark ages of browsers and JavaScript.

For the sample, I wanted to keep everything very simple. I started with a circle (ellipse with the same height and width) in a canvas. In order to move the ball, I chose to position the ball at (0, 0) and use a TranslateTransform to adjust the position. You could easily move the ball around with Left and Top, but I also plan to use RotateTransform in later samples so it makes sense to use a TransformGroup to manipulate the object.

The XAML for the base page is just a Canvas and a rectangle that acts as a border. The ball is placed on the canvas using the common TransformGroup that is created by Expression Blend when you add a transform to an object. Expression Blend also uses the same order for the transforms (ScaleTransform, SkewTransform, RotateTransform, TranslateTransform) and will reorder the transforms back to this order if you adjust any transform using Blend (at least as of Blend 2.5 June 2008 Preview). Here is the XAML for the page:

<UserControl x:Class="BounceTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Canvas x:Name="LayoutRoot">
        <Rectangle x:Name="Boundary" Height="300" Width="400" 
            Canvas.Left="0" Canvas.Top="0"
            Fill="#FFFFFFFF" Stroke="#FF000000" />
        <Ellipse x:Name="Ball01" Height="25" Width="25"
            Canvas.Left="0" Canvas.Top="0"
            Fill="#FFEE3131" Stroke="#FF000000"
            RenderTransformOrigin="0.5,0.5">
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform/>
                    <TranslateTransform X="50" Y="50"/>
                </TransformGroup>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Canvas>
</UserControl>

I created a small class to manage the position and velocity of the ball. The constructor takes the source shape (the ball), the velocity, and the boundary shape. For this scenario, I assume that the ball already has the RotateTransform otherwise it throws an exception. The code could easily create a RotateTransform if it isn't present. The Update method takes the elapsed time since the previous method call. The boundary checking is extremely simple and just changes the direction of the motion when the ball hits a boundary edge. This obviously needs a lot of work for more complex scenarios, but it will do for this simple case.

public class ShapeVelocity
{
	public Shape shape;
	public Vector velocity;
	public TranslateTransform translate;
	public Size bounds;
	public Size container;

	public ShapeVelocity(Shape AShape, Vector AVelocity, Shape BoundsContainer)
	{
		this.shape = AShape;
		this.velocity = AVelocity;

		var renderTransform = this.shape.RenderTransform;
		if (renderTransform is TransformGroup)
		{
			TransformGroup transformGroup = (TransformGroup)renderTransform;
			foreach (Transform transform in transformGroup.Children)
				if (transform is TranslateTransform)
					this.translate = (TranslateTransform)transform;
		}
		if (this.translate == null)
			throw new ArgumentException("Shape must have a TranslateTransform in it");

		container = new Size(BoundsContainer.Width, BoundsContainer.Height);
		bounds = new Size(
			this.shape.ActualWidth + this.shape.StrokeThickness,
			this.shape.ActualHeight + this.shape.StrokeThickness);
	}

	public void Update(TimeSpan Interval)
	{
		Rect pos = new Rect(
			translate.X,
			translate.Y,
			bounds.Width,
			bounds.Height);

		if ((velocity.X < 0.0) && (pos.Left < 0.0))
			velocity.X = -velocity.X;
		else if ((velocity.X > 0.0) && (pos.Right > container.Width))
			velocity.X = -velocity.X;
		if ((velocity.Y < 0.0) && (pos.Top < 0.0))
			velocity.Y = -velocity.Y;
		else if ((velocity.Y > 0.0) && (pos.Bottom > container.Height))
			velocity.Y = -velocity.Y;

		translate.X += velocity.X * (double) Interval.Milliseconds / 1000.0;
		translate.Y += velocity.Y * (double) Interval.Milliseconds / 1000.0;
	}
}

In anticipation of future examples, I used a List<Shape> collection to store the ball shape to updae the position. For the motion, I used the StoryBoard instead of a DispatcherTimer based on the recommendation of Adam Kinney.

public partial class Page : UserControl
{
    private DateTime _lastTime = DateTime.MinValue;
    private double _initialSpeed = 50.0;
    private Storyboard _storyboard;
    private List<ShapeVelocity> _shapes;

    public Page()
    {
        InitializeComponent();
        _shapes = new List<ShapeVelocity>();
        _storyboard = new Storyboard();
        _storyboard.Duration = TimeSpan.FromMilliseconds(10);
        _storyboard.Completed += new EventHandler(storyboard_Tick);
        this.Loaded += new RoutedEventHandler(Page_Loaded);
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        _shapes.Add(new ShapeVelocity(Ball01, new Vector(_initialSpeed, _initialSpeed), Boundary));
        _lastTime = System.DateTime.Now;
        _storyboard.Begin();
    }

    void storyboard_Tick(object sender, EventArgs e)
    {
        DateTime now = System.DateTime.Now;
        TimeSpan interval = now - _lastTime;
        foreach (ShapeVelocity s in _shapes)
        {
            s.Update(interval);
        }
        _lastTime = now;
        _storyboard.Begin();
    }
}

So that is the very simple bouncing ball sample.

Get Microsoft Silverlight