Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/cache.php on line 36

Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/query.php on line 21

Deprecated: Assigning the return value of new by reference is deprecated in /home/dieajax/public_html/wordpress/wp-includes/theme.php on line 507
10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #5 (C#) : Die, AJAX!

10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #5 (C#)

Welcome bac..

Ahhh, screw it. You know the drill.

This time, we blow $#!* up.

Prerequisites

QuickStart

If you want to see the end result of this tutorial and you have installed all the prerequisites, then please download the ZIP file below, unzip it and open the shootorial.html file in your browser. Now, you can blow up enemy ships and they will also explode when they hit you (however, enemy missles have no effect…yet ;)). Like before, you will need to click on the Silverlight control to give it focus first.

You can also see it in action, here.

Lesson

Step 1: Create the IGameEntityManager interface

In Shootorial 3, we added the IGameEntity interface which represented every game element on the screen. IGameEntity’s Update function took a Canvas object as a parameter. This causes two problems:

  1. It creates a tight coupling between every game entity and the Canvas which would severely hamper unit testing. (I’ll be the first to admit that there are plenty of other tight coupling issues in these shootorials, but this is one of the most glaring :)).
  2. Game entities adding and removing themselves from the Canvas individually leaves no way to intercept this behavior at a common point.

So, to fix this issue, we will declare a IGameEntityManager object that will abstract away the need for direct access to the instance of the Canvas. Open a file named IGameEntityManager.cs and put this code in there:


namespace ShootorialApplication
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Collections.Generic;
 
    public interface IGameEntityManager
    {
        void AddGameEntity(ContentControl control);
        void RemoveGameEntity(ContentControl control);
        void AddEnemy(EnemyShip enemyShip);
        void RemoveEnemy(EnemyShip enemyShip);
        Ship HeroShip { get; }
        IEnumerable<EnemyShip> Enemies { get; }
        bool DetectCollision(ContentControl controlOne, ContentControl controlTwo);
    }    
}

To fully abstract away access to the canvas, the IGameEntity also needs updating:


    public interface IGameEntity
    {
        //Called once per frame
        void Update( IGameEntityManager theManager );
        void Move( Direction direction );
    }  

So, instead of requiring an instance of Canvas, the Update function now uses an instance of IGameEntityManager.

Rather than tell you what these new functions do, I will show you.

Step 2: Implement the IGameEntityManager interface

Instead of creating a new GameEntityManager class to implement the IGameEntityManager interface, we will reuse the the ShootorialControl class:


namespace ShootorialApplication
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows.Shapes;
    using System.Collections.Generic;
 
    public partial class ShootorialControl : UserControl, IGameEntityManager
...
}

Now, to implement the functions. First, the AddGameEntity and RemoveGameEntity functions:


    public partial class ShootorialControl : UserControl, IGameEntityManager
    {
        ...
        public void AddGameEntity(ContentControl control)
        {
            theCanvas.Children.Add(control);
        }
 
        public void RemoveGameEntity(ContentControl control)
        {
            theCanvas.Children.Remove(control);
        }
        ...
    }

These functions delegate to Canvas.Children.Add and Canvas.Children.Remove functions, respectively. This prevents unecessary coupling between IGameEntity and the Canvas object.

Next, the AddEnemy and RemoveEnemy functions:


    public partial class ShootorialControl : UserControl, IGameEntityManager
    {
        private IList<EnemyShip> enemies = new List<EnemyShip>();
        ...
        public void AddEnemy(EnemyShip enemyShip)
        {
            enemies.Add(enemyShip);
            this.AddGameEntity(enemyShip);
        }
 
        public void RemoveEnemy(EnemyShip enemyShip)
        {
            enemies.Remove(enemyShip);
            this.RemoveGameEntity(enemyShip);
        }
        ...
    }

In addition to adding new game elements to the canvas via the AddGameEntity and RemoveGameEntity functions, these functions also add to a list of enemy objects tracked in the highlighted “enemies” variable. This will come in handy later to test whether or not the hero ship’s bullets have hit their target.

Speaking of which, this class exposes both the hero ship and the of currently displaying enemies through these properties:


    public partial class ShootorialControl : UserControl, IGameEntityManager
    {
        ...
        public Ship HeroShip
        {
            get
            {
                return spaceShip;
            }
        }
 
        public IEnumerable<EnemyShip> Enemies
        {
            get
            {
                return enemies;
            }
         }
        ...
    }

