Click here to Skip to main content
15,885,195 members
Articles / Desktop Programming / WPF

Shoot'em Up .NET

Rate me:
Please Sign up or sign in to vote.
4.99/5 (73 votes)
6 Nov 2013CPOL25 min read 74.6K   108   34
This is a small tutorial on how to create a Shoot'em Up game in .NET using mostly declarative animations.

Before Introduction

If you only want to download the code and see what's happening, then the important things are:

  • There's a solution for Silverlight 5 and one for WPF, but most units are simply used by both;
  • The game uses the directional keys to move, Space to shot and either Esc, P or Enter to pause;
  • The game has three levels. Between the levels you may acquire upgrades to your ships, so if you think the initial ships are too weak, well, that's on purpose.

You can play the Silverlight game by opening this link: http://paulozemek.azurewebsites.net/ShootEmUpTestPage.2013_10_10.html

Or you can download the WPF version by clicking on the next image:

Image 1

Introduction

I know that many programmers would love to write games. I myself started to program computers because I wanted to create games and, even if I already wrote some games, I've spent most of my time dealing with systems and solving problems related to database connectivity and better caching mechanisms.

One of the problems I see to write games is the lack of material. In my particular case, I usually look for graphics and sounds, as those are the things that I am not able to do. Yet, I think there's missing material for games in general. It is hard to find game tutorials and, when I find something, I only see material to write games using XNA or Unity. There is almost no material on how to write games using WPF or Silverlight or other similar technologies.

And, considering that I usually write games using those technologies, I though it will be a good idea to present a solution on how to write games using non-game specific technologies.

Why not use XNA or Unity?

My idea on writing games without using XNA or Unity is to show that you can use normal UI technologies to write games, which in many cases simplify the job as you don't need to care about how to render things to the screen or to recreate layout controls. Also, by doing this you will be able to create games without having to install such libraries/engines and you will be able to write games even for Windows 8, as using C# + XAML (which is what I am going to use in this article) is a trait of Silverlight, WPF and of Windows 8 applications.

Declarative Animations

Most articles about games say that we must deal with a "game loop", moving objects (like the player ship and enemies) at each frame or time lapse. That's said even in articles that aren't specific for XNA or Unity and that's really how most games are written, yet it is not how it must be.

Seeing the Storyboard used by both WPF and Silverlight it is possible to see that animations can be declarative and a game is mostly made of animations. So, why should we care about a game loop?

The most compelling reason may be interaction. Declarative animations aren't interactive. They update values and the only input they have is time. Well, at least that's what happens with WPF and Silverlight's Storyboards, but that doesn't mean you can't have declarative animations that can react to some external factors.

That, and some other reasons, made me write a library dedicated to animations recently, which I presented here. In such library, the declarative animations are still written in C#, not in the XAML, using a Fluent API that's very similar to common declarative code, yet it allows the use of delegates to build part of the animations or to validate some conditions, which is all we need to move a character when the user press some keys.

With such library, it is possible to write the following code to control the player character movements:

C#
AnimationBuilder.
BeginParallel().
  BeginLoop().
    BeginPrematureEndCondition(() => HorizontalMove >= 0).
      RangeBySpeed(() => Canvas.GetLeft(this), 0, 120, (value) => Canvas.SetLeft(this, value)). 
    EndPrematureEndCondition().
  EndLoop().
  BeginLoop().
    BeginPrematureEndCondition(() => HorizontalMove <= 0).
      RangeBySpeed(() => Canvas.GetLeft(this), canvas.Width-Width, 120, (value) => Canvas.SetLeft(this, value)). 
    EndPrematureEndCondition().
  EndLoop().
  BeginLoop().
    BeginPrematureEndCondition(() => VerticalMove >= 0).
      RangeBySpeed(() => Canvas.GetTop(this), 0, 120, setTop). 
    EndPrematureEndCondition().
  EndLoop().
  BeginLoop().
    BeginPrematureEndCondition(() => VerticalMove <= 0).
      RangeBySpeed(() => Canvas.GetTop(this), canvas.Height-Height, 120, setTop). 
    EndPrematureEndCondition().
  EndLoop().
EndParallel();

And, the best of all, such library does not change the kind of application you write. You can continue to create a WPF application, a Silverlight application, a Windows Forms application, a Console application or whatever you want, even a XNA application. Everything that's really needed by such library is an "event" that's triggered fast enough to be able to play the animations (in fact, you can even fake such event when calling the animations if, for example, you want to write a video file with a "consistent timing").

