Building a Full Featured Mobile Camera Application: C4FCamera

This
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.
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.
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="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://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.
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="https://schemas.microsoft.com/client/2007" xmlns:x="https://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.
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
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
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:
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:
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!
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.
Excelente juego! muchas gracias por compartir la forma de crear un videojuego.
Thanks!!
Thank you for this excelente article.
Love this - but when I did the code, xamlReader, CollisionRadius, or RNGCryptoServiceProvider. Am I missing a few namespaces?
@g-force Download the source code to verify what namespaces you're missing.
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
@ninja4 which screen disappears? You can build Silverlight applications with Express Editions
@g-force, @ninja4, did you get up and running? Feel free to contact me for assistance.
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 ??
Do you have the xaml code for this ?
@Lee simpleshooter.codeplex.com has all the source code for the game
@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"
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