Finally, to determine whether or not bullets (or ships) have connected, we will need to add some sort of “collision detection”. In the world of video game programming, collision detection encompasses a body of techniques used to see whether two game elements have “hit” each other by trying to occupy the same space. Since we use a ContentControl to describe our game elements, we simply need to figure out when two content controls overlap. The IGameEntityManager class works across all game elements, so it will contain this functionality in the DetectCollision function:


    public partial class ShootorialControl : UserControl, IGameEntityManager
    {
        ...
        public bool DetectCollision( ContentControl controlOne, ContentControl controlTwo )
        {
           Rect controlOneRect =    new Rect(    new Point(     Convert.ToDouble(controlOne.GetValue(Canvas.LeftProperty)),
                                        Convert.ToDouble(controlOne.GetValue(Canvas.TopProperty))
                                ),
                                new Point(    (Convert.ToDouble(controlOne.GetValue(Canvas.LeftProperty)) + controlOne.ActualWidth),
                                        (Convert.ToDouble(controlOne.GetValue(Canvas.TopProperty)) + controlOne.ActualHeight)
                                )
                        );
 
            Rect controlTwoRect =    new Rect(    new Point(     Convert.ToDouble(controlTwo.GetValue(Canvas.LeftProperty)),
                                        Convert.ToDouble(controlTwo.GetValue(Canvas.TopProperty))
                                ),
                                new Point(    (Convert.ToDouble(controlTwo.GetValue(Canvas.LeftProperty)) + controlTwo.ActualWidth),
                                        (Convert.ToDouble(controlTwo.GetValue(Canvas.TopProperty)) + controlTwo.ActualHeight)
                                )
                        );
 
            controlOneRect.Intersect(controlTwoRect);
 
            return !(controlOneRect == Rect.Empty);
        }
        ...
    }

Lots of code, but still very simple. Each Rect structure declaration represents a rectangle, called the “bounding rectangle” in video game programmer speak, that fully encompasses the corresponding ContentControl. For example, even though the outline of the hero ship contains many bumps and grooves, if you drew the smallest rectangle around it such that the rectangle didn’t cross into any part of the ship image, you would have its “bounding rectangle”. So, testing to see if two these ContentControls overlap, makes for a very simple (but somewhat inaccurate) method of collision detection. The call to Rect.Intersect determines if the two rectangles representing “controlOne” and “controlTwo” overlap and if so, it puts the resulting rectangle (more than likely very small) into the “controlOneRect” variable. If they do not overlap, then “controlOneRect” will equal the Rect.Empty value. See the Rect structure documentation for more information.

As mentioned in Step 1, we want to limit access to the visual Canvas as much as possible, so let’s update the two functions which work on the canvas reference directly with ones that use the IGameEntityManager instead. First, ShootorialControl_KeyDown function:


        private void ShootorialControl_KeyDown(object sender, KeyEventArgs e)
        {
            if( e.Key == Key.Right )
                spaceShip.Move( Direction.Right );
            else if( e.Key == Key.Left )
                spaceShip.Move( Direction.Left );
            else if( e.Key == Key.Up )
                spaceShip.Move( Direction.Up );
            else if( e.Key == Key.Down )
                spaceShip.Move( Direction.Down );
            else if( e.Key == Key.Space && shootLimiter == 8 )
            {
                shootLimiter = 0;
                spaceShip.Fire(this);
            }
        }

And now, the ShootorialControl_Rendering function:


        private void ShootorialControl_Rendering(object sender, EventArgs e)
        {
            if( shootLimiter < 8 )
                shootLimiter += 1;
 
            enemyTimer += 1;
 
            if( enemyTimer > 60 )
            {
                enemyTimer = 0;
 
                this.AddEnemy(new EnemyShip());
            }
 
            for( int elementIndex = 0; elementIndex < theCanvas.Children.Count; elementIndex++ )
            {
                IGameEntity gameObject = theCanvas.Children[elementIndex] as IGameEntity;
 
                if( gameObject != null )
                {
                     gameObject.Update(this);
                }
            }
        }

That should do it. Now, let’s put some of these new functions to work.

Step 3: Use the IGameEntityManager interface

First, we’ll update the ScrollingBackround class in ScrollingBackground.cs:


    public class ScrollingBackground : ContentControl, IGameEntity
    {
        ...
        public void Update( IGameEntityManager theManager )
        {
            Move(Direction.Left);
        }
        ...
    }

Pretty simple. Remember, in Step 1 we updated the IGameEntity which the ScrollingBackground class implements, so it needed updating here. Next, the Ship class:


    public class Ship : ContentControl, IGameEntity
    {
        ...
        public void Update( IGameEntityManager theManager )
        {
        }
 
        public void Fire(IGameEntityManager theManager)
        {
            Missile missile = new Missile(Canvas.GetLeft(this) + 57, Canvas.GetTop(this) + 17);
 
            theManager.AddGameEntity(missle);
        }
        ...
}

Again, very simple. Note, the call to AddGameEntity replaces the previous call to Canvas.Children.Add.

We will update the rest of the classes while adding collision detection logic at the same time. But before that happens, we need to add two new classes.

Step 4: Create the Explosion class

