Mar 14

Source code: http://www.bluerosegames.com/SpaceRocks/SpaceRocks_4_Inheriting_Sprite.zip

One of the big benefits of using a Silverlight Templated Control for our sprite class is that it’s easy to inherit from it. This can be tricky if using a user control and if you use something else it’s hard to associate visuals with it. The first sprite type we’ll inherit from Sprite is our ship. Create a new Silverlight Templated Control called Ship.cs. This creates a new template in Generic.xaml and a barebones class definition. This class inherits from Control by default, but you can change it to inherit from Sprite (since Sprite inherits from Control this is ok).

In Generic.xaml, let’s replace the default template generated for the Ship control with one for how we want the ship to look.

<Style TargetType="local:Ship">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Ship">
                <Canvas Height="27" Width="22">
                    <Path Data="M11,1.5 L19,25 14,21 8,21 3,25 11,1.5z" Stroke="White" StrokeThickness="2" StrokeLineJoin="Round"/>
                </Canvas>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

You can also edit templates in Generic.xaml in Blend by selecting them from the Resources tab. This is how the ship looks in Blend:

image

We’ll do some other work later to make it look more like the original arcade game but for now this will work. Now if we go into the Game class and replace the sprite with our ship and center it on the screen the code will look like this:

using System;
using System.Windows.Controls;
using Microsoft.Xna.Framework;

namespace SpaceRocks
{
    public partial class Game : UserControl
    {
        Ship ship;

        public Game()
        {
            InitializeComponent();
            ship = new Ship();
            ship.Position = new Vector2(320,240);
            LayoutRoot.Children.Add(ship);
        }

        private void GameLoop_Update(object sender, SilverArcade.SilverSprite.SimpleEventArgs<TimeSpan> e)
        {
            double seconds = e.Result.TotalSeconds;
        }
    }
}

Now if you run the game you should see the sprite in the middle of the screen. Well, almost in the middle if the screen. The top left corner of the sprite is actually in the middle of the screen. We can see this easier if we draw a little rectangle in the middle of the screen:

<Rectangle Width="4" Height="4" Canvas.Left="318" Canvas.Top="238" StrokeThickness="0" Fill="White" />

And the output looks like this:

image

For this game, we want to position sprites by specifying their center value. Since the sprites are going to be different sizes it would be best to figure out dynamically based on the size of the sprite where the center is.If we add this code to the Sprite class all of our sprites will be able to take advantage of it. What we’ll do is use the OnApplyTemplate method to get the size of the root element in the sprite template and then subtract half of the size from the position when the set the Canvas.Left and Canvas.top properties. Here is the new Sprite class:

using System.Windows.Controls;
using Microsoft.Xna.Framework;
using System.Windows;
using System.Windows.Media;

namespace SpaceRocks
{
    public class Sprite : Control
    {
        double x, y;
        double dw = 0;
        double dh = 0;
        static Vector2 gameSize = new Vector2(640, 480);

        public Sprite()
        {
            this.DefaultStyleKey = typeof(Sprite);
            this.SizeChanged += new SizeChangedEventHandler(Sprite_SizeChanged);
        }

        void Sprite_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Vector2 pos = Position;
            x = double.NaN;
            y = double.NaN;
            Position = pos;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            FrameworkElement element = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;
            dw = element.Width / 2;
            dh = element.Height / 2;
            Width = element.Width;
            Height = element.Height;
        }

        public double X
        {
            get
            {
                return x;
            }
            set
            {
                if (x != value)
                {
                    x = value;
                    this.SetValue(Canvas.LeftProperty, x - dw);
                }
            }
        }

        public double Y
        {
            get
            {
                return y;
            }
            set
            {
                if (y != value)
                {
                    y = value;
                    this.SetValue(Canvas.TopProperty, y - dh);
                }
            }
        }

        public Vector2 Position
        {
            get
            {
                return new Vector2((float)x, (float)y);
            }
            set
            {
                X = value.X;
                Y = value.Y;
            }
        }

        public Vector2 Velocity { get; set; }

        public void Update(double elapsedSeconds)
        {
            Position += Velocity * (float)elapsedSeconds;
            if (Position.X > gameSize.X) Position -= new Vector2(gameSize.X, 0);
            if (Position.X < 0) Position += new Vector2(gameSize.X, 0);
            if (Position.Y > gameSize.Y) Position -= new Vector2(0, gameSize.Y);
            if (Position.Y < 0) Position += new Vector2(0, gameSize.Y);
        }
    }
}

In the OnApplyTemplate method, we get the root element of the template using the VisualTreeHelper class, and set the size of the control to the size of the root element. We also set dw and dh to half of this size so that we can use these values when setting the left and top properties. In the SizeChanged event, in order to force the position to be set when the size changes, we need to set the backing fields to an invalid value (well, actually any value other than the previous value) otherwise the actual positioning code will be skipped. This is one problem with the optimization of checking againt the backing field property but it’s not that big of a deal and the benefits far outweigh this extra complexity.

Now if you run the game you should see that the ship is centered on the screen.

image

We can now remove our little rectangle since it has served its purpose. In the next step we’ll see how we can rotate the ship.

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

One Response to “Space Rocks game step 4: Inheriting from Sprite”

  1. Space Rocks game step 9: the asteroid sprite | Silverlight Games 101 Says:

    [...] Just like with the ship, we can create a Silverlight Templated Control for the asteroids and then change it to inherit from Sprite. By inheriting from Sprite we’ll get support for movement based on a velocity and wrapping at the edges of the screen. So create a new Silverlight Templated Control from the new item wizard and call it Rock.cs. As we previously saw with the ship, this creates a new entry in Generic.xaml which contains the control template for the Rock sprite. You can edit this in Blend or by hand to make it look how you want. The asteroids are a bit different from the ship since there are 3 different shapes for the asteroids. Even the smaller versions (asteroids come in large, medium, and small) have those same 3 shapes. You could create a different Silverlight Templated Control for each variation and have them all inherit from Rock (which inherits from Sprite) but since they are so similar we can do a little visual magic instead. [...]

Leave a Reply

preload preload preload