Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Creating Games with Silverlight: A Simple Shooter

imageThis article will take you through some of the steps to creating the basics for writing games with Silverlight. This is a simple shooter style game that contains some of the building blocks for games that require abilities such as vectors, collision detection, a game loop, movement, and keyboard input. Please refer to the full source code for this project for any areas of the code that are not covered in detail in this article.

Inspiration

There are a number of examples and tutorials out there to help get started creating games. You will find the basics of this game to be very similar to a Visual C# Coding4Fun article here. This article will show you how to create a shooter style game in Silverlight. My first Silverlight game experience was playing with a Asteroids style game by Bill Reiss. My usage of vectors and how to do a game loop came from great examples like his and others. There are a number of sites and blogs where you can get good info on game development with Silverlight. Here are just a few.

Basic Layout

To begin, Open Visual Studio and create a new Silverlight Application in either C# or VB.net. First, we will start with the layout of the screen. First, I added a canvas to the grid inside the Page.xaml that is created for you. I set it's background to black, and named it 'gameRoot'.

<UserControl x:Class="SimpleShooter.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
   <Grid x:Name="LayoutRoot">
        <Canvas x:Name="gameRoot" Width="500" Height="400" Background="Black" >
        </Canvas>
    </Grid>
</UserControl>

Next we will start adding controls to our project for things like game entities, and information displays. In this example. To complete layout of the game, add user controls to the progect called: Info, LivesRemaining, Score, and WaveInfo. In those controls, To begin I added basic TextBlocks to display information on the state of the game. Please note there are better ways to arrange things in a user control, beyond what I show here. I am keeping to a simple canvas and direct positioning to introduce the concept of Canvas.Top and Canvas.Left. I have now placed these controls on Page.xaml and assigned an x:Name to each. At this point, the controls have only a TextBlock in them.

<UserControl x:Class="SimpleShooter.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:SimpleShooter="clr-namespace:SimpleShooter"
    Width="500" Height="400">
    <Grid x:Name="LayoutRoot">
        <Canvas x:Name="gameRoot" Width="500" Height="400" Background="Black">
            <SimpleShooter:RemainingLives x:Name="ctlLives" Canvas.Top="380" Canvas.Left="10" />
            <SimpleShooter:Score x:Name="ctlScore" Canvas.Top="10" Canvas.Left="10" />
            <SimpleShooter:WaveInfo x:Name="ctlWaveInfo" Canvas.Left="440" Canvas.Top="10" />
            <SimpleShooter:Info x:Name="ctlInfo" Canvas.Top="10"/>
        </Canvas>
    </Grid>
</UserControl>

To finish our layout lets add a star field. To do this, we will write function to generate random numbers, and another that will randomly distribute ellipses on our base canvas. We will need to inherit from System.Security.Cryptography for this:

C#

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();

        GenerateStarField(350);
    }

    void GenerateStarField(int numberOfStars)
    {

        for (int i = 0; i < numberOfStars; i++)
        {

            Ellipse star = new Ellipse();
            double size = GetRandInt(10, 800) * .01;
            star.Width = size;
            star.Height = size;
            star.Opacity = GetRandInt(1, 5) * .1;
            star.Fill = new SolidColorBrush(Colors.White);
            int x = GetRandInt(0, (int)Math.Round(gameRoot.Height, 0));
            int y = GetRandInt(0, (int)Math.Round(gameRoot.Width, 0));
            star.SetValue(Canvas.TopProperty, (double)x);
            star.SetValue(Canvas.LeftProperty, (double)y);
            gameRoot.Children.Add(star);
        }
    }

    public int GetRandInt(int min, int max)
    {
        Byte[] rndBytes = new Byte[10];
        RNGCryptoServiceProvider rndC = new RNGCryptoServiceProvider();
        rndC.GetBytes(rndBytes);
        int seed = BitConverter.ToInt32(rndBytes, 0);
        Random rand = new Random(seed);
        return rand.Next(min, max);
    }
}

VB

Partial Public Class Page
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
        GenerateStarField(350)
    End Sub

    Private Sub GenerateStarField(ByVal numberOfStars As Integer)

        For i As Integer = 0 To numberOfStars - 1

            Dim star As New Ellipse()
            Dim size As Double = GetRandInt(10, 800) * 0.01
            star.Width = size
            star.Height = size
            star.Opacity = GetRandInt(1, 5) * 0.1
            star.Fill = New SolidColorBrush(Colors.White)
            Dim x As Integer = GetRandInt(0, CInt(Math.Round(gameRoot.Height, 0)))
            Dim y As Integer = GetRandInt(0, CInt(Math.Round(gameRoot.Width, 0)))
            star.SetValue(Canvas.TopProperty, CDbl(x))
            star.SetValue(Canvas.LeftProperty, CDbl(y))
            gameRoot.Children.Add(star)
        Next
    End Sub

    Public Function GetRandInt(ByVal min As Integer, ByVal max As Integer) As Integer
        Dim rndBytes As [Byte]() = New [Byte](9) {}
        Dim rndC As New RNGCryptoServiceProvider()
        rndC.GetBytes(rndBytes)
        Dim seed As Integer = BitConverter.ToInt32(rndBytes, 0)
        Dim rand As New Random(seed)
        Return rand.[Next](min, max)
    End Function
