Silverlight Games 101

Write games in Silverlight 2 using C# by Bill Reiss
Our upcoming Silverlight book for beginners (includes a great chapter on game development in Silverlight!) Hello! Silverlight 2 with Dave Campbell, available online now!



Pages

    Recent posts

    Navigation

    Archive

    Blogroll

      Tampa Divorce Lawyer

      North of Tampa in Lutz, Florida. A Tampa Divorce Lawyer focusing on family, divorce, and real estate law.

      Disclaimer

      The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

      Generating Asteroids for the Game

      Source code for this tutorial: http://www.bluerosegames.com/silverlight-games-101/samples/GeneratingAsteroids.zip

      In the original asteroids arcade game, the asteroids themselves were randomly assigned from a predefined set of shapes. I thought it would be more interesting to dynamically generate the outline of each asteroid as we create them.

      The first this we want to do is move the CreateVectorFromAngle and DegreesToRadians methods to another class and make them static and public so that we can use them other places. So in the BlueRoseGames.Helpers project there is a new class called MathHelpers and this is the MathHelpers.cs file:

      using System;
       
      namespace BlueRoseGames.Helpers
      {
          public static class MathHelpers
          {
              public static double DegreesToRadians(double degrees)
              {
                  double radians = ((degrees / 360) * 2 * Math.PI);
                  return radians;
              }
       
              public static Vector CreateVectorFromAngle(double angleInDegrees, double length)
              {
                  double x = Math.Sin(DegreesToRadians(180 - angleInDegrees)) * length;
                  double y = Math.Cos(DegreesToRadians(180 - angleInDegrees)) * length;
                  return new Vector(x, y);
              }
          }
      }

      Change the existing Sprite code to call these static methods instead.

      Now create a new Silverlight User Control in the SpaceRocks project and call it Asteroid.xaml. Add a using statement so that we can use Vector and MathHelpers:

      using BlueRoseGames.Helpers;

      What we’ll do for our asteroids is take a few angles around the circle and using some randomly shuffled radius values, generate a polygon. Then we’ll make these asteroids the content of Sprite objects to get things like positioning, velocity, and wrapping.

      First, change the Asteroid.xaml to have a Canvas for the LayoutRoot and remove the Background property:

      <UserControl x:Class="SpaceRocks.Asteroid"
          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">
       
          </Canvas>
      </UserControl>

      The Height and Width don’t matter since we’ll explicitly set those in the constructor anyway. It’s important that we set the Width and Height so that the Sprite object that wraps the Asteroid knows how big to make itself.

      Here is the code behind for the asteroid:

      using System;
      using System.Windows;
      using System.Windows.Controls;
      using System.Windows.Media;
      using System.Windows.Shapes;
      using BlueRoseGames.Helpers;
       
      namespace SpaceRocks
      {
          public partial class Asteroid : UserControl
          {
              static Random rand = new Random();
       
              void shuffle(double[] lengths)
              {
                  for (int i = 0; i < 100; i++)
                  {
                      int i1 = rand.Next(lengths.Length);
                      int i2 = rand.Next(lengths.Length);
                      if (i1 != i2)
                      {
                          double tmp = lengths[i1];
                          lengths[i1] = lengths[i2];
                          lengths[i2] = tmp;
                      }
                  }
              }
       
              public Asteroid(int radius)
              {
                  InitializeComponent();
                  this.Width = radius * 2;
                  this.Height = radius * 2;
                  double[] lengths = { 1, 1, 1, .97, .97, .94, .94, .91, .91, .88, .85, .82, .82, .79, .76, .73, .61, .51 };
                  shuffle(lengths);
                  Polygon poly = new Polygon();
                  poly.Stroke = new SolidColorBrush(Colors.White);
                  poly.StrokeThickness = 1.5;
                  for (int i = 0; i < 18; i++)
                  {
                      float degrees = i * 20;
                      Vector v = MathHelpers.CreateVectorFromAngle(degrees, radius * lengths[i]);
                      poly.Points.Add(new Point(v.X + radius, v.Y + radius));
                  }
                  LayoutRoot.Children.Add(poly);
              }
          }
      }

      The shuffle() method will take an array and shuffle the elements of the array randomly, like in a deck of cards. In our case, we will be shuffling distances from the center of the object.

      Notice that I have added a radius parameter to the Asteroid constructor. This will allow us to easily create asteroids of varying sizes.

      The first thing to do is set the height and width of the asteroid based on the radius, then the lengths array is a series of percentages stored as double values. The lengths array determines for each of 18 points along the outside of the asteroid (one for every 20 degrees) how far that point will be from the center, with a value of 1 representing a distance equal to the radius, and a value of .5 would be half of the radius.

      This array is shuffled so that the percentage values are mixed up and each asteroid will look differently. I use this technique instead of just randomly generating the distance from the center values to avoid asteroids with values that fluctuate too much or ones where you get a bunch of small values, etc. This seems to work pretty well.

      Next we create our polygon, and going in 20 degree intervals, calculate the vector from the center for each point and add the point to the points collection for the polygon. Then we add the polygon to the root canvas for the Asteroid control.

      Then all that's left is to randomly generate a starting position and velocity, and to set the Width and Height properties of the user control (inherited from Sprite).

      Ok so now to use this class. In the Page class, add a field to hold a list of Asteroids:

      List<Sprite> asteroids = new List<Sprite>();

      and then, at the end of the Page_Loaded method, add the following:

      for (int i = 0; i < 4; i++)
      {
          Sprite a = new Sprite(new Asteroid(40));
          asteroids.Add(a);
          gameSurface.Children.Add(a);
      }

      Note that the asteroids are added both to our list of Asteroids (so that they can be easily accessed later in the Update and other methods) and to the list of Children for the game surface. If you don't add your user control to the list of children for the game surface, they will not be displayed.

      Then to move the asteroids, in the gameLoop_Update method, add the following:

      foreach (Sprite asteroid in asteroids)
      {
          asteroid.Update(elapsed);
      }

      so if all went well, if you run the program, you should see 4 asteroids floating around.

      asteroids

      Posted: Nov 23 2008, 22:25 by Bill Reiss | Comments (1) RSS comment feed |
      • Currently 0/5 Stars.
      • 1
      • 2
      • 3
      • 4
      • 5
      Filed under:
      Adding Thrust

      Full source for this tutorial: http://www.bluerosegames.com/silverlight-games-101/samples/AddingThrust.zip

      A famous April Fools Day network RFC stated:

      With sufficient thrust, pigs fly just fine. However, this is not necessarily a good idea. It is hard to be sure where they are going to land, and it could be dangerous sitting under them as they fly overhead.

      Fortunately, we're not dealing with pigs, we're dealing with space ships, although a DreamBuildPlay contest warmup entry replaced space ships with space cows http://udderassault.whatsupnow.com/ so I guess you could make this game with space pigs, but I digress.

      So what do we need to do to make the ship move around the screen?

      • We need to store the current velocity and current position of the ship somewhere
      • When the "up" key is pressed, we need to change the velocity based on the direction the ship is currently pointing, and compare this new value against the maximum velocity of the ship and adjust it appropriately if it exceeds the max velocity
      • Based on the velocity, we need to reposition the ship in the game loop
      • If the ship hits an edge, we need to "wrap" to the other end of the screen by repositioning the ship

      During my previous work with game frameworks, the data type that was used more than any other was the Vector. Unfortunately, the Vector value type is available in WPF, but is omitted in Silverlight 2. Because I feel it is important to have access to Vector in game programming, I have implemented my own version of the Vector struct in BlueRoseGames.Helpers. It follows the interface of the WPF version, so if it becomes available, it should be easy to switch.

      Vectors are made up of a direction and a magnitude, and in games programming, are most commonly represented as an X and Y value. A vector with a magnitude of 3 in the X direction and 1 in the Y direction can be written as (3,1). We will be using Vectors to store the velocities of our sprites in this game.

      Repositioning a Sprite

      It may seem strange at first that the User Interface elements in Silverlight don't have a Left or Top property. The explanation that I have heard for this is that Canvases (and other Xaml graphics elements) are not always absolutely positioned, many times they are in a "flow" layout, where there is no absolute positioning, it is all relative to the container and other elements within that container. For this reason, the absolute positioning properties are not built in to the object, and must be accessed indirectly through the SetValue and GetValue methods.

      So to set the Left position of an element, you do something like the following:

      this.SetValue(Canvas.LeftProperty, 100d);

      Since this is a bit verbose, let's encapsulate this logic in a Property. Position and Velocity are going to be needed for each sprite in our game, so it makes sense to create a class which our other sprites can inherit from so that we don't have to duplicate this logic. So let's create a user control called Sprite in our SpaceRocks project. Here is the XAML for the Sprite user control:

      <UserControl x:Class="SpaceRocks.Sprite"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
          Width="50" Height="50" RenderTransformOrigin=".5,.5">
          <UserControl.RenderTransform>
              <RotateTransform x:Name="rotate"/>
          </UserControl.RenderTransform>
          <Grid x:Name="LayoutRoot" Background="Red">
       
          </Grid>
      </UserControl>

      And this is the code behind:

      using System.Windows;
      using System.Windows.Controls;
      using BlueRoseGames.Helpers;
       
      namespace SpaceRocks
      {
          public partial class Sprite : UserControl
          {
              Point position;
              public Vector Velocity;
       
              public Sprite()
              {
                  InitializeComponent();
              }
       
              public Sprite(FrameworkElement content)
              {
                  InitializeComponent();
                  this.Content = content;
                  this.Width = content.Width;
                  this.Height = content.Height;
              }
       
              public Point Position
              {
                  set
                  {
                      position = value;
                      this.SetValue(Canvas.LeftProperty, value.X - Width / 2);
                      this.SetValue(Canvas.TopProperty, value.Y - Height / 2);
                  }
                  get
                  {
                      return position;
                  }
              }
       
              public double RotationAngle
              {
                  get
                  {
                      return rotate.Angle;
                  }
                  set
                  {
                      rotate.Angle = value;
                  }
              }
          }
      }

      Notice that when setting the position, we subtract half the length and half the width. This is so that Position actually corresponds to the center of the object. This is a matter of preference and also depends on the type of game. In this case, I felt it made sense to center the objects around their position values. If the X and Y values were set without this offset, the position of the sprite would correspond to the top left corner.

      Now instead of declaring the ship sprite in the Page.xaml, let’s declare the Ship as type Sprite and create it in the Page constructor. First in Page.xaml.cs, declare the ship sprite:

      Sprite ship;

      Then make the Page constructor look like this:

      public Page()
      {
          InitializeComponent();
          ship = new Sprite();
          ship.Position = new Point(320, 240);
          gameSurface.Children.Add(ship);
          this.GotFocus += new RoutedEventHandler(Page_GotFocus);
          this.LostFocus += new RoutedEventHandler(Page_LostFocus);
          this.MouseLeftButtonDown += new MouseButtonEventHandler(Page_MouseLeftButtonDown);
          gameLoop = new CompositionTargetGameLoop();
          gameLoop.Update += new GameLoop.UpdateHandler(gameLoop_Update);
          keyHandler = new KeyHandler(this);
      }

      So basically we’re creating the sprite for the ship, setting its position, and adding it to the gameSurface canvas. Everything else can stay the same. Now if you run the game, it will look like this:

      redsprite

      The sprite is a red block since that’s what the XAML for the Sprite control has by default. Notice, however, that there’s another constructor on the Sprite control which takes a FrameworkElement:

      public Sprite(FrameworkElement content)
      {
          InitializeComponent();
          this.Content = content;
          this.Width = content.Width;
          this.Height = content.Height;
      }

      When you use this one, whatever is specified for the FrameworkElement is used as the content for the sprite. So if we change the ship creation in the Page constructor to this:

      ship = new Sprite(new Ship());

      Then things will look and behave like they did before. The difference is that now we have a Sprite control that we can reuse for other sprites in the game and it can have our common sprite logic in it.

      Now to add code to change the position of the ship. First, if we're thrusting, we need to modify the velocity of the ship based on the ship's current rotation. Let's add a method to the Sprite class called Thrust, and a couple of helper methods:

      private double DegreesToRadians(double degrees)
      {
          double radians = ((degrees / 360) * 2 * Math.PI);
          return radians;
      }
       
      public Vector CreateVectorFromAngle(double angleInDegrees, double length)
      {
       
          double x = Math.Sin(DegreesToRadians(180 - angleInDegrees)) * length;
          double y = Math.Cos(DegreesToRadians(180 - angleInDegrees)) * length;
          return new Vector(x, y);
      }
       
      public void Thrust(TimeSpan ElapsedTime)
      {
          Vector v = CreateVectorFromAngle(RotationAngle, ElapsedTime.TotalSeconds * 300);
          Velocity += v;
          if (Velocity.Length > 500)
          {
              Velocity *= (500 / Velocity.Length); 
          }
      }

      So what does this do? It converts the current rotation and a magnitude to a Vector. This Vector is in the direction that the ship is currently facing. Then we add that to the current velocity. Then, if the magnitude (Length) of the Velocity is greater than a maximum Velocity of 500, we scale the Velocity vector to have a magnitude of 500.

      Now, we need code to call the Thrust method if a certain key is pressed. We'll use the "W" key. So in the gameLoop_Update method in the Page class, add the following:

      if (keyHandler.IsKeyPressed(Key.W))
      {
          ship.Thrust(elapsed);
      }

      Now all we need to do is to update the position based on the velocity for the sprite in the game loop. We also need to see if the sprite has gone off the screen, and if so, we need to wrap it to the other edge of the screen. Add the following to the Sprite class:

      Point WrapPositionToScreen(Point p)
      {
          Point result = p;
          if (result.X > 640) result -= new Vector(640, 0);
          if (result.X < 0) result += new Vector(640, 0);
          if (result.Y > 480) result -= new Vector(0, 480);
          if (result.Y < 0) result += new