June 2007 - Posts

Zero Gravity game for Silverlight 1.1

The team at Terralever has put together a very polished game using Silverlight 1.1 called Zero Gravity. You can get more details at Tim Heuer's blog here:

http://timheuer.com/blog/archive/2007/06/26/zerogravity.aspx

It's really a great example of how designers and developers can work together on a project more closely that they have been able to work before, and some of the great results that can come out of it.

Posted by Bill Reiss | 2 comment(s)

Loading XAML Dynamically

Source code for this tutorial: http://silverlightrocks.com/cs/files/folders/slg101_tutorials/entry71.aspx

By default, when you create a new Silverlight User Control, code is generated in the constructor which reads the associated XAML file and calls InitializeFromXaml to load it into the control. This is probably the most common option, but you can get some interesting results that would be difficult to do otherwise by changing this default behavior.

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.

Before we get started, there are a couple of methods in the Ship class that we will need in the Asteroid class, so let's move them to the Sprite class so that all of our sprites can access them. These methods are CreateVectorFromAngle and DegreesToRadians.

Now create a new Silverlight User Control and call it Asteroid. Then go into the Asteroid class and change it to inherit from Sprite, like we did earlier for the Ship class, and add a using statement so that we can use Vector and any other utilities we may need:

using SilverlightGames101.Utilities;

By inheriting from Sprite, the asteroids will get all of the nice behaviors we added to the Sprite class, like position and velocity, and wrapping around the edges of the screen. The main thing we have left is to generate the XAML. Change the Asteroid.xaml to look like this:

<Canvas xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="{1}"
        Height="{1}"
>
  <Path x:Name="path" Data="{0}" Stroke="#FFFFFFFF" StrokeThickness="2" Width="{1}" Height="{1}"/>
</Canvas>

Notice the {0} and {1} placeholders. Anyone who has done some .Net programming probably has a good idea of what these are going to be for. In this case, we'll do a simple string replace using string.Format to create our dynamic XAML.

I think it would be easiest at this point to list out the entire completed Asteroid class and step through some of the more important parts.

namespace SpaceRocks
{
    public class Asteroid : Sprite
    {
        FrameworkElement root;
        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)
        {
            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("SpaceRocks.Asteroid.xaml");
            string pathXaml = "";
            double[] lengths = { 1, 1, 1, .97, .97, .94, .94, .91, .91, .88, .85, .82, .82, .79, .76, .73, .61, .51 };
            shuffle(lengths);
            for (int i = 0; i < 18; i++)
            {
                float degrees = i * 20;
                Vector v = CreateVectorFromAngle(degrees, radius * lengths[i]);
                if (degrees == 0)
                {
                    pathXaml += string.Format("M{0},{1} L", (int)v.X + radius, (int)v.Y + radius);
                }
                else
                {
                    pathXaml += string.Format("{0},{1} ", (int)v.X + radius, (int)v.Y + radius);
                }
            }
            pathXaml += "z";
string xaml = string.Format(new System.IO.StreamReader(s).ReadToEnd(), pathXaml, radius * 2); root = this.InitializeFromXaml(xaml); Position = new Point(rand.Next(0, 640), rand.Next(0, 480)); Velocity = CreateVectorFromAngle(rand.Next(360), rand.Next(30, 70)); Width = radius * 2; Height = radius * 2; } } }

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 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.

So in the loop, for each of the shuffled lengths and a corresponding angle, we can generate a vector which represents the location of the point in X,Y space relative to the center of the asteroid.

The first time through the loop, we need to do a "Move To" command (designated by "M") and in subsequent iterations, we need to do a "Line To" command (designated by "L"). In order to make the point relative to the center instead of the top left corner, we can add the radius to the vector value.

Finally, to close the path, we add a "z" to the end.

Next, let's create the xaml we will want to feed into InitializeFromXaml. To do this, we'll use a simple string.Format statement which replaces {0} with out dynamically generated path and {1} with double the radius, which is also the width and height of our asteroid.

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<Asteroid> asteroids = new List<Asteroid>();

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

for (int i = 0; i < 4; i++)
{
    Asteroid a = new Asteroid(40);
    asteroids.Add(a);
    this.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 page. If you don't add your user control to the list of children for the page, they will not be displayed.

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

for (int i=0; i<asteroids.Count; i++)
{
    asteroids[i].Update(ElapsedTime);
}

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

asteroid_dynamic

Posted by Bill Reiss | 8 comment(s)

Adding Thrust

Full source for this tutorial: http://silverlightrocks.com/community/files/folders/slg101_tutorials/entry41.aspx

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 1.1 Alpha. 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 SLG101Utilities. 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<double>(Canvas.LeftProperty, 100);

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 class called Sprite. This is the code:

 

using System;
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 SilverlightGames101.Utilities;

namespace SpaceRocks
{
    public class Sprite : Control
    {
        Point position;
        public Vector Velocity;
        public Point Position
        {
            set
            {
                position = value;
                this.SetValue<double>(Canvas.LeftProperty, value.X - Width/2);
                this.SetValue<double>(Canvas.TopProperty, value.Y - Height/2);
            }
            get
            {
                return position;
            }
        }
    }
}

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 change the Ship class to inherit from Sprite instead of Control, and add a statement to the end of the Page_Loaded method in the Page class setting the initial position of the ship to the center of the screen or (320,240).

ship.Position = new Point(320, 240);

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 Ship 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(ElapsedTime);
}

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 Vector(0, 480);
    return result;
}

public void Update(TimeSpan ElapsedTime)
{
    Position = WrapPositionToScreen(Position + Velocity * ElapsedTime.TotalSeconds);
}

And then in the GameLoop_Update method of the Page class, call this update method for the ship object:

ship.Update(ElapsedTime);

and we're done for now. Run the program again and you should be able to maneuver the ship.

Posted by Bill Reiss | 2 comment(s)
More Posts