End Class

We now have a function called GenerateStarField. Note that it is adding each ellipse to the 'Children' of our base gameRoot canvas, and how the Top and Left properties determine the positions of those ellipses. Now we have a basic layout of our game and a background.

image

Sprites and Vectors

I am not going to go in to great detail on sprites or vectors. There are a number of great resources (see other articles linked to in this post), as well as another good Coding4Fun article on this topic. We are however, going to add a class to represent a sprite and a vector to our code:

C#

public abstract class Sprite
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Vector Velocity { get; set; }
    public Canvas SpriteCanvas { get; set; }
    private Point _position;
    public Point Position
    {
        get
        {
            return _position;
        }
        set
        {
            _position = value;
            SpriteCanvas.SetValue(Canvas.TopProperty, _position.Y - (Height / 2));
            SpriteCanvas.SetValue(Canvas.LeftProperty, _position.X - (Width / 2));
        }
    }

    public Sprite(Double width, Double height, Point position)
    {
        Width = width;
        Height = height;

        SpriteCanvas = RenderSpriteCanvas();

        SpriteCanvas.Width = width;
        SpriteCanvas.Height = height;
        //NOTE: because the setter for Position uses both Height and Width, it is important this comes after they are set.
        Position = position;
    }

    public abstract Canvas RenderSpriteCanvas();

    public Canvas LoadSpriteCanvas(string xamlPath)
    {
        System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream(xamlPath);
        return (Canvas)XamlReader.Load(new System.IO.StreamReader(s).ReadToEnd());
    }

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

VB

Public MustInherit Class Sprite
    Private _Width As Double
    Public Property Width() As Double
        Get
            Return _Width
        End Get
        Set(ByVal value As Double)
            _Width = value
        End Set
    End Property
    Private _Height As Double
    Public Property Height() As Double
        Get
            Return _Height
        End Get
        Set(ByVal value As Double)
            _Height = value
        End Set
    End Property
    Private _Velocity As Vector
    Public Property Velocity() As Vector
        Get
            Return _Velocity
        End Get
        Set(ByVal value As Vector)
            _Velocity = value
        End Set
    End Property
    Private _SpriteCanvas As Canvas
    Public Property SpriteCanvas() As Canvas
        Get
            Return _SpriteCanvas
        End Get
        Set(ByVal value As Canvas)
            _SpriteCanvas = value
        End Set
    End Property
    Private _position As Point
    Public Property Position() As Point
        Get
            Return _position
        End Get
        Set(ByVal value As Point)
            _position = value
            SpriteCanvas.SetValue(Canvas.TopProperty, _position.Y - (Height / 2))
            SpriteCanvas.SetValue(Canvas.LeftProperty, _position.X - (Width / 2))
        End Set
    End Property

    Public Sub New(ByVal initialWidth As [Double], ByVal initialHeight As [Double], ByVal initialPosition As Point)
        Width = initialWidth
        Height = initialHeight

        SpriteCanvas = RenderSpriteCanvas()

        SpriteCanvas.Width = Width
        SpriteCanvas.Height = Height
        'NOTE: because the setter for Position uses both Height and Width, it is important this comes after they are set. 
        Position = initialPosition
    End Sub

    Public MustOverride Function RenderSpriteCanvas() As Canvas

    Public Function LoadSpriteCanvas(ByVal xamlPath As String) As Canvas
        Dim s As System.IO.Stream = Me.[GetType]().Assembly.GetManifestResourceStream(xamlPath)
        Return DirectCast(XamlReader.Load(New System.IO.StreamReader(s).ReadToEnd()), Canvas)
    End Function

    Public Overridable Sub Update(ByVal elapsedTime As TimeSpan)
        Position = (Position + Velocity * elapsedTime.TotalSeconds)
    End Sub
End Class

This Sprite class will give us the basis for entities in the game such as a ship, aliens, and projectiles. For all these items, we need to know the location of the item, the site, and what it looks like. We will take advantage of Point to track our position, and a property of type Canvas to display the XAML for each of these items. The constructor sets these initial parameters, and calls a RenderSpriteCanvas method to be implemented by each class that inherits from it. This method allows the inheriting class to set the contents of the canvas and therefore as control of the way the sprite looks.

Next we have our vector class that will help us control movement of our sprite. Again, I will not go into detail on vectors as this code is borrowed and adapted from many other readily available sources:

C#

public struct Vector
{
    public double X;
    public double Y;

    public Vector(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double Length
    {
        get
        {
            return Math.Sqrt(LengthSquared);
        }
    }

    public double LengthSquared
    {
        get
        {
            return X * X + Y * Y;
        }
    }

    public void Normalize()
    {
        double length = Length;
        X /= length;
        Y /= length;
    }

    public static Vector operator -(Vector vector)
    {
        return new Vector(-vector.X, -vector.Y);
    }

    public static Vector operator *(Vector vector, double scalar)
    {
        return new Vector(scalar * vector.X, scalar * vector.Y);
    }

    public static Point operator +(Point point, Vector vector)
    {
        return new Point(point.X + vector.X, point.Y + vector.Y);
    }

    static 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);
    }

    static public double DegreesToRadians(double degrees)
    {
        double radians = ((degrees / 360) * 2 * Math.PI);
        return radians;
    }
}