Kongregate’s fifth tutorial adds functionality to make enemy ships explode and enemies shoot, so we will do the same. First, the explosions: create a file named Explosion.cs and put this code in it:


namespace ShootorialApplication
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Controls;
    using System.Windows.Shapes;
 
    public class Explosion : ContentControl, IGameEntity
    {
        private static BitmapImage[] explosionImages = new BitmapImage[] {
            new BitmapImage(new Uri("/explosion1.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion2.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion3.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion4.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion5.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion6.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion7.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion8.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion9.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion10.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion11.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion12.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion13.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion14.png", UriKind.RelativeOrAbsolute)),
            new BitmapImage(new Uri("/explosion15.png", UriKind.RelativeOrAbsolute))
        };
 
        private int curExplosionImage = 0;
        private Image explosionImage = new Image();
 
        public Explosion(double left, double top)
        {
            explosionImage.Source = explosionImages[curExplosionImage];
 
            this.Content = explosionImage;
 
            Canvas.SetLeft(this, left);
            Canvas.SetTop(this, top);
        }
 
        public void Update(IGameEntityManager theManager)
        {
            if( curExplosionImage == explosionImages.Length )
            {
                theManager.RemoveGameEntity(this);
            }
            else
            {
                explosionImage.Source = explosionImages[curExplosionImage];
                curExplosionImage++;
            }
        }
 
        public void Move(Direction direction)
        {
        }
    }
}

The static array of BitmapImages contains each “frame” of an animated explosion. Think of a “frame” as a specific image displayed at a specific point in time. Displaying many frames rapidly simulates animation, in this case the animation of an exploding ship.

So, the the constructor displays the first frame of the explosion and positions it at the point represented by the “left” and “top” parameters. The Update function first checks to see if we’ve reached the end of the animation. If so, it removes the explosion, making the ship officially “dead”. If not, it displays the next frame of the explosion.

This class will come in handy, later. Now, let’s add enemy missiles.

Step 4: Create the EnemyMissile class

Similar to Kongregate’s tutorial we will create a class to represent enemy missiles. Create a file named EnemyMissle.cs and put this code in it:


namespace ShootorialApplication
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows.Shapes;
 
    public class EnemyMissile : ContentControl, IGameEntity
    {
        private int speed = 20;
 
        public EnemyMissile(double left, double top)
        {
            Rectangle missleRectangle = new Rectangle();
 
            missleRectangle.Height = 7;
            missleRectangle.Width = 15;
            missleRectangle.Fill = new SolidColorBrush(Colors.Red);
            missleRectangle.Stroke = new SolidColorBrush(Colors.Black);
            missleRectangle.RadiusX = 3;
            missleRectangle.RadiusY = 3;
            missleRectangle.StrokeThickness = 2;
 
            this.Content = missleRectangle;
 
            Canvas.SetLeft(this, left);
            Canvas.SetTop(this, top);
        }
 
        public void Update( IGameEntityManager theManager )
        {
            Move(Direction.Left);
 
            if( theManager.DetectCollision( this, theManager.HeroShip ))
            {
                theManager.RemoveGameEntity(this);
            }

            
            if( Canvas.GetLeft(this) < 0)
            {
                theManager.RemoveGameEntity(this);
            }
        }
 
        public void Move( Direction direction )
        {
            Canvas.SetLeft(this, Canvas.GetLeft(this) - speed);
        }
    }
}

If this class looks similar to the Missile class, it should because I copied the code ;). However, I also highlighted some of the major differences. First, as defined in the constructor, enemy missiles will appear red instead of white. Second, in the Update function we get our first look at collision detection logic. The call to DetectCollision simply asks whether or not this missile has hit the hero ship. If so, it asks IGameEntityManager to remove it from display via the RemoveGameEntity function. Finally, the Move function subtracts the “speed” variable from the current position, meaning that missiles fired from enemies will fly to the left.

Good, now let’s go through the rest of the classes.

Step 5: Update the EnemyShip class

The EnemyShip class requires lots of updating, so let’s take it one at a time. First, since ships can now explode, let’s add an Explode function:


    public class EnemyShip : ContentControl, IGameEntity
    {
        ...
        private bool exploded = false;
        ...
        public void Explode()
        {
            exploded = true;
        }
    }

This function merely sets the “exploded” variable which tracks the state of whether or not the enemy ship has exploded. Now, let’s make the enemy ship explode when it collides with the hero ship, by changing the Update function:


    public class EnemyShip : ContentControl, IGameEntity
    {
        ...
        public void Update( IGameEntityManager theManager )
        {
            if( theManager.DetectCollision( this, theManager.HeroShip ))
            {
                this.Explode();
            }
 
            if( exploded )
            {
                theManager.RemoveEnemy(this);
                theManager.AddGameEntity(new Explosion( Canvas.GetLeft(this), Canvas.GetTop(this) ));
            }
            else
            {
                Move(Direction.Left);
    
                if( Canvas.GetLeft(this) < -100 )
                {
                    theManager.RemoveEnemy(this);
                }
            }
        }
        ...
    }

