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

Published Sunday, June 17, 2007 2:07 PM by Bill Reiss

Comments

# re: Loading XAML Dynamically

thanks for this, it's very helpful getting me started on my silverlight game.

Monday, June 18, 2007 5:26 PM by andrew

# re: Loading XAML Dynamically

Very cool.  It runs great!  One thing, though -- although the code builds and runs, I can't get intellisense to work on anything but the SpaceRocks namespace.  Any thoughts on that?

Saturday, July 14, 2007 1:46 AM by bonder

# re: Loading XAML Dynamically

never mind, I'm an idiot. :)

A couple suggestions:

1. Consider moving the base Sprite class into the utilities folder.

2. Consider moving the utilities folder into a separate project so it can be built as a library.

3. This is fun! :)

Saturday, July 14, 2007 1:51 AM by bonder

# re: Loading XAML Dynamically

Thanks for the feedback. The reason I didn't put Sprite in the utilities library is because the needs of the sprite class can vary significantly based on the game you are doing, but I'll ponder this some more.

Saturday, July 14, 2007 9:46 PM by Bill Reiss

# re: Loading XAML Dynamically

Agreed, but probably there is a good abstract base class lurking in there sometime in the future from which several concrete children are waiting to spring. :)

Tuesday, August 21, 2007 4:29 AM by bonder

# re: Loading XAML Dynamically

Your shuffle routine can be optimized quite a bit in a very simple way.  As written, you're performing 100 random swaps within the array; 100 presumably being chosen as an amount that gives a high probability of every element in the array being swapped at least once.

The thing about randomness is that after a certain point, the results of applying more randomness aren't distinguisable from what you had before.  In general, applying an "overkill" number of swaps to the array at random positions is no different than simply guaranteeing to swap each index of the array once with a random destination.  Not only does this give you high entropy in the final result, it minimizes the number of loop iterations AND the number of times you call rand.Next().  If you've never profiled the .NET random number generator, believe me, you want to minimize calls to rand.Next().  There's also very little to be gained by testing the source and target array indexes to avoid "null swaps"; generally speaking, the overhead of performing the test is larger than just doing a useless swap sometimes.

Try this shuffle implementation instead.  The cycles you free will be put to much better use elsewhere in your game loop:

void shuffle(double[] lengths)

{

   for (int i = 0; i < lengths.Length; i++)

       {

           int j = rand.Next(lengths.Length);

           double tmp = lengths[i];

           lengths[i] = lengths[j];

           lengths[j] = tmp;

       }

   }

}

Monday, September 17, 2007 6:31 PM by cloister

# re: Loading XAML Dynamically

cloister, thanks for this, I guess I was focusing on the dynamic XAML part instead of the random part, and didn't think about how big my sample was. I'll look to incorporate your changes as soon as I can.

Bill

Monday, September 17, 2007 7:05 PM by Bill Reiss

# re: Loading XAML Dynamically

I keep having problems with the Asteroid class. I get an error stating that "The name 'CreateVectorFromAngle' does not exist in the current context'. Any thoughts? I tried your source code and I got a different problem so if you could help that would be great! Oh and when are you posting another blog post? You're doing great!

Sunday, December 02, 2007 5:41 PM by gamerpsp360

# re: Loading XAML Dynamically

Hey There - I've been following your tutorials for 2.0, but they don't go as far as the 1.x version so I'm trying to adopt... Your sprite inherits from a Control. From what I'm seeing in 2.0, I assume I use a UserControl. However, because 2.0 uses partial classes, I can't change my XAML's code behind to inherit from my baseclass of Spite since the auto-generated code inherits from UserControl. What would be the best way to handle this in 2.0? Thanks Chu

Saturday, May 31, 2008 6:56 PM by chumad

# re: Loading XAML Dynamically

Yeah you can't do that right now, there's a bug in Beta 1, Beta 2 will be out soon which fixes this. I'll write a tutorial about it when it's available.

Saturday, May 31, 2008 7:03 PM by Bill Reiss

# re: Loading XAML Dynamically

So did beta 2 fix the inheriting problem? Hope so :)

Saturday, June 14, 2008 8:58 AM by chumad

# re: Loading XAML Dynamically

The inherited UserControl story is a bit better in Beta 2, there is a good post about it here:

www.shahed.net/.../Implementing-your-own-base-class-for-user-controls-in-Silverlight-2.aspx

PErsonally I've been moving more towards an encapsulation model instead of an inheritance, hopefully I'll be able to post about it soon.

Bill

Saturday, June 14, 2008 12:11 PM by Bill Reiss