VB

Public Structure Vector
    Public X As Double
    Public Y As Double

    Public Sub New(ByVal x__1 As Double, ByVal y__2 As Double)
        X = x__1
        Y = y__2
    End Sub

    Public ReadOnly Property Length() As Double
        Get
            Return Math.Sqrt(LengthSquared)
        End Get
    End Property

    Public ReadOnly Property LengthSquared() As Double
        Get
            Return X * X + Y * Y
        End Get
    End Property

    Public Sub Normalize()
        Dim length__1 As Double = Length
        X /= length__1
        Y /= length__1
    End Sub

    Public Shared Operator -(ByVal vector As Vector) As Vector
        Return New Vector(-vector.X, -vector.Y)
    End Operator

    Public Shared Operator *(ByVal vector As Vector, ByVal scalar As Double) As Vector
        Return New Vector(scalar * vector.X, scalar * vector.Y)
    End Operator

    Public Shared Operator +(ByVal point As Point, ByVal vector As Vector) As Point
        Return New Point(point.X + vector.X, point.Y + vector.Y)
    End Operator


    Public Shared Function CreateVectorFromAngle(ByVal angleInDegrees As Double, ByVal length As Double) As Vector
        Dim x As Double = Math.Sin(DegreesToRadians(180 - angleInDegrees)) * length
        Dim y As Double = Math.Cos(DegreesToRadians(180 - angleInDegrees)) * length
        Return New Vector(x, y)
    End Function

    Public Shared Function DegreesToRadians(ByVal degrees As Double) As Double
        Dim radians As Double = ((degrees / 360) * 2 * Math.PI)
        Return radians
    End Function
End Structure


Now we can implement Sprite with a new Ship class. Add a class called Ship, and a file called Ship.xaml to your project. Be sure to set the properties of Ship.xaml to 'Embedded Resource'. We now need to inherit from our Sprite class:

C#

public class Ship : Sprite
{
    public Ship(double width, double height, Point firstPosition)
        : base(width, height, firstPosition)
    {

    }

    public override Canvas RenderSpriteCanvas()
    {
        return LoadSpriteCanvas("SimpleShooter.Sprites.Ship.xaml");
    }
}

VB

Public Class Ship
    Inherits Sprite
    Public Sub New(ByVal width As Double, ByVal height As Double, ByVal firstPosition As Point)
        MyBase.New(width, height, firstPosition)

    End Sub

    Public Overloads Overrides Function RenderSpriteCanvas() As Canvas
        Return LoadSpriteCanvas("SimpleShooter.Ship.xaml")
    End Function
End Class

When Ship is instantiated, it calls the constructor of it's base, Sprite. It also implements the RenderSpriteCanvas method and specifies the XAML (a simple white square) to load into the sprite's canvas. Now we are ready to add a sprite to our main page. In this simple game, we will have only one ship (the others will be aliens), so lets add a property to our page, and a function that will instantiate our ship:

<Canvas x:Name="LayoutRoot" Width="30" Height="30" 
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Rectangle Height="30" Width="30" Fill="White" />
</Canvas>

C#

void InitializeGame()
{
    PlayerShip = new Ship(10, 10, new Point(100, 300));
    gameRoot.Children.Add(PlayerShip.SpriteCanvas);
}

VB

Private Sub InitializeGame()
    PlayerShip = New Ship(10, 10, New Point(100, 300))
    gameRoot.Children.Add(PlayerShip.SpriteCanvas)
End Sub

We can now call this method from our page constructor and when we run the project, we get a white square (our ship) at the bottom left of our Page. Taking a closer look, we set our page size to 500 x 400, and the InitializeGame method places our Ship at a point 100 pixels from the left of the gameRoot canvas, and 300pixels from the top.

Keyboard Input and the Game Loop

Now we are ready to make some things move. To start, we need choose keys on the keyboard to allow movement, and act on those events. We then need to capture those key presses and act on them if they are relevant to our game loop. Once again, may examples of key handlers and game loops are readily available, so I won't go into detail. The keyboard handler captures all key up and down events. We can therefore ask our instance of the handler if a key is pressed at any given time. The game loop is just that, a constant loop. It consists of a storyboard that has the start called on it and it immediately ends. The class raises an event, and starts the storyboard again. Subscribers to Update event are provided a value that reports the number of milliseconds since the last update. That value can be applied to vectors to apply smooth movement to sprites. To take advantage of these classes, we need to add an instance of both a KeyHandler and GameLoop to our Page. To do this, we update the InitializeGame and Page constructor, and add a handler for the GameLoop:

C#

public Page()
{
    InitializeComponent();
    keyHandler = new KeyHandler(this);
    GenerateStarField(350);
    InitializeGame();
}

void InitializeGame()
{
    gameLoop = new GameLoop(this);
    gameLoop.Update += new GameLoop.UpdateHandler(gameLoop_Update);

    PlayerShip = new Ship(10, 10, new Point(100, 360));
    gameRoot.Children.Add(PlayerShip.SpriteCanvas);

    gameLoop.Start();
}

