Creating a Game Loop Part 1

Source code for this tutorial: http://silverlightrocks.com/cs/files/folders/silverlight_2_tutorials_source_code/entry317.aspx

XAML based technologies such as Silverlight or WPF have robust animation capabilities which can be used to create compelling interfaces. Tools such as Blend can be used to create some very sophisticated timeline animations. Games, however, have their own needs when it comes to animation. These needs often don't fall into a timeline based style. Typically games will employ what is called a game loop to periodically check the state of the game and act appropriately, including collision detection, input handling, etc.

This has gotten a bit more interesting with Silverlight 2. There are now two ways to implement this, and I can't say yet which is better. I'm very interested to have you try each method out, and see which works better for you. I have a feeling it may fall somewhere in between and using a combination of both may be the best option.

Technique 1: Using an Empty Storyboard

Storyboards are the basis for time based animations in Silverlight, and typically contain one or more animation elements. There is, however, a little trick that can be done with Storyboards which is actually borrowed from the Flash guys. If you create a Storyboard with a duration of 0, it will fire its Completed event one frame after it is begun. So if you begin the Storyboard again in the Completed event, you'll have a timer that executes once per frame.

NOTE: You cannot begin a Storyboard in a control (the Page is just a UserControl like any other) until the control is loaded. Therefore, we'll put the code to create the Storyboard in the Loaded event of the Page.  

First let's create a Storyboard field named gameLoop in the Page class.

   1: Storyboard gameLoop;

Then in the constructor, after the InitializeComponent() statement, add the following:

   1: this.Loaded += new RoutedEventHandler(Page_Loaded);

Then we need the Loaded event and the gameLoop's Completed event:

   1: void Page_Loaded(object sender, RoutedEventArgs e)
   2: {
   3:     gameLoop = new Storyboard();
   4:     gameLoop.SetValue(FrameworkElement.NameProperty, "gameloop");
   5:     this.Resources.Add(gameLoop);
   6:     gameLoop.Completed += new EventHandler(gameLoop_Completed);
   7:     gameLoop.Begin();
   8: }
   9:  
  10: void gameLoop_Completed(object sender, EventArgs e)
  11: {
  12:     // do your game loop processing here
  13:     gameLoop.Begin();
  14: }
 

Ok great, so now gameLoop_Completed will fire on each event interval. Let's add some code to make the ship rotate. First we need to modify the XAML for the ship a bit. Modify the ship.xaml to look like this:

   1: <UserControl x:Class="SpaceRocks.Ship"
   2:     xmlns="http://schemas.microsoft.com/client/2007" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     Width="26" Height="40" >
   5:     <Canvas x:Name="LayoutRoot" RenderTransformOrigin="0.5,0.5">
   6:         <Canvas.RenderTransform>
   7:             <RotateTransform x:Name="rotateTransform" Angle="0"/>
   8:         </Canvas.RenderTransform>
   9:         <Path Data="M0,38 L12,0,24,38,18,32,7,32z" Stroke="#FFFFFFFF" StrokeThickness="2"/>
  10:     </Canvas>
  11: </UserControl>

The new stuff in the XAML is the RenderTransformOrigin attribute and the Canvas.RenderTransform element. The RenderTransformOrigin attribute is specifying that the origin for any transformations is half of the width and half of the height.

The RotateTransform element is setting up a rotation that we can then access via code. Note the x:Name of rotateTransform. Naming an element in XAML makes it easy to reference that element from code. At compile time, a partial class is created which contains the InitializeComponent method. In the InitializeComponent method, declarations and initializations are added for any element which has an x:Name attribute. If you go to the Ship's constructor, and right click on the InitializeComponent() method, and select "Go to Definition", you'll see this partial class. It should look something like this:

   1: public partial class Ship : System.Windows.Controls.UserControl {
   2:     
   3:     internal System.Windows.Controls.Canvas LayoutRoot;
   4:     
   5:     internal System.Windows.Media.RotateTransform rotateTransform;
   6:     
   7:     private bool _contentLoaded;
   8:     
   9:     /// <summary>
  10:     /// InitializeComponent
  11:     /// </summary>
  12:     [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  13:     public void InitializeComponent() {
  14:         if (_contentLoaded) {
  15:             return;
  16:         }
  17:         _contentLoaded = true;
  18:         System.Windows.Application.LoadComponent(this, new System.Uri("/SpaceRocks;component/Ship.xaml", System.UriKind.Relative));
  19:         this.LayoutRoot = ((System.Windows.Controls.Canvas)(this.FindName("LayoutRoot")));
  20:         this.rotateTransform = ((System.Windows.Media.RotateTransform)(this.FindName("rotateTransform")));
  21:     }
  22: }

Notice the rotateTransform field that was automatically generated for us based on the x:Name attribute we put on the RotateTransform. Now let's close that file and leave it alone, it's a generated file and if we make any changes to it we would lose them.

To allow the Page object to get access to the rotateTransform field, let's create a public property on the Ship object called RotationAngle.

   1: public double RotationAngle
   2: {
   3:     get { return rotateTransform.Angle; }
   4:     set { rotateTransform.Angle = value; }
   5: }

And now from the game loop, before the Begin() statement, add code to increment the rotation angle:

   1: void gameLoop_Completed(object sender, EventArgs e)
   2: {
   3:     // do your game loop processing here
   4:     ship.RotationAngle++;
   5:     gameLoop.Begin();
   6: }

And now if you run the program, you should see the ship rotating. This could also have been done with a Storyboard which executed for a certain duration and changed the angle from 0 to 360 and the repeated forever, but in our case, we are going to need tighter control over the rotation of the ship since soon we will be rotating the ship based on keyboard input.

In the next post, we'll look at how to use a DispatcherTimer to do a game loop.

Share this post : digg it! dotnetkicks it! technorati!
Published Friday, March 14, 2008 2:07 AM by Bill Reiss

Comments

# re: Creating a Game Loop Part 1

Hey there!  This is a great blog.  Been watching it for quite some time now with my own Silverlight Game development in 1.0 and 1.1.

I was wondering why you chose to use a Storyboard as a timer rather than the new Timer Dispatcher in 2.0?

pagebrooks.com/.../silverlight-2-has-a-timer-dispatchertimer.aspx

Friday, March 14, 2008 10:55 AM by OmegaMythos

# re: Creating a Game Loop Part 1

I'm covering the DispatcherTimer in "Creating a Game Loop Part 2" coming soon. It seems like I get smoother animations with the storyboard method, I'm planning on putting a sample together that does both so that I can visually compare the two side by side.

Friday, March 14, 2008 11:14 AM by Bill Reiss

# re: Creating a Game Loop Part 1

Great!  I look forward to it!

Saturday, March 15, 2008 2:25 AM by OmegaMythos

# re: Creating a Game Loop Part 1

What if you want to speed up or slow down the animation? Seems that the speed will change depending upon your CPU.

Monday, March 17, 2008 4:07 PM by snowman

# re: Creating a Game Loop Part 1

See Part 2 :)

Monday, March 17, 2008 4:55 PM by Bill Reiss