So, the Update function first checks for a collision between this enemy ship and the hero ship. If so, it calls explode. Next, if the enemy ship has exploded, it replaces itself with an Explosion. Note that it passes its position into the Explosion class’ constructor, so they both appear in the same spot.

Finally, let’s make the enemy ship shoot missiles:


    public class EnemyShip : ContentControl, IGameEntity
    {
        ...
        private int shootTimer = 0;
      
        public void Update( IGameEntityManager theManager )
        {
            if( theManager.DetectCollision( this, theManager.HeroShip ))
            {
                this.Explode();
            }
 
            if( exploded )
            {
                theManager.RemoveEnemy(this);
                theManager.AddGameEntity(new Explosion( Canvas.GetLeft(this), Canvas.GetTop(this) ));
            }
            else
            {
                Move(Direction.Left);
    
                if( Canvas.GetLeft(this) < -100 )
                {
                    theManager.RemoveEnemy(this);
                }
                else
                {
                    shootTimer +=1;
                
                    if(shootTimer > 30)
                    {
                        shootTimer = 0;
                        theManager.AddGameEntity( new EnemyMissile( Canvas.GetLeft(this) - 25, Canvas.GetTop(this) + 2 ));
                    }
                }

            }
        }
        ...
    }

Similar to the hero ship, this class has a “shootTimer” variable that determines when enemy ships fire. If this variable goes above 30, the enemy ship “shoots” a missile by creating a new EnemyMissile class and telling IGameEntityManager to add it.

Last but not least, let’s update the Missile class.

Step 6: Update the Missile class

    public class Missle : ContentControl, IGameEntity
    {
        ...
        public void Update( IGameEntityManager theManager )
        {
            if( Canvas.GetLeft(this) > 640 )
            {
                theManager.RemoveGameEntity(this);
            }
            else
            {
                foreach( EnemyShip enemy in theManager.Enemies )
                {
                    if( theManager.DetectCollision( enemy, this ))
                    {
                        enemy.Explode();
                        theManager.RemoveGameEntity(this);
                    }
                }
            }
 
            Move(Direction.Right);
        }
        ...
    }
}

Recall that in Step 2 we added the Enemies property to the IGameEntityManager class which would hold the list of currently displaying enemies. In its Update function, the missile loops through each enemy ship and checks for a collision. If it has collided with an enemy ship, it tells it to explode, and then removes itself from display.

Whew, glad that’s over. Now, let’s build it.

Step 7: Build it

Even after adding a few new classes and several new images, the build file should remain pretty self explanatory:


<ItemGroup>
  <Compile Include="IGameEntity.cs">
  </Compile>
  <Compile Include="Missile.cs">
  </Compile>
  <Compile Include="Ship.cs">
  </Compile>
  <Compile Include="ScrollingBackground.cs">
  </Compile>
  <Compile Include="EnemyShip.cs">
  </Compile>
  <Compile Include="IGameEntityManager.cs">
  </Compile>
  <Compile Include="EnemyMissile.cs">
  </Compile>
  <Compile Include="Explosion.cs">
  </Compile>

</ItemGroup>
 
...

<ItemGroup>
   <None Include="explosion1.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion2.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion3.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion4.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion5.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion6.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion7.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion8.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion9.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion10.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion11.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion12.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion13.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion14.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
   <None Include="explosion15.png">
     <CopyToOutputDirectory>Always</CopyToOutputDirectory>
   </None>
</ItemGroup>

Of course, the build command still hasn’t changed:


"C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" ShootorialApplication.csproj
Step 7: Run it!

To run it, simply open the shootorial.html file in your browser. Now, you can blow up enemy ships and they will also explode when they hit you. Remember, to control the ship, you need to give Silverlight focus by clicking on the Silverlight control first (i.e just click on the ship).

Conclusion

Yet another big article, and, by the looks of it, they won’t get any shorter for a while. But, one should expect that when talking about concepts like collision detection. Keep in mind that simple collision detection between rectangles barely scratches the surface of what most games today need, so I encourage you to do more reading on the subject. Next time, we will make the hero ship take damage, add scoring and add a health meter, so stay tuned!

Share and Enjoy:
These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • StumbleUpon
  • Reddit
  • del.icio.us
Related Posts:
10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #4 (C#)
10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #2 (C#)
10 Minute Silverlight Game Programming Tutorial - Shootorial Conversion #3 (C#)
10 Minute Game Programming Tutorial: Silverlight - Shootorial #1 Conversion (C#)

Comments

Comments are closed.