void gameLoop_Update(TimeSpan elapsed)
{
    //clear the current vector so the sprite is not moving unless a keys is pressed
    PlayerShip.Velocity = new Vector(0, 0);
    if (keyHandler.IsKeyPressed(Key.Left))
    {
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125);
    }
    if (keyHandler.IsKeyPressed(Key.Right))
    {
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125);
    }
    PlayerShip.Update(elapsed);
}

VB

Public Sub New()
    InitializeComponent()
    keyHandler = New KeyHandler(Me)
    GenerateStarField(350)
    InitializeGame()
End Sub

Private Sub InitializeGame()
    gameLoop = New GameLoop(Me)
    AddHandler gameLoop.Update, AddressOf gameLoop_Update

    PlayerShip = New Ship(10, 10, New Point(100, 360))
    gameRoot.Children.Add(PlayerShip.SpriteCanvas)

    gameLoop.Start()
End Sub

Private Sub gameLoop_Update(ByVal elapsed As TimeSpan)
    'clear the current vector so the sprite is not moving unless a keys is pressed 
    PlayerShip.Velocity = New Vector(0, 0)
    If keyHandler.IsKeyPressed(Key.Left) Then
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125)
    End If
    If keyHandler.IsKeyPressed(Key.Right) Then
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125)
    End If

    PlayerShip.Update(elapsed)
End Sub

image

Now we have a functioning game loop. Launch the app, click on the Silverlight control to give it focus, and you can now use the arrow keys to move our ship right and left. One problem however is that you are able to take the ship completely off the screen. To prevent this, we can add MinX and MaxX properties to our ship class and override the Update method it inherits from Sprite. Be sure to also add those min and max values after the Ship is instantiated in InitializeGame of our Page:

C#

public class Ship : Sprite
{
    public double MaxX { get; set; }
    public double MinX { get; set; }

    public Ship(double width, double height, Point firstPosition)
        : base(width, height, firstPosition)
    {

    }

    public override Canvas RenderSpriteCanvas()
    {
        return LoadSpriteCanvas("SimpleShooter.Sprites.Ship.xaml");
    }

    public override void Update(System.TimeSpan elapsedTime)
    {
        //verify that this is a position we can move to
        if (Position.X > MaxX)
        {
            Position = new Point(MaxX, Position.Y);
            Velocity = new Vector(0, 0);
        }
        if (Position.X < MinX)
        {
            Position = new Point(MinX, Position.Y);
            Velocity = new Vector(0, 0);
        }
        base.Update(elapsedTime);
    }
}

VB

Public Class Ship
    Inherits Sprite
    Private _MaxX As Double
    Public Property MaxX() As Double
        Get
            Return _MaxX
        End Get
        Set(ByVal value As Double)
            _MaxX = value
        End Set
    End Property
    Private _MinX As Double
    Public Property MinX() As Double
        Get
            Return _MinX
        End Get
        Set(ByVal value As Double)
            _MinX = value
        End Set
    End Property

    Public Sub New(ByVal width As Double, ByVal height As Double, ByVal firstPosition As Point)
        MyBase.New(width, height, firstPosition)

    End Sub

    Public Overloads Overrides Function RenderSpriteCanvas() As Canvas
        Return LoadSpriteCanvas("SimpleShooter.Ship.xaml")
    End Function

    Public Overloads Overrides Sub Update(ByVal elapsedTime As System.TimeSpan)
        'verify that this is a position we can move to 
        If Position.X > MaxX Then
            Position = New Point(MaxX, Position.Y)
            Velocity = New Vector(0, 0)
        End If
        If Position.X < MinX Then
            Position = New Point(MinX, Position.Y)
            Velocity = New Vector(0, 0)
        End If
        MyBase.Update(elapsedTime)
    End Sub
End Class

Prepare to Fire!

We now have all the basic plumbing to add additional sprites such as aliens or projectiles. To begin, we will add additional classes inheriting from Sprite: Alien, Missle, and Bomb, along with Alien.xaml, Missle.xaml, and Bomb.xaml (these new .xaml files need to be set as Embedded Resources). These xaml files are just like our Ship.xaml with different sizes and colors. Make the Aliens and their bombs red, and shrink the bomb and missile height and width to 5. The xaml is very similar, but the classes themselves are going to have a few differing capabilities. Bomb and Missile are very similar. They only need to load their corresponding xaml. Here is our Bomb class for example:

C#

public class Bomb : Sprite
{
    public double MaxX { get; set; }
    public double MinX { get; set; }

    public Bomb(double width, double height, Point firstPosition)
        : base(width, height, firstPosition)
    {

    }

    public override Canvas RenderSpriteCanvas()
    {
        return LoadSpriteCanvas("SimpleShooter.Sprites.Bomb.xaml");
    }

    public override void Update(System.TimeSpan elapsedTime)
    {
        base.Update(elapsedTime);
    }
}

VB