So, if you are worried that you should use an external library to write the presented game, don't be. It is a library, true, but it can be added to any application and, as you will receive the source code, you can add only the parts that you use to your applications and games.

Before writing some code

This article will explain important steps to write a game. Yet, it is not enough to simply copy those code blocks to have a game. In fact, to start you will need to create a new project (Silverlight or WPF) and add a reference to the animation library (be it to the dll or to its source code). Also, some images, sounds and other files aren't presented as part of the article itself but you will be able to verify that all steps presented here are really in use by downloading the final application.

1. The stars

I already said that I usually look for graphics as I am not a designer and a game needs a good design. Sometimes, by simply changing a badly written character by a better looking one the game seems to be more real and interesting.

Originally I was unsure if I would write a space Shoot'em Up game or a platform one, but as I didn't find good looking backgrounds, I decided to go to the Shoot'em Up game as I am capable of creating its background. In fact, I started writing the background because I wanted to check if it will be acceptable or not.

To have a background I only needed stars, so I immediately created a Star user control. It's only content is an Ellipse, which has its real size defined by code. In its code, the Intensity determines its color (maybe all white was OK, but I wanted some small differences) and how fast it will go down. To create the illusion that the ship is always moving forward, what really happens is that only the stars move towards the bottom.

The initial Star animation was a simple Range from its actual position to the off screen bottom and, by creating some stars at random positions I had something that looked like snow, as the stars were going off-screen and no new stars were appearing.

I thought about many possible solutions to make new stars appear but I opted to fake it. Each time a Star goes off-screen it is repositioned on the top (also off-screen) and a new horizontal position and Intensity is calculated. So, I can simply restart the animation (that is, put it inside a Loop) and it looks like new stars are appearing all the time. And, as a new position, size and even a small off-screen value to the top is also calculated you don't keep seeing a star that's disappearing from the bottom immediately reapearing on the top.

The final code is this:

C#
public IEnumerable<IAnimation> CreateAnimation()
{
  var canvas = MainPage._instance.canvas;
  int width = (int)canvas.Width;

  canvas.Children.Add(this);

  while(true)
  {
    yield return 
      AnimationBuilder.
      RangeBySpeed
      (
        (int)Canvas.GetTop(this),
        canvas.Height,
        (_intensity + 1),
        (value) => Canvas.SetTop(this, value)
      );

    int x = MainPage._random.Next(width);
    int y = -MainPage._random.Next(30);
    int intensity = MainPage._random.Next(MaxIntensity + 1);

    Canvas.SetLeft(this, x);
    Canvas.SetTop(this, y);
    Intensity = intensity;
  }

If you want, you can see a working version of it at: http://paulozemek.azurewebsites.net/ShootEmUp/StarsSilverlight.html

2. The player character

Image 2

To move the character I used RangeBySpeed animations that are enclosed by PrematureEndConditions. That is, I have a RangeBySpeed that moves the character to the right, but that ends prematurely if the right arrow is not pressed.

I used a RangeBySpeed instead of a normal Range because a character at the center of the screen will arrive the right limit faster than a character that is located at the extreme left of the screen. If I was using a normal Range then the character will always take the same time to arrive to the right corner, which will represent as if it was moving at different speeds.

The big trick here was to put each one of the possible moves (left, right, up, down) ranges inside their own conditions, which are inside their own loops, so they can reexecute if you hold a key down, then stop, then press the key again. And finally, all those animations are inside a ParallelGroup animation (well, at least horizontal and vertical movements are allowed to run in parallel).

The only missing thing was how to tell a PrematureEndCondition if it should end or not. At least in Silverlight we can't simply check if a key is pressed or not. To solve that, I implemented the KeyDown and KeyUp events to set the HorizontalMove and verticalMove, and the conditions only check those variables. Note that if we were writing the game for other frameworks (even for WPF) we may be able to check if a key is down without having to implement the KeyDown and KeyUp events.

I already shown the code to animate the character, so here I will present the KeyUp and KeyDown event handlers:

C#
void _KeyDown(object sender, KeyEventArgs e)
{
  switch(e.Key)
  {
    case Key.Left:
      HorizontalMove = -1;
      break;

    case Key.Right:
      HorizontalMove = 1;
      break;

    case Key.Up:
      VerticalMove = -1;
      break;

    case Key.Down:
      VerticalMove = 1;
      break;
  }
}
void _KeyUp(object sender, KeyEventArgs e)
{
  switch(e.Key)
  {
    case Key.Left:
      if (HorizontalMove == -1)
        HorizontalMove = 0;

      break;

    case Key.Right:
      if (HorizontalMove == 1)
        HorizontalMove = 0;

      break;

    case Key.Up:
      if (VerticalMove == -1)
        VerticalMove = 0;

      break;

    case Key.Down:
      if (VerticalMove == 1)
        VerticalMove = 0;

      break;
  }
}

And you can see a working version of it at http://paulozemek.azurewebsites.net/ShootEmUp/StarsAndPlayerSilverlight.html.

Note: You may need to click on the Silverlight window to be able to control the ship using the directional arrows.

3. The enemies

Image 3 Image 4 Image 5

How can I say that a game is a Shoot'em Up if there's nothing to shoot?

Again I started by creating a class, named Enemy. Maybe for a really versatible game I should create it as an abstract class and then implement each different enemy in its own class, but for this game, a single class was enough.

At the first moment, the Enemy class had a fixed ship image and its animation was a simple Range to go from the off-screen top to the off-screen bottom. Then, there's another animation that keeps creating new enemies from time to time.

The code is like this:

C#
return
  AnimationBuilder.
  BeginSequence().
    Add(() => canvasChildren.Add(this)).
    BeginPrematureEndCondition(() => Health <= 0).
      RangeBySpeed(-Height, MainPage._instance.canvas.Height, (30 + MainPage._random.Next(90)) * speed, (value) => Canvas.SetTop(this, value)). 
    EndPrematureEndCondition().
    ExplodeIfDead(this, 1).
    Add(() => canvasChildren.Remove(this)).
  EndSequence();

4. The rectangle collisions + segmented time

At this moment we have a player character that moves, new enemies that appear on the screen... but the ships never collide.

To do a basic collision detection, we deal with rectangles. We consider the Canvas.Left and Canvas.Top of the characters and their Width and Height to build the rectangles, and then we check if there's an intersection using the method Intersect of the Rect type.

Seeing this image, we can easily see the intersection between the red and the green rectangles, with I am representing with the yellow rectangle.

Image 6

But, when do we check for the collision?

The game is using declarative animations, which don't have a right moment to do the collision detection. You can easily put an imperative animation and check for collisions everytime it is invoked, but this will create an imprecise collision detection, as the Updates may be invoked with different time lapses.

The solution is the use of a segmented time (be it with the BeginSegmentedTime/EndSegmentedTime or by instantiating a SegmentedType directly). Such segmented time guarantees that it will update its inner animations at maximum intervals determined by its SegmentDuration. Note that animations will stay smooth, as they will be updated in smaller intervals if the time lapses are short, yet the SegmentCompleted will only be invoked when a segment is completed (that is, if the segment is of 15 milliseconds, an Update of 10 milliseconds will not generate a segment end, but a new Update of 5 will complete those 15 milliseconds and generate the completed event).

If, for example, the time lapse defined is of 15 miliseconds and there are two updates, both of ten miliseconds, what will really happen is:

  1. The first update will update the inner animation with those 10 miliseconds;
  2. The second update will be split in 2 steps. The first step will update only 5 miliseconds, to complete the 15 miliseconds to invoke the event, then it will invoke the SegmentCompleted event. And then finally it will do an extra update of 5 miliseconds to put all the objects at the right places.

The most important thing here is to put all the animations that participate on the collision detection as inner animations of this segmented time, as external animations will not be segmented and so a slowdown may make the external animation advance 500 milliseconds directly instead of segmenting it by 15 milliseconds before each collision detection.

So, the code to do the basic collision detection can look like this:

C#
internal static void _CheckAllCollisions()
{
  var objects = _instance.canvas.Children;

  PlayerCharacter._instance.IsReceivingDamage = false;
  foreach(var enemy in objects.OfType<Enemy>())
  {
    if (PlayerCharacter._instance.Health > 0)
    {
      if (enemy.CollidesWith((WriteableBitmap)enemy.image.Source, PlayerCharacter._instance, (WriteableBitmap)PlayerCharacter._instance.image.Source))
      {
        PlayerCharacter._instance.Health --;
        enemy.Health --;
        PlayerCharacter._instance.IsReceivingDamage = true;
      }
    }
  }
}

5. Explosions

The explosion may look very simple as we are already detecting collisions and we already have lots of animations. Yet, there's something very different here.

Up to this moment, all animations are simply composed of movement as we don't have different frames. Considering I am not simulating a explosion by code, but using an already drawn animation, I must be able to load and play it.

Searching the internet I found some explosion sprite sheets. So, at this moment, I must be able to use a sprite sheet.

A sprite sheet is usually composed of many equally sized images (well, at least that's the most common case if we have a single animation in the sprite sheet). One of the sprite sheets I found is this:

Image 7

And what we need to do is to show only one of the "sprites" at once. To do this, I created the SpriteSheet user control. Its main purpose is simply to load a sprite sheet image but present only one frame at a time.

To do that we must apply a RectangleGeometry to the Clip property of the UserControl. The content of the control is a canvas with an image and so, each time I that must show a different frame, I change the Canvas.Left and/or the Canvas.Top of the image (to show the second frame, we must set the Canvas.Left to minus the frame width, so the first frame is invisible at the left and the second frame becomes visible).

Well, I did something more. The SpriteSheet control is capable of presenting the frames with a different size. To do this, I multiply the entire image size by the size I want to show the frame, then I divide the the size by the real frame size. With the entire image stretched I must also use the modified size when doing the Left/Top calculations, so the method that really "repositions the frame" ended up like this:

C#
private void _Invalidate()
{
  var source = _bitmapSource;
  if (source == null)
    return;

  if (_bitmapSource.PixelWidth <= 0)
    return;

  if (double.IsNaN(Width) || double.IsNaN(Height))
    return;

  int itemsPerRow = _bitmapSource.PixelWidth / _frameWidth;
  int column = _frameIndex % itemsPerRow;
  int row = _frameIndex / itemsPerRow;

  Canvas.SetLeft(_image, Width * -column);
  Canvas.SetTop(_image, Height * -row);

  _image.Width = _bitmapSource.PixelWidth * Width / _frameWidth;
  _image.Height = _bitmapSource.PixelHeight * Height / _frameHeight;

  _clip.Rect = new Rect(0, 0, Width, Height);
}

Finally, considering that I created the FrameIndex property, to animate the explosion it is enough to do a Range from the first frame index to the last frame index in a time that we consider acceptable (I used 1 second in most cases).

Of course, I have to create such spritesheet component for the explosion, position it at the same place of a exploding ship, add it to the canvas were all characters are already added, play it and then remove it from the canvas. So, the explosion animation is like this:

C#
var explosion = _SpriteSheetImages.CreateExplosion((int)enemy.Width);

// And inside the animation:
Add
(
  () => 
  {
    Canvas.SetLeft(explosion, Canvas.GetLeft(enemy));
    Canvas.SetTop(explosion, Canvas.GetTop(enemy));
    Canvas.SetZIndex(explosion, Canvas.GetZIndex(enemy) + 1);
    canvasChildren.Add(explosion);
  }
).
BeginParallel().
  Range(0, explosion.FrameCount, time, (value) => explosion.FrameIndex = value).
  Range(1.0, 0.0, time/2, (value) => enemy.Opacity = value).
EndParallel().
Add(() => canvasChildren.Remove(explosion)).

You may notice that I also apply a range to the enemy's Opacity. To me it looks more natural, as the ships start to disappear while it is exploding, as my explosion doesn't include the ship image itself being destroyed. If there was an explosion per ship, already including the ship parts, I may immediately remove the ship from the screen while playing the explosion.

5.1. Sound of an explosion.

The explosion isn't complete without the sound of an explosion. Initially I was creating a new media element each time I wanted to play a sound, which wasn't working really well.

I don't really know the reason for this, but sometimes the sounds weren't playing. I don't believe it was because too many sound channels were used, as sometimes that happened to the first sounds.

So, the solution I found was to have the MediaElements created all the time by adding them to the MainWindow. So, each time I wanted to play a sound, I simply played the right media element.

In some cases, though, I wanted to play the same sound 2 or 3 times before letting it stop. For sounds that start with a high volume and then decrease I solved this by simply stopping the sound that was still playing and then starting to play it again. But in case of some explosions that happened in sequence it appeared to be simply "delaying" the sound of a single explosion, which happened when the last ship was destroyed.

I can't say that I used the best solution of all times, but I solved the issue by creating 3 media elements for the explosions and by alternating them when playing. So, the first, second and third explosion may play at the same time and, if there's a fourth explosion, it will stop the first one, which is probably ending already and so, at least to me, the explosions sound great.

So, when playing a explosion sound I get the right media element using the _GetExplosion method:

C#
private static int _explosionIndex;

// this is initialized in the constructor with 3 explosions.
private static MediaElement[] _explosions; 

internal static MediaElement _GetExplosion()
{
  var result = _explosions[_explosionIndex];
  _explosionIndex++;

  if (_explosionIndex >= _explosions.Length)
    _explosionIndex = 0;

  return result;
}

6. The shoots

When I first thought about the shoots, I thought that there will be all kinds of different shoots, like explosive one, laser ones and the like. But I started with a single one, a Laser, which as you may imagine became a class.

But when I finally decided to add new resources to the game I decided to only change the shoots between normal and strong, without really creating new shoots. I did it only to avoid creating a huge game, as my purpose is not the create the best game, but to create a tutorial on how to create a basic game.

So, we have a Laser class and such class is responsible for doing the animation of a shoot. I needed to change the player animation to include the shot itself, with is done by adding the following code to the Parallel animation of the PlayerCharacter:

C#
BeginLoop().
  BeginRunCondition(() => IsShootPressed && _hotOffset > 0).
    BeginSequence().
      Add(_Shoot).
      Wait(() => _traits._waitBetweenShoots / _shootSpeed).
    EndSequence().
  EndRunCondition().
EndLoop().

And the shot itself is again a simple RangeBySpeed, but as I play a sound and make the laser become weaker after touching enemies, the final code is like this:

C#
AnimationBuilder.
BeginSequence().
  PlaySound(() => MainPage._instance.mediaElementLaser, 0.4).
  BeginPrematureEndCondition(() => _health <= 0).
    RangeBySpeed(Canvas.GetTop(this), -Height, 300, (value) => Canvas.SetTop(this, value)).
  EndPrematureEndCondition().
  Add(() => MainPage._instance.canvas.Children.Remove(this)).
EndSequence();

And, as the shot also collides, I've added a code to check for shot collisions inside the SegmentCompleted event (in fact, inside the foreach over all the enemies):

C#
foreach(var shoot in objects.OfType<Laser>())
{
  Rect shotRect = 
    new Rect
    (
      Canvas.GetLeft(shoot) - Canvas.GetLeft(enemy),
      Canvas.GetTop(shoot) - Canvas.GetTop(enemy),
      shoot.Width,
      shoot.Height
    );

  int oldHealth = enemy.Health;
  if (oldHealth > 0 && shotRect.CollidesWith((WriteableBitmap)enemy.image.Source))
  {
    if (enemy._hitBy.Add(shoot))
    {
      byte value = (byte)shoot.Health;
      var hit = new Hit();
      hit.Intensity = value;
      hit.SetCenterX(shoot.GetCenterX());
      hit.SetCenterY(shoot.GetCenterY());
      AnimationManager.Add(hit.CreateAnimation());
    }

    int shootDamage = PlayerCharacter._instance._traits._shootDamage;

    if (shoot._isGreen)
      shootDamage *= 2;

    enemy.Health -= shootDamage;
    shoot.Health -= 10;
    PlayerCharacter._instance.Score += oldHealth - enemy.Health;

    if (enemy.Health <= 0)
    {
      enemy._killedByShoot = true;
      shoot._killCount++;

      double enemyValue = enemy.MaxHealth / 40.0;
      enemyValue *= enemyValue;
      enemyValue *= 100;

      int killScore = (int)enemyValue / enemy._hitBy.Count;
      if (killScore < 1)
        killScore = 1;

      killScore *= shoot._killCount;

      PlayerCharacter._instance.Score += killScore;

      var positionedScore = new PositionedScore();
      var animation = positionedScore.CreateAnimation(enemy.GetCenterX(), enemy.GetCenterY(), killScore);
      AnimationManager.Add(animation);
    }
  }
}

As you can see, I am still ignoring the single responsibility principle. I am having to change the SegmentCompleted handler everytime there's a new kind of valid collision. If I really wanted to have a game that grows I would have to review such implementation, but I still think it is OK to keep it that way for a game with only 3 levels.

7. The pixel by pixel collision detection

You probably noticed that somethings don't require a specific order. I could've added the pixel by pixel collision detection before supporting the shoots, yet I opted to first have the working shoots to then write the better collision detection algorithm. If I was working with somebody else we could've worked at the same time on different things, so don't take that seven number as a required order.

Considering I started the project as a Silverlight one, and considering I already knew that the WriteableBitmap has the Pixels property, I immediately though that I should convert my bitmaps to WriteableBitmaps to check their pixels. Maybe I should've used a better approach, as it is not the best one for WPF and I did create a WPF project reusing most of the Silverlight code untouched. Yet, that's what I did and I am presenting it. For the images that will participate on collision detection (that is, the ship images) I load them as WriteableBitmaps.

The Silverlight's WriteableBitmap.Pixels property returns them as an array of int. The colors are in the ARGB format and, to detect a collision, I only consider the pixels that have a opacity greater than 127 (that is, the A(lpha) element should be bigger than 127).

Maybe this is the hardest code to understand in this application, so I will try to split it in some parts:

  1. I still keep the code that does a rectangle intersection. In fact, if there is no intersection there is no chance of collision and, if there is an intersection we must check the pixel values;
  2. To get a pixel at a given X, Y coordinate, considering the Pixels property is a single dimension array, we should do a calculation like this:
    C#
    int pixelIndex = (y * bitmap.Width) + x;
    
  3. Look at the intersection image. You can see that betwen the Red and the Green "squares" (don't judge the precision of my drawing) there is a yellow intersection. When analysing for the collision, we will be analysing all of the intersection pixels from both sides, so we need to calculate where exactly we should check each image. The position "0, 0" of the intersection coincides as the 0, 0 of the Green square, but it is not the 0, 0 of the red square.

Well, considering those factors and also trying to optimize the reads, I finished up with this code:

C#
public static bool CollidesWith(this FrameworkElement element1, WriteableBitmap bitmap1, FrameworkElement element2, WriteableBitmap bitmap2)
{
  int left1 = (int)Canvas.GetLeft(element1);
  int top1 = (int)Canvas.GetTop(element1);
  int width1 = (int)element1.Width;
  int height1 = (int)element1.Height;

  int left2 = (int)Canvas.GetLeft(element2);
  int top2 = (int)Canvas.GetTop(element2);
  int width2 = (int)element2.Width;
  int height2 = (int)element2.Height;

  Rect rect1 = new Rect(left1, top1, width1, height1);
  Rect rect2 = new Rect(left2, top2, width2, height2);

  Rect intersect = rect1;
  intersect.Intersect(rect2);
      
  if (intersect.IsEmpty)
    return false;

  int intersectLeft = (int)intersect.Left;
  int intersectTop = (int)intersect.Top;
  int intersectRight = (int)intersect.Right;
  int intersectBottom = (int)intersect.Bottom;

  var pixels1 = bitmap1.Pixels;
  var pixels2 = bitmap2.Pixels;

  for(int y=intersectTop; y<intersectBottom; y++)
  {
    int index1 = (y-top1)*width1 + (intersectLeft-left1);
    int index2 = (y-top2)*width2 + (intersectLeft-left2);
    for(int x=intersectLeft; x<intersectRight; x++)
    {
      if ((pixels1[index1] & 0xFF000000) > 0x7F000000 && (pixels2[index2] & 0xFF000000) > 0x7F000000)
        return true;

      index1++;
      index2++;
    }
  }

  return false;
}

The & 0xFF000000 is used to extract only the Alpha element of the ARGB pixel. The > 0x7F000000 is comparing if such value is greater than 127. In fact, I could've extracted only the alpha element as a value from 0 to 255, but this code avoids an extra operation.

I only calculate the index1 and index2 variables when I move from a line to another. I could've put the index1 and index2 calculation inside the inner for, but as I know I only need to read the "next" pixels, I prefer to calculate it only once per line and then increment both index1 and index2 at each iteraction.

You can see that if I find a collision I immediately return. In my case, I only want to know if there's a collision or not. If you needed to know the number of pixels in collision, you would need to increment a variable (like a result) and continue checking the pixels.

8. The boss.

Image 8

During the initial creation I simply wrote a single Enemy class. To create the boss I considered refactoring it, but I decided that it was still not needed (that's usually a source of evil, but I am lazy sometimes). I simply created a new animation method for the boss and, at construction, I gave it a different image and health.

The boss can't simply disappear like normal ships, so I used an imperative animation that keeps yield returning RangeBySpeed animation with random values to move the boss over the screen. And, inside the level animation itself, I put the boss animation inside a sequence that first waits 60 seconds (later I changed for 3 minutes) before making the boss appear.

So, the boss animation is like this:

C#
public IAnimation CreateBoss1Animation()
{
  Canvas.SetTop(this, -Height);
  this.SetCenterX(MainPage._instance.canvas.Width / 2);

  return
    AnimationBuilder.
    BeginSequence().
      Add(() => MainPage._instance.canvas.Children.Add(this)).
      BeginPrematureEndCondition(() => Health <= 0).
        Add(new ImperativeAnimation(_Boss1Animation())).
      EndPrematureEndCondition().
      ExplodeIfDead(this, 3).
      Add(() => MainPage._instance.canvas.Children.Remove(this)).
      Add(() => _bossDefeated = true).
    EndSequence();
}

And the animation that puts the boss on the level is like this:

C#
BeginSequence().
  Wait(3 * 60).
  Add(CreateBossAnimation(boss)).
EndSequence().

9. The levels and the game animation

Well, I started the game by simply "throwing" things to the screen. The game was starting directly. There wasn't a start screen or different levels. I even made the Game Over screen as part of the PlayerCharacter animation, so I was ignoring the Single Responsibility Principle at that moment.

In fact, you may notice that even in the last version there are some static variables that you may not like. Well, I can only say that I only corrected and abstracted things that I needed. I really did a lazy job when I started to write the game. The theory is not hard: "Each class should have a single responsibility". "You should have good abstractions". But, when I started, I was only focusing on a really limited result at a time (like creating the star background or making the character move). Only later I decided to create levels, a start screen with different options and the like.

Then, the last thing I did was to really add the option to restart the game, as before the game ended and I needed to reload the page (or close and open the WPF application) to play the game again.

Well, I think I am getting too lazy, as I will only say that the "entire game" is played by this animation:

C#
AnimationBuilder.
BeginLoop().
  BeginSequence().
    Add(_ShowStartMenu()).
    BeginPrematureEndCondition(() => PlayerCharacter._instance.Health <= 0).
      Add(_PlayLevels()).
    EndPrematureEndCondition().
    BeginRunCondition(() => PlayerCharacter._instance.Health <= 0).
      Add(_GameOver()).
    EndRunCondition().
    BeginRunCondition(() => PlayerCharacter._instance.Health > 0).
      Add(_YouConqueredTheUniverse()).
    EndRunCondition().
  EndSequence().
EndLoop();

So, I really think that if you want to understand the improvements I made to the game, it is time to see the code and play with it (or play it if you didn't do it up to this moment).

WPF

I think I started the WPF application only when I put the first boss into the game. I created a new project and I then copied only the project file to the same directory of the Silverlight application. Considering I can't use the #if directive in XAML and that WPF uses a Window instead of a UserControl as the initial "page", I created a new Window. But, if you see, I made it use the same class as the Silverlight one.

I really did the minimum to make the game work in WPF. For example, WPF doesn't have a Pixels property in its WriteableBitmap, yet it allows to copy the pixels of any BitmapSource to an int array, so I could've avoided the WriteableBitmap in WPF, but I only did the minimum to have the "Pixels" property accessible. So, if you find some #if WPF that's where I did changes specific to WPF.

Well, I think that if you look at the code you will see that there aren't lots of those #ifs, but there are enough to prove that WPF isn't simply more complete than Silverlight, they are different.

Points of Interest

Maybe writing a game using declarative animations is the most interesting part. But, to me, the thing that most impressed me is the fact that some classes are more complete and friendly in Silverlight than in WPF when, for many time, I believed WPF was the more complete one.

In the final version, when you kill the boss the sound is played more slowly to try to simulate a big explosion, yet such code only works in Silverlight as there isn't a PlaybackRate in WPF's MediaElement and the SpeedRatio simply didn't give me the expected result.

Final Comment

I know that I didn't made the most appealing game and that 3D games are usually what you would expect for games in this era. But I am no designer and I didn't find many good and compatible images in 2D and, at least to me, finding them is 3D is harder.

But don't worry, most concepts (like the animations made by time, which can be achieved through the declarative animations, and the moments to do collision detection) are probably going to be the same on 3D games. Surely you will need some different algorithms to do the collision detection, but the basic idea is: You will be updating "properties", checking states (like collisions and keys) and you may be creating and adding new animations to some ParallelGroups. And, in frameworks like XNA you will also need to implement an animation manager that deals with the Draws after updating the animation objects.

I really hope you enjoy this article and that you at least finish the game once (or should I say that "I hope you conquer the universe!" ?)

Images and Sounds

All the images and sounds used in this game were got from the internet. For the sounds I used the site http://www.freesfx.co.uk/.

The images were got from different sites. The explosion spritesheet that I am showing in the article was got from https://www.touchdevelop.com/lvxdmali, but there's another explosion (a big one) that was got from http://april-young.com/home/wp-content/uploads/2012/04/Explosion1spritesheet.png.

I really don't remember where I found the characters, as I downloaded lots of images before really choosing the ones I considered the best.

In fact, the license of this article doesn't apply to the images and I don't know if there's any license on those images. Yet I hope I am not doing anything bad as I am not gaining money from the game.

A Hidden Feature: 5598

My son wrote 5598 by "hitting the keyboard" sometimes (he was 9 months old when he did it). I should tell you that I really considered putting some hidden feature using such code in the game, but as he did write this into the article, well, I decided it will be interesting to finish the article with it. And if you are seeing this message, good job at "hacking" the source code of the article.

Facebook applications

The article itself is finished. Now I want to ask for something as only today I really decided to look at how to write things for Facebook. In fact, I started that by accident, as I decided to kill all the applications that were in my profile and in the details I entered the help that explains how to build facebook applications. And I easily made this game into a facebook game (well, not a social game yet, but it works inside facebook).

What I really need now is to know if is users unrelated to me are seeing the game at https://apps.facebook.com/pfzshootemup/[^] and I want to ask if someone can tell me how can I show the user profile information (and the friend list information) from a Silverlight application and if it is possible to show such information from an external application (that is, the WPF version of the application).

Every help is welcome and you can contact me by using the e-mail paulozemek@outlook.com

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionI thought Silverlight was dead....? Pin
MacSpudster1-Nov-16 5:23
professionalMacSpudster1-Nov-16 5:23 
AnswerRe: I thought Silverlight was dead....? Pin
Paulo Zemek1-Nov-16 5:33
mvaPaulo Zemek1-Nov-16 5:33 
GeneralRe: I thought Silverlight was dead....? Pin
MacSpudster3-Nov-16 10:55
professionalMacSpudster3-Nov-16 10:55 
QuestionHow to change animation properties at run time Pin
berkumans5-Oct-16 11:37
berkumans5-Oct-16 11:37 
AnswerRe: How to change animation properties at run time Pin
Paulo Zemek6-Oct-16 5:56
mvaPaulo Zemek6-Oct-16 5:56 
AnswerRe: How to change animation properties at run time Pin
Paulo Zemek12-Oct-16 8:25
mvaPaulo Zemek12-Oct-16 8:25 
Questionupdates for vs 2012 - vs 2013 Pin
kiquenet.com24-Jun-14 0:46
professionalkiquenet.com24-Jun-14 0:46 
AnswerRe: updates for vs 2012 - vs 2013 Pin
Paulo Zemek24-Jun-14 10:21
mvaPaulo Zemek24-Jun-14 10:21 
GeneralMy vote of 5 Pin
Baxter P1-Mar-14 6:03
professionalBaxter P1-Mar-14 6:03 
GeneralRe: My vote of 5 Pin
Paulo Zemek1-Mar-14 8:04
mvaPaulo Zemek1-Mar-14 8:04 
QuestionMy rating of 5 Pin
Bill_Hallahan18-Dec-13 15:10
Bill_Hallahan18-Dec-13 15:10 
AnswerRe: My rating of 5 Pin
Paulo Zemek18-Dec-13 15:43
mvaPaulo Zemek18-Dec-13 15:43 
QuestionVoting 5 Pin
toni galani12-Dec-13 19:36
toni galani12-Dec-13 19:36 
AnswerRe: Voting 5 Pin
Paulo Zemek13-Dec-13 1:57
mvaPaulo Zemek13-Dec-13 1:57 
GeneralMy vote of 5 Pin
StM0n12-Dec-13 0:32
StM0n12-Dec-13 0:32 
GeneralRe: My vote of 5 Pin
Paulo Zemek12-Dec-13 2:53
mvaPaulo Zemek12-Dec-13 2:53 
GeneralMy vote of 5 Pin
Renju Vinod8-Dec-13 22:48
professionalRenju Vinod8-Dec-13 22:48 
GeneralRe: My vote of 5 Pin
Paulo Zemek9-Dec-13 3:08
mvaPaulo Zemek9-Dec-13 3:08 
Answer5 Stars & Answer to one of your questions Pin
Dan Colasanti6-Dec-13 22:00
professionalDan Colasanti6-Dec-13 22:00 
GeneralRe: 5 Stars & Answer to one of your questions Pin
Paulo Zemek7-Dec-13 5:03
mvaPaulo Zemek7-Dec-13 5:03 
QuestionWell done Pin
db7uk8-Nov-13 2:30
db7uk8-Nov-13 2:30 
AnswerRe: Well done Pin
Paulo Zemek8-Nov-13 2:38
mvaPaulo Zemek8-Nov-13 2:38 
Question5 Pin
palko.g7-Nov-13 2:37
palko.g7-Nov-13 2:37 
AnswerRe: 5 Pin
Paulo Zemek7-Nov-13 2:42
mvaPaulo Zemek7-Nov-13 2:42 
Questionvery nice Pin
BillW335-Nov-13 4:58
professionalBillW335-Nov-13 4:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.