Public Class Bomb
    Inherits Sprite
    Private _MaxX As Double
    Public Property MaxX() As Double
        Get
            Return _MaxX
        End Get
        Set(ByVal value As Double)
            _MaxX = value
        End Set
    End Property
    Private _MinX As Double
    Public Property MinX() As Double
        Get
            Return _MinX
        End Get
        Set(ByVal value As Double)
            _MinX = value
        End Set
    End Property

    Public Sub New(ByVal width As Double, ByVal height As Double, ByVal firstPosition As Point)
        MyBase.New(width, height, firstPosition)

    End Sub

    Public Overloads Overrides Function RenderSpriteCanvas() As Canvas
        Return LoadSpriteCanvas("SimpleShooter.Sprites.Bomb.xaml")
    End Function

    Public Overloads Overrides Sub Update(ByVal elapsedTime As System.TimeSpan)
        MyBase.Update(elapsedTime)
    End Sub
End Class

We now need to add an Alien class similar to our Ship class. These will both need a bit more capability. For one, we are going to have them both fire at each other. In the case of the Alien, it will be firing down, and it's monition of choice will be a bomb:

C#

public class Alien : Sprite
{
    public double fireRateMilliseconds = 2000;
    public double fireVelocity = 250;
    public double wayPointMin;
    public double wayPointMax;
    public double speed = 100;
    public bool spawnWait;
    public DateTime spawnComplete;
    public double MaxX { get; set; }
    public double MinX { get; set; }

    public Alien(double width, double height, Point firstPosition)
        : base(width, height, firstPosition)
    {

    }

    public void CheckDirection()
    {
        if (Position.X > wayPointMax)
        {
            Velocity = Vector.CreateVectorFromAngle(270, speed);
        }
        if (Position.X < wayPointMin)
        {
            Velocity = Vector.CreateVectorFromAngle(90, speed);
        }
    }

    public override Canvas RenderSpriteCanvas()
    {
        return LoadSpriteCanvas("SimpleShooter.Sprites.Alien.xaml");
    }

    public override void Update(TimeSpan elapsedTime)
    {
        CheckDirection();
        base.Update(elapsedTime);
    }

    public Bomb Fire()
    {
        Bomb bomb = new Bomb(5, 5, Position);
        bomb.Velocity = Vector.CreateVectorFromAngle(180, fireVelocity);
        return bomb;
    }
}

VB

Public Class Alien
    Inherits Sprite
    Public fireRateMilliseconds As Double = 2000
    Public fireVelocity As Double = 250
    Public wayPointMin As Double
    Public wayPointMax As Double
    Public speed As Double = 100
    Public spawnWait As Boolean
    Public spawnComplete As DateTime
    Private _MaxX As Double
    Public Property MaxX() As Double
        Get
            Return _MaxX
        End Get
        Set(ByVal value As Double)
            _MaxX = value
        End Set
    End Property
    Private _MinX As Double
    Public Property MinX() As Double
        Get
            Return _MinX
        End Get
        Set(ByVal value As Double)
            _MinX = value
        End Set
    End Property

    Public Sub New(ByVal width As Double, ByVal height As Double, ByVal firstPosition As Point)
        MyBase.New(width, height, firstPosition)

    End Sub

    Public Sub CheckDirection()
        If Position.X > wayPointMax Then
            Velocity = Vector.CreateVectorFromAngle(270, speed)
        End If
        If Position.X < wayPointMin Then
            Velocity = Vector.CreateVectorFromAngle(90, speed)
        End If
    End Sub

    Public Overloads Overrides Function RenderSpriteCanvas() As Canvas
        Return LoadSpriteCanvas("SimpleShooter.Alien.xaml")
    End Function

    Public Overloads Overrides Sub Update(ByVal elapsedTime As TimeSpan)
        CheckDirection()
        MyBase.Update(elapsedTime)
    End Sub

    Public Function Fire() As Bomb
        Dim bomb As New Bomb(5, 5, Position)
        bomb.Velocity = Vector.CreateVectorFromAngle(180, fireVelocity)
        Return bomb
    End Function
End Class

Now that we are going to add munitions to the game, we need to be able to know when these munitions collide with other sprites. To do this, we are going to add a collision detection method to our sprite class. As a form of simple collision detection, we will give each sprite a CollisionRadius from its center point. To keep it simple we will make the radius one half the width of our sprites. A vector created from these two points can then detect if the sum of those two radius is greater than the length of our vector. If it is, they have collided:

C#

public static bool Collides(Sprite s1, Sprite s2)
{
    Vector v = new Vector((s1.Position.X) - (s2.Position.X), (s1.Position.Y) - (s2.Position.Y));
    if (s1.CollisionRadius + s2.CollisionRadius > v.Length)
    {
        return true;
    }
    else
    {
        return false;
    }
}

VB

Public Shared Function Collides(ByVal s1 As Sprite, ByVal s2 As Sprite) As Boolean
    Dim v As New Vector((s1.Position.X) - (s2.Position.X), (s1.Position.Y) - (s2.Position.Y))
    If s1.CollisionRadius + s2.CollisionRadius > v.Length Then
        Return True
    Else
        Return False
    End If
End Function

We are now ready to extend our game loop of our Page to do more than track the movement of our Ship. We begin by taking action on more than just the arrow keys. We will wire up the space bar for shooting. Lets add an enumeration to track the state of our game. We will also add a supporting class that will handle the firing of munitions and adding those to our game. We will keep this simple for now, but it is an obvious place to being extending this code to take advantage of things like partial classes that could contain functionality like our 'Fire' method:

C#

public enum GameState
{
    Ready = 0,
    Running = 1,
    Paused = 2,
    BetweenWaves = 3,
    GameOver = 4
}

if (keyHandler.IsKeyPressed(Key.Space))
{
    switch (status)
    {
        case GameState.Ready:
            break;
        case GameState.Running:
            EntityFired(PlayerShip);
            break;
        case GameState.Paused:
            break;
        case GameState.BetweenWaves:
            status = GameState.Running;
            ctlInfo.GameInfo = "";
            StartWave();
            break;
        case GameState.GameOver:
            break;
        default:
            break;
    }
}

void EntityFired(Sprite shooter)
{
    Debug.WriteLine(shooter);
    switch (shooter.ToString())
    {
        case "SimpleShooter.Ship":
            if (missles.Count == 0)
            {
                Missle missle = ((Ship)shooter).Fire();
                missles.Add(missle);
                gameRoot.Children.Add(missle.SpriteCanvas);

            }
            break;
        case "SimpleShooter.Alien":
            Bomb bomb = ((Alien)shooter).Fire();
            bombs.Add(bomb);
            gameRoot.Children.Add(bomb.SpriteCanvas);
            break;
        default:
            break;
    }
}

VB

Public Enum GameState
    Ready = 0
    Running = 1
    Paused = 2
    BetweenWaves = 3
    GameOver = 4
End Enum

If keyHandler.IsKeyPressed(Key.Space) Then
    Select Case status
        Case GameState.Ready
            Exit Select
        Case GameState.Running
            EntityFired(PlayerShip)
            Exit Select
        Case GameState.Paused
            Exit Select
        Case GameState.BetweenWaves
            status = GameState.Running
            ctlInfo.GameInfo = ""
            StartWave()
            Exit Select
        Case GameState.GameOver
            Exit Select
        Case Else
            Exit Select
    End Select
End If

Private Sub EntityFired(ByVal shooter As Sprite)
    Debug.WriteLine(shooter)
    Select Case shooter.ToString()
        Case "SimpleShooter.Ship"
            If missles.Count = 0 Then
                Dim missle As Missle = DirectCast(shooter, Ship).Fire()
                missles.Add(missle)

                gameRoot.Children.Add(missle.SpriteCanvas)
            End If
            Exit Select
        Case "SimpleShooter.Alien"
            Dim bomb As Bomb = DirectCast(shooter, Alien).Fire()
            bombs.Add(bomb)
            gameRoot.Children.Add(bomb.SpriteCanvas)
            Exit Select
        Case Else
            Exit Select
    End Select
End Sub

To make it easier to track the state of our game, let's add public properties to our controls and let them be the keepers of those game states. For instance, give the ReamainingLives control a Lives property, and have the setter of that property also update the TextBlock on the control to show the user how many lives are left. Perform a similar task for the other three controls:

C#

public partial class RemainingLives : UserControl
{
    private int _lives;
    public int Lives
    {
        get { return _lives; }
        set
        {
            _lives = value;
            string livesString = string.Empty;
            for (int i = 0; i < _lives - 1; i++)
            {
                livesString = string.Format("{0}{1}", livesString, "A");
            }
            txtRemainingLives.Text = livesString;
        }
    }

    public RemainingLives()
    {
        InitializeComponent();
    }
}

VB

Partial Public Class RemainingLives
    Inherits UserControl
    Private _lives As Integer
    Public Property Lives() As Integer
        Get
            Return _lives
        End Get
        Set(ByVal value As Integer)
            _lives = value
            Dim livesString As String = String.Empty
            For i As Integer = 0 To _lives - 2
                livesString = String.Format("{0}{1}", livesString, "A")
            Next
            txtRemainingLives.Text = livesString
        End Set
    End Property

    Public Sub New()
        InitializeComponent()
    End Sub
End Class

Now we have a number of other things to add to our page class. We are going to have lots of entities to deal with in our game loop. We will have a ship, x number of Aliens, x number of Bombs, and at some points we will have our Missiles, though we restricted that to one at a time in our EntityFired method. We need to, in each game loop check if any missile hits any alien or leaves the game map, if any bomb hits our ship or leaves the game map, and if it is time for a Alien to fire at the ship. Our Page already has a property for our Ship, but the Aliens, Bombs, Missiles need to be collections. When we begin to loop through our Bombs for instance, we will want to remove a bomb if it strikes a Ship or leaves the game map. Since we are going to be iterating these collections, we cannot subtract from those collections as we are looping through them. There are a number of approaches for this, but to keep it simple, we will add a corresponding collection for each of our Bomb, Alien, and Missile collections to track the ones that need to be removed and removed from our game canvas. With this approach, our game loop can add to the "remove these" collection, and then take action on them when we leave the main loop for the parent collection:

C#

List<Alien> aliens;
List<Alien> aliensRemove;
List<Alien> alienShooters;
List<Bomb> bombs;
List<Bomb> bombsRemove;
List<Missle> missles;
List<Missle> misslesRemove;

VB

Private aliens As List(Of Alien)
Private aliensRemove As List(Of Alien)
Private alienShooters As List(Of Alien)
Private bombs As List(Of Bomb)
Private bombsRemove As List(Of Bomb)
Private missles As List(Of Missle)
Private misslesRemove As List(Of Missle)

We also want to add a class to our page that can track the waves of aliens we are going to send one at a time. In addition, we can add a collection to hold these waves. Each wave will track the number of aliens that spawn at once, the total number of aliens faced in the wave, how many aliens get to drop bombs, and how frequently they can fire.

C#

public class WaveData
{
    public WaveData(int count, double fireRate, int atOnce, int fireatonce)
    {
        EnemyCount = count;
        fireRateMilliseconds = fireRate;
        enemiesAtOnce = atOnce;
        fireAtOnce = fireatonce;
        waveEmpty = false;
    }
    public int EnemyCount { get; set; }
    public double fireRateMilliseconds { get; set; }
    public int enemiesAtOnce { get; set; }
    public int fireAtOnce { get; set; }
    public bool waveEmpty { get; set; }
}

VB

Public Class WaveData
    Public Sub New(ByVal count As Integer, ByVal fireRate As Double, ByVal atOnce As Integer, ByVal fireatonce__1 As Integer)
        EnemyCount = count
        fireRateMilliseconds = fireRate
        enemiesAtOnce = atOnce
        fireAtOnce = fireatonce__1
        waveEmpty = False
    End Sub
    Private _EnemyCount As Integer
    Public Property EnemyCount() As Integer
        Get
            Return _EnemyCount
        End Get
        Set(ByVal value As Integer)
            _EnemyCount = value
        End Set
    End Property
    Private _fireRateMilliseconds As Double
    Public Property fireRateMilliseconds() As Double
        Get
            Return _fireRateMilliseconds
        End Get
        Set(ByVal value As Double)
            _fireRateMilliseconds = value
        End Set
    End Property
    Private _enemiesAtOnce As Integer
    Public Property enemiesAtOnce() As Integer
        Get
            Return _enemiesAtOnce
        End Get
        Set(ByVal value As Integer)
            _enemiesAtOnce = value
        End Set
    End Property
    Private _fireAtOnce As Integer
    Public Property fireAtOnce() As Integer
        Get
            Return _fireAtOnce
        End Get
        Set(ByVal value As Integer)
            _fireAtOnce = value
        End Set
    End Property
    Private _waveEmpty As Boolean
    Public Property waveEmpty() As Boolean
        Get
            Return _waveEmpty
        End Get
        Set(ByVal value As Boolean)
            _waveEmpty = value
        End Set
    End Property
End Class

We will need to setup an initialization method for our game, to setup all our collections for sprites, and add progressively difficult waves to our game. The meat of it all now lies in our game loop. For each collection of sprites we call methods that will iterate over its contents and make decisions based on collisions, locations of the sprites. After a loop on each main collection, we can use the 'remove us' collection to clear items from the main collection, remove their canvas from our game canvas, and delete them from the main collection. Finally, we check to see if enough time has elapsed to let the aliens drop another bomb:

C#

void gameLoop_Update(TimeSpan elapsed)
{
    //clear the current Vector so the sprite is not moving unless a keys is pressed
    PlayerShip.Velocity = new Vector(0, 0);
    if (keyHandler.IsKeyPressed(Key.Left))
    {
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125);
    }
    if (keyHandler.IsKeyPressed(Key.Right))
    {
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125);
    }
    if (keyHandler.IsKeyPressed(Key.Space))
    {
        switch (status)
        {
            case GameState.Ready:
                break;
            case GameState.Running:
                EntityFired(PlayerShip);
                break;
            case GameState.Paused:
                break;
            case GameState.BetweenWaves:
                status = GameState.Running;
                ctlInfo.GameInfo = "";
                StartWave();
                break;
            case GameState.GameOver:
                break;
            default:
                break;
        }
    }
    PlayerShip.Update(elapsed);

    BombLoop(elapsed);
    MissleLoop(elapsed);
    AlienLoop(elapsed);

    foreach (Alien alien in aliensRemove)
    {
        aliens.Remove(alien);
        gameRoot.Children.Remove(alien.SpriteCanvas);
        AlienShot(alien);
    }
    aliensRemove.Clear();

    foreach (Missle missle in misslesRemove)
    {
        missles.Remove(missle);
        gameRoot.Children.Remove(missle.SpriteCanvas);
    }
    misslesRemove.Clear();

    if (nextShot <= DateTime.Now)
    {
        nextShot = DateTime.Now.AddMilliseconds(enemyShootMilliseonds).AddMilliseconds(elapsed.Milliseconds * -1);

        shotsThisPass = shotsAtOnce;
        if (shotsThisPass > aliens.Count)
        {
            shotsThisPass = aliens.Count;
        }

        if (aliens.Count > 0)
        {
            foreach (Alien alien in aliens)
            {
                alienShooters.Add(alien);
            }
        }

        while (alienShooters.Count > shotsThisPass)
        {
            alienShooters.RemoveAt(GetRandInt(0, alienShooters.Count - 1));
        }

        foreach (Alien alien in alienShooters)
        {
            EntityFired(alien);
        }

        alienShooters.Clear();
    }
}

VB

Private Sub gameLoop_Update(ByVal elapsed As TimeSpan)
    'clear the current Vector so the sprite is not moving unless a keys is pressed 
    PlayerShip.Velocity = New Vector(0, 0)
    If keyHandler.IsKeyPressed(Key.Left) Then
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125)
    End If
    If keyHandler.IsKeyPressed(Key.Right) Then
        PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125)
    End If
    If keyHandler.IsKeyPressed(Key.Space) Then
        Select Case status
            Case GameState.Ready
                Exit Select
            Case GameState.Running
                EntityFired(PlayerShip)
                Exit Select
            Case GameState.Paused
                Exit Select
            Case GameState.BetweenWaves
                status = GameState.Running
                ctlInfo.GameInfo = ""
                StartWave()
                Exit Select
            Case GameState.GameOver
                Exit Select
            Case Else
                Exit Select
        End Select
    End If
    PlayerShip.Update(elapsed)

    BombLoop(elapsed)
    MissleLoop(elapsed)
    AlienLoop(elapsed)

    For Each alien As Alien In aliensRemove
        aliens.Remove(alien)
        gameRoot.Children.Remove(alien.SpriteCanvas)
        AlienShot(alien)
    Next
    aliensRemove.Clear()

    For Each missle As Missle In misslesRemove
        missles.Remove(missle)
        gameRoot.Children.Remove(missle.SpriteCanvas)
    Next
    misslesRemove.Clear()

    If nextShot <= DateTime.Now Then
        nextShot = DateTime.Now.AddMilliseconds(enemyShootMilliseonds).AddMilliseconds(elapsed.Milliseconds * -1)

        shotsThisPass = shotsAtOnce
        If shotsThisPass > aliens.Count Then
            shotsThisPass = aliens.Count
        End If

        If aliens.Count > 0 Then
            For Each alien As Alien In aliens
                alienShooters.Add(alien)
            Next
        End If

        While alienShooters.Count > shotsThisPass
            alienShooters.RemoveAt(GetRandInt(0, alienShooters.Count - 1))
        End While

        For Each alien As Alien In alienShooters
            EntityFired(alien)
        Next

        alienShooters.Clear()
    End If
End Sub

Now we can almost call it a game:

image

Taking it one step further, I jumped into Expression Blend to create a bit more interesting XAML that our sprites are loading into their SpriteCanvas. This XAML can be found in the full download of the source code. The results make our game just a tad bit more interesting:

image

Conclusion

Silverlight provides a number of capabilities to enable robust game development. It should be noted that in this game, we are only scratching the surface. The core of this example used a game loop to position sprites on a canvas. Additional capabilities within Silverlight such as animations, styling, templating, and visual states provide even more ways to push the limits of browser based games. If you are at all like me, dabbling with game development is a great way to get your feet wet with Silverlight and learn some basics. Happy coding!

If you want to try this out, the download link for the source code is at the top of the article!

About The Author

Roger Guess is the Director of IT for The Wedge Group where he works with technologies such as Silverlight and WPF. He writes games in his spare time, and blogs at SilverlightAddict.com. He can be reached via email at email@rogerguess.net.

Tags:

Follow the Discussion

  • posicionamiento en buscadoresposicionami​ento en buscadores

    Excelente juego! muchas gracias por compartir la forma de crear un videojuego.

    Thanks!!

  • ReihaniReihani

    Thank you for this excelente article.

  • g-forceg-force

    Love this - but when I did the code, xamlReader, CollisionRadius, or RNGCryptoServiceProvider. Am I missing a few namespaces?

  • Clint RutkasClint I'm a "developer"

    @g-force Download the source code to verify what namespaces you're missing.

  • ninja4ninja4

    Sir, I really need help. I cannot get Silverlight to even start up. I click the okay button and it does not do anything. The screen just dissapears. Could it be that I am using Visual C# Express Edition?

    Thanks,

    ninja4

  • Clint RutkasClint I'm a "developer"

    @ninja4 which screen disappears?  You can build Silverlight applications with Express Editions

  • RogerRoger

    @g-force, @ninja4, did you get up and running? Feel free to contact me for assistance.

  • Lee13Lee13

    hey,

    when adding the controls to the page.xaml, I get an error report saying that I am missing an attribute name on SimpleShooter.

    Can anyone help ??

  • LeeLee

    Do you have the xaml code for this ?

  • Clint RutkasClint I'm a "developer"

    @Lee simpleshooter.codeplex.com has all the source code for the game

  • Clint RutkasClint I'm a "developer"

    @Henry You need to get the Silverlight tooling.  www.microsoft.com/.../details.aspx

    A quick way also is to leverage WebPI (www.microsoft.com/.../platform.aspx) and click "Visaul Web Developer Express" and "Silverlight 4 Tools for Visual Studio 2010"

  • HenryHenry

    I failed on the first sentence of the instructions. Is this a records?

    How do I create a Silverlight application in VB2010 Express? Do I create a WPFBrowser application? Or do I need to do something to make a "Silverlight application" selectable in the "new project" window? I will read some of the links posted to get the basics, but this looks like fun! Back to the old days of typing in game code from magazines.....

  • Hi. Where is the download code? I didn't find. Thanks

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.