Chapter 8. Putting It All Together

All right, you’ve built a solid design and have the start of what could become a pretty cool game. Again, the concept of the game is that a player will control one sprite and try to avoid hitting the chasing sprites as they fly across the screen, while also trying to catch the evading sprites. Now you need to add some scoring and some game logic and do some other fine-tuning to get your game to where you want it.

The first thing you’ll do in this chapter is add some scoring to your game. The first step when you’re writing a game and you start to look at scoring is to decide what events will trigger a change in score. Sometimes scores will change when a weapon of some kind hits the player. At other times, you’ll change the score when the player hits something herself. Still other times, you’ll want to change the score when the user accomplishes something (e.g., answers a question, solves a puzzle, etc.).

In this game, you’re going to change the score when an enemy sprite leaves the screen without having run into the player (meaning that the player successfully avoided that sprite).

It would make sense, given that scoring mechanism, for you to add a score value to each individual sprite. Some sprites might be worth more than others, based on their speed or some other factor that you determine.

In addition to deciding how to calculate the score, you need to be able to draw the score on the screen. We’ll tackle that side of the scoring problem first, and then see how to adjust the score whenever a sprite crosses the screen without hitting the player.

In addition to adding scoring to your game and learning to draw text using SpriteFonts in this chapter, you’ll also add some variety to your sprites by introducing different sprite images and different sounds for each sprite type. You’ll also add a background image, look at game states, and add a power-up to the game.

Drawing 2D Text

This chapter builds on the code that you finished writing in Chapter 7. Open that game project and use it throughout this chapter.

First, you’ll need to add an integer variable representing a sprite’s score value to the Sprite base class (note the addition of the public get accessor as well, via auto-implemented properties):

public int scoreValue {get; protected set;}

Modify both constructors in the Sprite class to receive an integer value for the score value for that sprite. The first constructor should pass the value to the second constructor, and the second constructor should use that value to set the scoreValue member variable. The constructors for the Sprite class should now look like this:

public Sprite(Texture2D textureImage, Vector2 position, Point frameSize,
    int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed,
    string collisionCueName, int scoreValue)
    : this(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, defaultMillisecondsPerFrame, collisionCueName,
    scoreValue)
{
}

public Sprite(Texture2D textureImage, Vector2 position, Point frameSize,
    int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed,
    int millisecondsPerFrame, string collisionCueName, int scoreValue)
{
    this.textureImage = textureImage;
    this.position = position;
    this.frameSize = frameSize;
    this.collisionOffset = collisionOffset;
    this.currentFrame = currentFrame;
    this.sheetSize = sheetSize;
    this.speed = speed;
    this.collisionCueName = collisionCueName;
    this.millisecondsPerFrame = millisecondsPerFrame;
    this.scoreValue = scoreValue;
}

You’ll also have to change the constructors for the derived classes (AutomatedSprite, ChasingSprite, EvadingSprite, and UserControlledSprite) to accept an integer parameter for the score value and to pass that value on to the base class constructor. The constructors for those sprites should look like this:

Constructors for the AutomatedSprite class

public AutomatedSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize,
    Vector2 speed, string collisionCueName, int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, collisionCueName, scoreValue)
{
}

public AutomatedSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize,
    Vector2 speed, int millisecondsPerFrame, string collisionCueName,
    int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, millisecondsPerFrame, collisionCueName, scoreValue)
{
}
Constructors for the ChasingSprite class

public ChasingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, string collisionCueName,
    SpriteManager spriteManager, int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, collisionCueName, scoreValue)
{
    this.spriteManager = spriteManager;
}

public ChasingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, int millisecondsPerFrame,
    string collisionCueName, SpriteManager spriteManager,
    int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, millisecondsPerFrame,
    collisionCueName, scoreValue)
{
    this.spriteManager = spriteManager;
}
Constructors for the EvadingSprite class

public EvadingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, string collisionCueName,
    SpriteManager spriteManager, float evasionSpeedModifier,
    int evasionRange, int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, collisionCueName, scoreValue)
{
    this.spriteManager = spriteManager;
    this.evasionSpeedModifier = evasionSpeedModifier;
    this.evasionRange = evasionRange;
}

public EvadingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, int millisecondsPerFrame,
    string collisionCueName, SpriteManager spriteManager,
    float evasionSpeedModifier, int evasionRange,
    int scoreValue)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, millisecondsPerFrame,
    collisionCueName, scoreValue)
{
    this.spriteManager = spriteManager;
    this.evasionSpeedModifier = evasionSpeedModifier;
    this.evasionRange = evasionRange;
}
Constructors for the UserControlledSprite class

public UserControlledSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize,
    Vector2 speed)
    : base(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, null, 0)
{
}

public UserControlledSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize,
    Vector2 speed, int millisecondsPerFrame)
    : base(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, millisecondsPerFrame, null, 0)
{
}

Tip

The UserControlledSprite will not have a score value associated with it, because the player can’t be awarded points for avoiding himself. Therefore, you won’t need to add a new parameter to the constructor for this class, but you will need to pass a 0 to the scoreValue parameter of the constructors for the base class.

Finally, in the SpriteManager class, you’ll need to add the score value as the final parameter in the constructor when initializing new Sprite objects. You’re currently creating objects only of type EvadingSprite, and you’re doing this at the end of the SpawnEnemy method. Add a zero as the score value for the EvadingSprites you’re creating. (You’ll be adding some logic later in this chapter that will create different types of sprites and assign different score values to those sprites based on their types.) The code that creates your EvadingSprite objects in the SpawnEnemy method should now look like this:

spriteList.Add(
    new EvadingSprite (Game.Content.Load<Texture2D>(@"imagesskullball"),
    position, new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), speed, "skullcollision", this, .75f, 150, 0));

You now have a way to calculate the score during the game based on events with different sprites. Even though you’re currently only using zeros as the score values, the underlying code is now there, so you can start to write some scoring logic for the game. First, you’ll need to add to the Game1 class a variable that represents the total score of the current game:

int currentScore = 0;

Now you’re ready to draw the score on the screen. Drawing text in 2D is done in a very similar manner to the way that you draw sprites. For every frame that is drawn, you will draw text on that frame using a SpriteBatch and an object called a SpriteFont. When drawing 2D images on the screen, you specify an image file to use and a Texture2D object to hold that image. XNA then takes the image from memory and sends the data to the graphics card.

The same thing happens when drawing with SpriteFont objects. In this case, a spritefont file is created. This is an XML file defining the characteristics of a given font: font family, font size, font spacing, etc. A SpriteFont object is used in memory to represent the spritefont. When the SpriteFont object is drawn, XNA will build a 2D image using the text you want to draw and the font specified in the XML file. The image is then sent to the graphics device to be drawn on the screen.

When drawing the game score, you’ll want to use the value of the currentScore variable. As the variable’s value changes, the score on the screen will be updated.

To draw text on the screen using a SpriteFont, you need to add a SpriteFont resource to your project. First, add a new resource folder to your project for font objects. Right-click the AnimatedSpritesContent project in Solution Explorer and select AddNew Folder. Name the folder Fonts. Now, add the actual spritefont resource. Right-click the new AnimatedSpritesContentFonts folder in Solution Explorer and select AddNew Item…. On the left side of the Add New Item window, select the Visual C# category. Select Sprite Font for the template and name the spritefont Score.spritefont (see Figure 8-1).

Adding Score.spritefont to your project
Figure 8-1. Adding Score.spritefont to your project

Tip

Spritefonts are resources that are picked up and processed by the content pipeline. As such, they must be created within the AnimatedSpritesContent project of your project.

By default, XNA 4.0 uses a font named “Kootenay” when creating a new spritefont. You can change the type of font used in your spritefont by editing the file and changing the name of the font found in the line of code that reads <FontName>Kootenay</FontName>. It is recommended that you select a font from the Redistributable Font Pack, which can be found at http://creators.xna.com/en-us/contentpack/fontpack.

Once you click the Add button, the spritefont file will be created and will open in Visual Studio. You’ll notice that it’s an XML file that contains information such as the font name, size, spacing, style, and so on. You can modify these values to customize your font as needed.

Next, you need to add a SpriteFont variable, which will store your spritefont in memory. Add the following class-level variable at the top of your Game1 class:

SpriteFont scoreFont;

Now, load your SpriteFont object via the content pipeline, the same way that you’ve loaded most other game assets. You’ll retrieve the font from the content pipeline using the Content.Load method. Add the following line of code to the LoadContent method in your Game1 class:

scoreFont = Content.Load<SpriteFont>(@"fontsscore");

The next step is to actually draw the text on the screen. As mentioned earlier, this is done via a SpriteBatch object. Instead of using the SpriteBatch’s Draw method, you’ll use a method called DrawString to draw text using a SpriteFont. Add the following code to the Draw method of your Game1 class, between the call to GraphicsDevice.Clear and the call to base.Draw:

spriteBatch.Begin(  );

// Draw fonts
spriteBatch.DrawString(scoreFont, "Score: " + currentScore,
    new Vector2(10, 10), Color.DarkBlue, 0, Vector2.Zero,
    1, SpriteEffects.None, 1);

spriteBatch.End(  );

Notice that the font needs to be drawn between calls to SpriteBatch.Begin and SpriteBatch.End, because XNA treats spritefonts just like any other 2D image resources.

The DrawString method has parameters that are similar to the parameters used in calls to SpriteBatch.Draw for 2D images. You can adjust the position, color, scale, etc. of the font to be drawn. The first parameter passed into the DrawString method is the SpriteFont object you wish to use to draw with, and the second parameter is the actual text you wish to draw.

Tip

In XNA 2.0, monospaced fonts did not render properly. XNA treated them as regular fonts (losing the benefit of a monospaced font). In XNA 4.0 this issue was fixed, and monospaced fonts now render properly.

Compile and run the game at this point and you should see the score drawn at the upper-left corner of the screen, as shown in Figure 8-2.

Yay! A score…wait…my score is zero…hmmm…
Figure 8-2. Yay! A score…wait…my score is zero…hmmm…

Excellent work! You now have an overall game score, and that means you’re one step closer to completing your game. However, the problem you have now is that you never update the game score. Before you do that, you need to add logic to create different types of sprites at random intervals rather than always creating the same type of sprite. Once you’ve implemented those changes, you can add scoring for the different sprite types.

Randomly Generating Different Sprite Types

To randomly generate sprites of different types, you first need to determine the likelihood that each type will be created. Most of the sprites in this game will be AutomatedSprites. ChasingSprites will be the next most common, and EvadingSprites will show up only occasionally. In this section, you’ll be assigning a percentage likelihood to each type of sprite. Each time a new sprite is spawned, you’ll determine which type of sprite to create based on those percentages. The exact percentage likelihood for each sprite type is something that you can play with as you test your game, and you can adjust the values to the point where they feel right to you, the developer.

To begin, open the SpriteManager class and add three new class-level variables representing the likelihood that each sprite type will be spawned:

int likelihoodAutomated = 75;
int likelihoodChasing = 20;
int likelihoodEvading = 5;

You’ll notice that the values added equal 100 (representing 100%). Essentially, 75% of the time you’ll generate an AutomatedSprite, 20% of the time a ChasingSprite, and 5% an EvadingSprite.

Now you have to add some code that will generate a random number and, based on the value of that random number, create one of the three sprite types. Open your SpriteManager class and replace this call to SpriteList.Add, which is at the end of the SpawnEnemy method:

spriteList.Add(
    new EvadingSprite (Game.Content.Load<Texture2D>(@"imagesskullball"),
    position, new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), speed, "skullcollision", this, .75f, 150, 0));

with the following code:

// Get random number
int random = ((Game1)Game).rnd.Next(100);
if (random < likelihoodAutomated)
{
    // Create AutomatedSprite
    spriteList.Add(
        new AutomatedSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0,0), new Point(6, 8),
        speed, "skullcollision", 0));
}
else if (random < likelihoodAutomated +
    likelihoodChasing)
{
    // Create ChasingSprite
    spriteList.Add(
        new ChasingSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0,0), new Point(6, 8),
        speed, "skullcollision", this, 0));            }else
{
    // Create EvadingSprite
    spriteList.Add(
        new EvadingSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0, 0), new Point(6, 8),
        speed, "skullcollision", this, .75f, 150, 0));
}

This code first generates a number between 0 and 99, and then compares that generated number to the value representing how often an AutomatedSprite should be generated. If the generated number is less than that value, an AutomatedSprite is created. If the randomly generated number is not less than that value, it is then compared against the sum of the value representing how often an AutomatedSprite should be generated and the value representing how often a ChasingSprite should be generated. If the generated value is less than the sum of those two values, a ChasingSprite is generated. If it isn’t less than that value, an EvadingSprite is generated.

If that doesn’t make sense, think of it this way: using the values from our current example, the random number (some value between 0 and 99) is first evaluated to see whether it is less than 75. If it is, an AutomatedSprite is generated. This represents a 75% chance that an AutomatedSprite will be created, which is exactly what you want. If the random number is greater than 75, it is then checked to see whether it is less than 95 (i.e., the sum of the chance of creating an AutomatedSprite, which is 75%, and the chance of creating a ChasingSprite, which is 20%). Because this comparison would never have been performed if the random number had been less than 75, this is essentially checking to see whether the value is between 75 and 94—which represents a 20% chance that a ChasingSprite will be created (again, this is exactly what you want). Finally, an EvadingSprite will be generated if neither case is true—in other words, if the random value is between 95 and 99, of which there is a 5% chance, as you wanted.

Oh yeah! This is all starting to come together. Compile and run the game, and you should notice that the majority of the sprites generated are AutomatedSprites, some ChasingSprites are mixed in, and an occasional EvadingSprite shows up. Good times!

The problem that your game has at this point is that while the sprites have different behaviors, they all look the same. Not only does that make things somewhat boring, but it’s also confusing to the player because one would expect similar-looking sprites to behave similarly. You need to add some diversity to these different types of sprites.

Adding Some Variety to Your Sprites

First things first, you need some more images. If you haven’t already done so, download the source code for this chapter of the book. Within this chapter’s source code (in the AnimatedSpritesAnimatedSpritesContentImages folder), you’ll find some sprite sheet files for the different types of sprites.

Right-click the ContentImages folder in Solution Explorer and select AddExisting Item…. Navigate to the AnimatedSpritesAnimatedSpritesContentImages folder of the source code for this chapter and add the bolt.png, fourblades.png, threeblades.png, and plus.png images to your project.

In addition to a new image for each of the new sprites, you’ll want some different sounds for collisions with each type. Also with the source code for this chapter, in the AnimatedSpritesAnimatedSpritesContentAudio folder, you’ll find some .wav files. Copy the boltcollision.wav, fourbladescollision.wav, pluscollision.wav, and threebladescollision.wav files from that folder to your own project’s AnimatedSpritesContentAudio folder using Windows Explorer. Remember that when dealing with actual sound files, you don’t add them to the project in Visual Studio. You need to copy them to your project’s AnimatedSpritesContentAudio directory, but you’ll add them to the project using XACT rather than within Visual Studio.

Once you’ve copied the files, start XACT so that you can add these new .wav files to your XACT project file.

In XACT, load the sound project for this game (the file should be called GameAudio.xap and should be located in your project’s AnimatedSpritesContentAudio folder). Open the Wave Bank and Sound Bank windows for your XACT project by double-clicking on the Wave Bank and Sound Bank nodes in the tree menu on the left.

In the Wave Bank window, right-click and select Insert Wave File(s)…. Select the four new .wav files that you’ve copied to your project’s ContentAudio directory, as shown in Figure 8-3.

When you click Open, the .wav files will be added to your Wave Bank window, which should now look something like Figure 8-4.

Selecting the four new .wav files
Figure 8-3. Selecting the four new .wav files
The four new .wav files are added to the Wave Bank and will appear highlighted in red
Figure 8-4. The four new .wav files are added to the Wave Bank and will appear highlighted in red

Next, drag the items you just added from the Wave Bank window into the Cue Name section of the Sound Bank window. You need to drop them in the Cue Name section rather than the Sound Name section because you need cue names for each of these sounds in order to play them from your code. Your window should now have the new sounds listed in the Wave Bank window and in the Sound Name and Cue Name sections of the Sound Bank window, as shown in Figure 8-5.

Cue names added for your new sounds
Figure 8-5. Cue names added for your new sounds

You can adjust the volume levels of the sounds to your liking, as described in Chapter 6. Once you’re satisfied, save your project, exit XACT, and return to Visual Studio.

Now you’ll need to assign the new images and sounds to the different types of sprites when they spawn. Luckily, because of the way you’ve designed your SpriteManager class, this will be easy. You are already randomly creating different types of sprites, and now all you need to do is apply the different images and sounds to those sprites.

You may be wondering about some of the files you’ve just added, because currently you have only three types of sprites. However, there are actually six different sprite sheets in your project. Take a look at Table 8-1 to see the intended use for each of the sprites in this game.

Table 8-1. Sprite images and their purposes

Sprite image

Name

Purpose

image with no caption

Three rings

Used for the player-controlled sprite.

image with no caption

Three blades

This saw image (as well as the four-blade image) is used in AutomatedSprites and represents the enemy of the player. The player must avoid getting hit by these objects, or the game will end. You’ll add “lives” to the game later in this chapter, allowing the player to get hit a number of times before the game ends.

image with no caption

Four blades

This saw image (as well as the three-blade image) is used in AutomatedSprites and represents the enemy of the player. The player must avoid getting hit by these objects, or the game will end. You’ll add “lives” to the game later in this chapter, allowing the player to get hit a number of times before the game ends.

image with no caption

Skull ball

Used in ChasingSprites that pursue the player. If an object of this type hits the player sprite, the player’s movement speed will be cut by 50% for a short time, making it harder to avoid the AutomatedSprites.

image with no caption

Plus

Used in ChasingSprites that pursue the player. If an object of this type hits the player sprite, the player sprite will double in size for a short time, making it harder to avoid AutomatedSprites.

image with no caption

Bolt

Used in EvadingSprites. This is the only type of sprite object the player will actually want to collide with. Colliding with a bolt will increase the player’s movement speed for a short time, allowing him to avoid AutomatedSprites more easily.

In the SpriteManager class’s SpawnEnemy method, you added the code that randomly generates different types of sprites. You’ll need to change that code to create one of the five nonplayer sprite types mentioned in the preceding table. If the random-number comparison indicates that you need to create an AutomatedSprite, you’ll need to do another random calculation to determine whether you’ll be creating a three-blade object or a four-blade object (both should have a 50% probability).

Likewise, if a ChasingSprite is chosen, you’ll need to do a random calculation to determine whether to create a plus or a skull ball (again, with a 50% chance for either one). Finally, if an EvadingSprite is to be created, you’ll create the bolt sprite. Replace the following code that you just added to the end of the SpawnEnemy method:

// Get random number
int random = ((Game1)Game).rnd.Next(100);
if (random < likelihoodAutomated)
{
    // Create AutomatedSprite
    spriteList.Add(
        new AutomatedSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0,0), new Point(6, 8),
        speed, "skullcollision", 0));
}
else if (random < likelihoodAutomated +
    likelihoodChasing)
{
    // Create ChasingSprite
    spriteList.Add(
        new ChasingSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0,0), new Point(6, 8),
        speed, "skullcollision", this, 0));            }
else
{
    // Create EvadingSprite
    spriteList.Add(
        new EvadingSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),
        position, new Point(75, 75), 10, new Point(0, 0), new Point(6, 8),
        speed, "skullcollision", this, .75f, 150, 0));
}

with this code:

// Get random number between 0 and 99
int random = ((Game1)Game).rnd.Next(100);
if (random < likelihoodAutomated)
{
    // Create an AutomatedSprite.
    // Get new random number to determine whether to
    // create a three-blade or four-blade sprite.
    if (((Game1)Game).rnd.Next(2) == 0)
    {
        // Create a four-blade enemy
        spriteList.Add(
            new AutomatedSprite(
                Game.Content.Load<Texture2D>(@"imagesfourblades"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "fourbladescollision", 0));
    }
    else
    {
        // Create a three-blade enemy
        spriteList.Add(
            new AutomatedSprite(
                Game.Content.Load<Texture2D>(@"images	hreeblades"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "threebladescollision", 0));
    }
}
else if (random < likelihoodAutomated +
    likelihoodChasing)
{
    // Create a ChasingSprite.
    // Get new random number to determine whether
    // to create a skull or a plus sprite.
    if (((Game1)Game).rnd.Next(2) == 0)
    {
        // Create a skull
        spriteList.Add(
            new ChasingSprite(
                Game.Content.Load<Texture2D>(@"imagesskullball"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "skullcollision", this, 0));
    }
    else
    {
        // Create a plus
        spriteList.Add(
            new ChasingSprite(
                Game.Content.Load<Texture2D>(@"imagesplus"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 4), speed, "pluscollision", this, 0));
    }
}
else
{
    // Create an EvadingSprite
    spriteList.Add(
            new EvadingSprite(
                Game.Content.Load<Texture2D>(@"imagesolt"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "boltcollision", this,
                .75f, 150, 0));
}

One important thing to note is that the sprite sheet for the plus ChasingSprite has six columns and only four rows, whereas all the others have six columns but eight rows. If you run your game and the plus sprite animates, disappears, and reappears, that most likely is the cause of your issue.

Compile and run the game now, and you should see a variety of objects running around the screen with different behaviors, as shown in Figure 8-6. These objects should also make different sounds when they collide with the player.

A variety of sprites—sweet!
Figure 8-6. A variety of sprites—sweet!

You probably received a compilation warning about the likelihoodEvading variable in the AnimatedSprite class. This variable is assigned but never used. If you look at your code, you’ll see that the variable really only exists to show the percentage likelihood of creating an evading sprite. The value isn’t used in the code. To avoid the warning, you can comment out that line (don’t remove it, though, as it’s useful for maintenance purposes—this variable quickly shows the percentage likelihood of an evading sprite being created).

Adding a Background Image

Next, you’ll add a little more spice to your game by adding a background image. With the source code for this chapter (again, in the AnimatedSpritesAnimatedSpritesContentImages folder), you’ll find an image named background.jpg. Add the image to the project the same way you added the other images (right-click the AnimatedSpritesContentImages folder, select AddExisting Item…, and navigate to the background.jpg image included with the source code).

Your SpriteManager class was built to handle animated sprites and derived classes. Something as simple as a background image can just be added to your Game1 class. You’ll need to add a Texture2D variable for the image:

Texture2D backgroundTexture;

and load the Texture2D image in the LoadContent method:

backgroundTexture = Content.Load<Texture2D>(@"Imagesackground");

Next, you’ll need to add the code to draw the image. Because you’ll now have multiple sprites being drawn within your Game1 class (the SpriteFont counts as a sprite, so you’ll be drawing a score sprite as well as a background sprite), you need to make sure that the score text is always on top of the background image. Typically, when trying to ensure that one sprite is on top of another, you modify the SpriteBatch.Begin call to include an appropriate SpriteSortMode. However, this is a case where you’re drawing only two items, and you know that you’ll always want to draw the score on top of the background. As such, you can forego the overhead involved in specifying a sort mode in the Begin method, and instead always draw the background first and then the score.

Modify the Draw method of your Game1 class to look like this:

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.White);

    spriteBatch.Begin(  );

    // Draw background image
    spriteBatch.Draw(backgroundTexture,
        new Rectangle(0, 0, Window.ClientBounds.Width,
        Window.ClientBounds.Height), null,
        Color.White, 0, Vector2.Zero,
        SpriteEffects.None, 0);

    // Draw fonts
    spriteBatch.DrawString(scoreFont, "Score: " + currentScore,
        new Vector2(10, 10), Color.DarkBlue, 0, Vector2.Zero,
        1, SpriteEffects.None, 1);

    spriteBatch.End(  );

    base.Draw(gameTime);
}

Compile and run the game, and you’ll see the impact that a background image can have on the overall look of the game (Figure 8-7). This is getting exciting—things are really starting to come together!

The game in progress with a sprite background
Figure 8-7. The game in progress with a sprite background

Nice job. You have a background and multiple types of sprites with varying behaviors. Now let’s take a look at finishing up the game scoring logic.

Game Scoring

As you’ll recall from our earlier discussion of this topic, the first thing you need to do is determine what event(s) will trigger a change in score. For this game, you’ll be updating the score whenever the user successfully avoids a three-blade, four-blade, skull ball, or plus sprite. You actually have already added the logic to determine when one of those sprites has been successfully avoided; it lies in the code that deletes the sprites when they disappear off the edge of the screen. If a sprite makes it across the screen and needs to be deleted, that means the user has avoided that sprite, and if it was a three-blade, four-blade, skull ball, or plus sprite, you need to give some points to the user.

Tip

Any time you’re developing a game, scoring rules and calculations are things you’ll need to think about. You’ll most likely formulate an idea, implement it, and then tweak it while testing your game to see whether it feels right and plays the way you want it to. For the purposes of this book, the scoring calculations and rules are laid out for you to learn. However, as you begin to feel more comfortable with the concepts in this book and this chapter specifically, feel free to change the rules and tweak the game to whatever feels right to you as both the developer and a player.

In the SpriteManager class, add three new class-level variables representing the three types of sprites you’ll be sending at the player, as well as public properties for each variable:

int automatedSpritePointValue = 10;
int chasingSpritePointValue = 20;
int evadingSpritePointValue = 0;

The chasing sprites are tougher than the automated ones, which just move in a straight line across the screen. As such, they are worth more points. The evading objects will be used for power-ups, and whereas the player will want to track them down to gain a performance bonus, there will be no scoring penalty or bonus for not colliding with those sprites.

You now need to add to your Game1 class a public method that will allow your SpriteManager to add to the game score. Because the deletion of sprites takes place in the SpriteManager, it makes sense to calculate the score at that point in the program. Add the following method to your Game1 class:

public void AddScore(int score)
{
    currentScore += score;
}

Next, you’ll need to locate the code that deletes the sprites when they go off the edge of the screen. This code resides in the Update method of your SpriteManager class. The method actually has two different places where sprites are deleted: one for sprites that are deleted because they have gone off the screen and one for sprites that are deleted because they have collided with the player object. Both cases use SpriteList.RemoveAt(i) to remove the sprite from the list of sprites in the game.

Find the code that removes sprites because they have gone off the edge of the screen. Currently, the code should look something like this:

// Remove object if it is out of bounds
if(s.IsOutOfBounds(Game.Window.ClientBounds))
{
    spriteList.RemoveAt(i);
    −−i;
}

You’ll need to modify the code to add the score for that sprite before removing it. Change the code as shown here (the added line is shown in bold):

// Remove object if it is out of bounds
if(s.IsOutOfBounds(Game.Window.ClientBounds))
{
    ((Game1)Game).AddScore(spriteList[i].scoreValue);
    spriteList.RemoveAt(i);
    −−i;
}

So you can verify that you placed the line in the correct place, your Update method should now look something like this (the changed code section is highlighted in bold):

public override void Update(GameTime gameTime)
{
    // Update player
    player.Update(gameTime, Game.Window.ClientBounds);

    // Check to see if it's time to spawn a new enemy
    nextSpawnTime −= gameTime.ElapsedGameTime.Milliseconds;
    if (nextSpawnTime < 0)
    {
        SpawnEnemy(  );

        // Reset spawn timer
        nextSpawnTime = ((Game1)Game).GetRandom.Next(
            ((Game1)Game).EnemySpawnMinMilliseconds,
            ((Game1)Game).EnemySpawnMaxMilliseconds);
    }

    // Update all sprites
    for (int i = 0; i < spriteList.Count; ++i)
    {
        Sprite s = spriteList[i];

        s.Update(gameTime, Game.Window.ClientBounds);

        // Check for collisions
        if (s.collisionRect.Intersects(player.collisionRect))
        {
            // Play collision sound
            if(s.GetCollisionCueName != null)
                ((Game1)Game).PlayCue(s.GetCollisionCueName);

            // Remove collided sprite from the game
            spriteList.RemoveAt(i);
            −−i;
        }

        // Remove object if it is out of bounds
        if(s.IsOutOfBounds(Game.Window.ClientBounds))
        {
            ((Game1)Game).AddScore(spriteList[i].GetScoreValue);
            spriteList.RemoveAt(i);
            −−i;
        }
    }

    base.Update(gameTime);
}

The Update method of your SpriteManager class is getting pretty hairy now, so it’s time for a little refactoring. Create a method called UpdateSprites that takes a parameter of type GameTime. Remove from the Update method the section of code that updates your sprites (player and nonplayer), and place it in the UpdateSprites method. In the place of the original code in the Update method, call UpdateSprites. Your Update method should now look like this:

public override void Update(GameTime gameTime)
{
    // Time to spawn enemy?
    nextSpawnTime −= gameTime.ElapsedGameTime.Milliseconds;
    if (nextSpawnTime < 0)
    {
        SpawnEnemy(  );

        // Reset spawn timer
        ResetSpawnTime(  );
    }

    UpdateSprites(gameTime);

    base.Update(gameTime);
}

Ahhh, yes…much better. Your UpdateSprites method, in turn, should look like this:

protected void UpdateSprites(GameTime gameTime)
{
    // Update player
    player.Update(gameTime, Game.Window.ClientBounds);

    // Update all nonplayer sprites
    for (int i = 0; i < spriteList.Count; ++i)
    {
        Sprite s = spriteList[i];

        s.Update(gameTime, Game.Window.ClientBounds);

        // Check for collisions
        if (s.collisionRect.Intersects(player.collisionRect))
        {
            // Play collision sound
            if (s.collisionCueName != null)
                ((Game1)Game).PlayCue(s.collisionCueName);

            // Remove collided sprite from the game
            spriteList.RemoveAt(i);
            −−i;
        }

        // Remove object if it is out of bounds
        if (s.IsOutOfBounds(Game.Window.ClientBounds))
        {
            ((Game1)Game).AddScore(spriteList[i].scoreValue);
            spriteList.RemoveAt(i);
            −−i;
        }

    }
}

Finally, you’ll need to add the appropriate score values to the constructors used to create each new sprite. For each AutomatedSprite that is generated, the final parameter (which represents the score value for that sprite) should be the automatedSpritePointValue member variable. Likewise, for each ChasingSprite generated, the final parameter should be the chasingSpritePointValue, and the final parameter for each EvadingSprite should be the evadingSpritePointValue property.

You’ll have to change these values in the constructors for each sprite type in the SpawnEnemy method of the SpriteManager class. To find the constructors easily, search in the SpriteManager.cs file for each instance of spriteList.Add. Each time spriteList.Add is called, you’re passing in a new Sprite object whose constructor you’ll need to modify. For clarification purposes, your SpawnEnemy method should now look something like this (the only changes are the final parameters in the constructors for each of the sprite types):

private void SpawnEnemy()
{
    Vector2 speed = Vector2.Zero;
    Vector2 position = Vector2.Zero;

    // Default frame size
    Point frameSize = new Point(75, 75);

    // Randomly choose which side of the screen to place enemy,
    // then randomly create a position along that side of the screen
    // and randomly choose a speed for the enemy
    switch (((Game1)Game).rnd.Next(4))
    {
        case 0: // LEFT to RIGHT
            position = new Vector2(
                -frameSize.X, ((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight
                - frameSize.Y));
            speed = new Vector2(((Game1)Game).rnd.Next(
                enemyMinSpeed,
                enemyMaxSpeed), 0);
            break;
        case 1: // RIGHT to LEFT
            position = new
                Vector2(
                Game.GraphicsDevice.PresentationParameters.BackBufferWidth,
                ((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight
                - frameSize.Y));

            speed = new Vector2(-((Game1)Game).rnd.Next(
                enemyMinSpeed, enemyMaxSpeed), 0);
            break;
        case 2: // BOTTOM to TOP
            position = new Vector2(((Game1)Game).rnd.Next(0,
            Game.GraphicsDevice.PresentationParameters.BackBufferWidth
                - frameSize.X),
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight);

            speed = new Vector2(0,
                -((Game1)Game).rnd.Next(enemyMinSpeed,
                enemyMaxSpeed));
            break;
        case 3: // TOP to BOTTOM
            position = new Vector2(((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferWidth
                - frameSize.X), -frameSize.Y);

            speed = new Vector2(0,
                ((Game1)Game).rnd.Next(enemyMinSpeed,
                enemyMaxSpeed));
            break;
    }

    // Get random number between 0 and 99
    int random = ((Game1)Game).rnd.Next(100);
    if (random < likelihoodAutomated)
    {
        // Create an AutomatedSprite.
        // Get new random number to determine whether to
        // create a three-blade or four-blade sprite.
        if (((Game1)Game).rnd.Next(2) == 0)
        {
            // Create a four-blade enemy
            spriteList.Add(
            new AutomatedSprite(
                Game.Content.Load<Texture2D>(@"imagesfourblades"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "fourbladescollision",
                automatedSpritePointValue));
        }
        else
        {
            // Create a three-blade enemy
            spriteList.Add(
            new AutomatedSprite(
                Game.Content.Load<Texture2D>(@"imageshreeblades"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "threebladescollision",
                automatedSpritePointValue));
        }
    }
    else if (random < likelihoodAutomated +
    likelihoodChasing)
    {
        // Create a ChasingSprite.
        // Get new random number to determine whether
        // to create a skull or a plus sprite.
        if (((Game1)Game).rnd.Next(2) == 0)
        {
            // Create a skull
            spriteList.Add(
            new ChasingSprite(
                Game.Content.Load<Texture2D>(@"imagesskullball"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 8), speed, "skullcollision", this,
                chasingSpritePointValue));
        }
        else
        {
            // Create a plus
            spriteList.Add(
            new ChasingSprite(
                Game.Content.Load<Texture2D>(@"images
lus"),
                position, new Point(75, 75), 10, new Point(0, 0),
                new Point(6, 4), speed, "pluscollision", this,
                chasingSpritePointValue));
        }
    }
    else
    {
        // Create an EvadingSprite
        spriteList.Add(
        new EvadingSprite(
            Game.Content.Load<Texture2D>(@"images·olt"),
            position, new Point(75, 75), 10, new Point(0, 0),
            new Point(6, 8), speed, "boltcollision", this,
            .75f, 150, evadingSpritePointValue));
    }
}

Oh yeah! Compile and run the game now, and you’ll see that as the sprites are successfully avoided and move off the screen, the point values for those sprites are added to the game score, as shown in Figure 8-8.

560 points!!! That’s amazing!!!
Figure 8-8. 560 points!!! That’s amazing!!!

Awesome! You have some sprites running around, and the game actually keeps score! You’re all done now, right? Er…uh, wait a minute…the game never ends. That means every time you play you can potentially get a high score by just sitting there and watching. Hmmm, on second thought, we have a ways to go. Let’s add some logic to create different game states and end the game when a player gets hit a given number of times.

Game States

Your game is coming along, but there has to be a way to end the game. Typically, when a game ends, the game window doesn’t just disappear; usually there’s some kind of game-over screen that displays your score or at least lets you know that you’ve failed (or succeeded) in your mission. That’s what you need to add next. While you’re at it, it’s also common to have the same kind of thing at the beginning of the game (perhaps a menu enabling the player to select options, or at least a splash screen presenting instructions and maybe displaying your name as the author of this great game). In the following sections, you’ll add both an introductory splash screen and a closing game-over screen.

Throughout the life of any game, the game will go through different states. Sometimes these states indicate that the player has moved to a different level in the game or a different area. Sometimes the game state depicts a status change for a player (like in Pac-Man, when you turn on the ghosts and begin to chase them rather than being chased). Regardless of the specifics, the game moves through different states, and in those different states the game behaves differently. One way to implement splash screens and game-over screens is by making use of these states.

To define some states for your game, you’ll need to enumerate the different possible states that the game can have. Create an enum variable at the class level in your Game1 class. Currently, you have only three states in your game: Start (where you display your splash screen), InGame (where the game is actually running), and GameOver (where you’ll display your game over screen). You’ll also need to create a variable of that enum type that will hold the current state of the game. You’ll want to initialize that current state variable to the game state representing the start of the game:

enum GameState { Start, InGame, GameOver };
GameState currentGameState = GameState.Start;

Currently in your Game1 class, you have Update and Draw methods that let you draw things on the game screen and update objects in the game. When you place code in one of those methods (such as code to draw the score and the background image), that code runs every time the method is called (i.e., in every frame throughout the life of the game). You’re going to want to separate the logic in the Update and Draw methods to allow you to write specific code that will run only in certain situations, depending on the current state of the game. You can do this by adding a switch statement to both methods with different case statements for each possible game state. Then, when you want to write specific code to update or draw items that should take place only in a given game state, you add that code to the Update or Draw methods within the case for that particular game state.

First, add a switch statement to the Update method of your Game1 class. The Update method should now look like this:

protected override void Update(GameTime gameTime)
{
    // Only perform certain actions based on
    // the current game state
    switch (currentGameState)
    {
        case GameState.Start:
            break;
        case GameState.InGame:
            break;
        case GameState.GameOver:
            break;
    }

    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
      ButtonState.Pressed)
        this.Exit(  );

    audioEngine.Update(  );

    base.Update(gameTime);
}

Next, do the same thing with the Draw method. Your Draw method already has logic in it to draw the score and the background image, but this stuff should be drawn only when the game is in the GameState.InGame state, so you’ll need to put that code in the appropriate case of the switch statement. Your Draw method should now look like this:

protected override void Draw(GameTime gameTime)
{
    // Only draw certain items based on
    // the current game state
    switch (currentGameState)
    {
        case GameState.Start:
            break;

        case GameState.InGame:
            GraphicsDevice.Clear(Color.White);
            spriteBatch.Begin(  );

            // Draw background image
            spriteBatch.Draw(backgroundTexture,
                new Rectangle(0, 0, Window.ClientBounds.Width,
                Window.ClientBounds.Height), null,
                Color.White, 0, Vector2.Zero,
                SpriteEffects.None, 0);

            // Draw fonts
            spriteBatch.DrawString(scoreFont,
                "Score: " + currentScore,
                new Vector2(10, 10), Color.DarkBlue,
                0, Vector2.Zero,
                1, SpriteEffects.None, 1);

            spriteBatch.End(  );
            break;

        case GameState.GameOver:
            break;
    }

    base.Draw(gameTime);
}

If you were to compile and run the application at this point, it would look kind of cool but a bit messed up. The score and background would be missing from the game window, and the animated sprites would not be erased from frame to frame, which would result in trails being left for the animations.

The score and background would be missing because the current game state is set to GameState.Start by default, and in that game state you aren’t drawing those items. Likewise, you’d see the trails because you don’t call GraphicsDevice.Clear in the GameState.Start state (you only do that in the GameState.InGame state).

The reason you’d still see your animated sprites is because the SpriteManager class isn’t affected by the game state logic you just added. You only added that code to the Game1 class; the SpriteManager is a game component and is not affected by the switch statement you just added.

To get all of this to work correctly, you’ll need to add some logic to disable your SpriteManager game component in certain game states and enable it in other states.

Enabling/Disabling GameComponents

By default, when you create an instance of a GameComponent and add it to the list of components in a game, the GameComponent is wired into the game loop. When the game’s Update method is called, so is the Update method of the GameComponent, and so on.

There are two properties that can be used to enable and disable a GameComponent. The Enabled property of a GameComponent will determine whether its Update method is called when the game’s own Update method is called. Likewise, the Visible property of a DrawableGameComponent will determine whether its Draw method is called when the game’s Draw method is called. Both of these properties are set to true by default. Go to the Initialize method in your Game1 class and set both properties to false immediately after adding the component to your list of game components (added lines are in bold):

spriteManager = new SpriteManager(this);
Components.Add(spriteManager);
spriteManager.Enabled = false;
spriteManager.Visible = false;

Tip

Why start the SpriteManager in a disabled state? Remember that the game starts in the GameState.Start state, which will be used for a splash screen of some sort. You’re not going to want sprites flying in and out of the screen at this point in the game. Hence, you’ll start the game with a disabled SpriteManager, and then, when the splash screen closes, you’ll move to a game playing state and activate the SpriteManager.

Next, add some code to show some text when the game is in the GameState.Start state. This will serve as your splash screen, and you can add graphics, text, and even animations or other effects to it, just as you would during the game itself. For now, you’ll just be adding some simple text that will tell the user that he needs to avoid the blade objects. In your Draw method, add to the GameState.Start case of your switch statement some code to display these simple instructions to the user:

case GameState.Start:
    GraphicsDevice.Clear(Color.AliceBlue);

    // Draw text for intro splash screen
    spriteBatch.Begin(  );
    string text = "Avoid the blades or die!";
    spriteBatch.DrawString(scoreFont, text,
        new Vector2((Window.ClientBounds.Width / 2)
        - (scoreFont.MeasureString(text).X / 2),
        (Window.ClientBounds.Height / 2)
        - (scoreFont.MeasureString(text).Y / 2)),
        Color.SaddleBrown);

    text = "(Press any key to begin)";
    spriteBatch.DrawString(scoreFont, text,
        new Vector2((Window.ClientBounds.Width / 2)
        - (scoreFont.MeasureString(text).X / 2),
        (Window.ClientBounds.Height / 2)
        - (scoreFont.MeasureString(text).Y / 2) + 30),
        Color.SaddleBrown);

    spriteBatch.End(  );
    break;

This code should be pretty straightforward; there’s nothing you haven’t seen before, other than the use of the SpriteFont’s MeasureString method. This method will return a Vector2 object indicating the size of the string being measured. This is helpful when trying to center a spritefont in the middle of the screen, which is exactly what is being done in this code. To center the text exactly in the middle of the screen, you divide the value of the Window.ClientBounds.Width property by two to find the horizontal middle of the screen, and then offset the text by subtracting half the width of the spritefont text you’re about to draw. You determine the width of the text you’re about to draw by using the SpriteFont.MeasureString method.

If you compile and run the code at this point, you’re going to be somewhat disappointed. After all the work you’ve put into this game, it doesn’t work! All you have now is a message telling you to avoid the blades or die; worse yet, the game screen says to press any key to get started, but no matter how hard you press those keys, nothing happens. That’s because you haven’t yet added any functionality to move from the GameState.Start state to the GameState.InGame state.

To move to the GameState.InGame state, add some code to the GameState.Start case of the switch statement in the Update method of the Game1 class. The following code will detect any key presses from the user and, when the player presses a key, change the game to the GameState.InGame state and activate your SpriteManager, which will allow sprites to start flying around the screen:

case GameState.Start:
    if (Keyboard.GetState().GetPressedKeys(  ).Length > 0)
    {
        currentGameState = GameState.InGame;
        spriteManager.Enabled = true;
        spriteManager.Visible = true;
    }
    break;

If you wanted to, you could also add support here for the player clicking a mouse button or pressing a button on the gamepad to start the game. In this case, you’d probably want to instruct the player to press any key, click a mouse button, or press a button on the gamepad to continue. It’s always a good idea to let players know what controls they can use so they don’t have to guess. Making players guess will always lead to unsatisfied gamers.

Compile and run the application now, and you’ll see a very simple splash screen (shown in Figure 8-9) that disappears when you press any key, at which point the game begins. Great job!

A very simple splash screen with a very scary message
Figure 8-9. A very simple splash screen with a very scary message

Now that you have a fancy, schmancy splash screen, it’s time to add the same type of screen at the end of the game. Before you do that, however, you’ll need to add logic that will actually make the game end.

Game-Over Logic and the Game-Over Screen

So, now you have to determine how your game will end. You already have an objective for the game: avoid the three- and four-blade sprites. But when is the game actually over? It seems a bit rough to end the game as soon as the user hits a single blade sprite. Instead, it might make the game a bit more enjoyable if the player had a certain number of lives to play with.

To accomplish this, first you’ll need to create a class-level variable in your Game1 class to keep track of the number of lives remaining, as well as a public property with get and set accessors to allow the SpriteManager to access and modify the value:

int numberLivesRemaining = 3;
public int NumberLivesRemaining
{
    get { return numberLivesRemaining; }
    set
    {
        numberLivesRemaining = value;
        if (numberLivesRemaining == 0)
        {            currentGameState = GameState.GameOver;
            spriteManager.Enabled = false;
            spriteManager.Visible = false;
        }
    }
}

Notice that when the property is set, its value is assigned to the numberLivesRemaining variable, and then that variable is checked to see whether its value is zero. If the value is zero, the game state is changed to GameState.GameOver and the SpriteManager is disabled and hidden. This allows you to decrement this value from the SpriteManager class and then, when the player is out of lives, have the game automatically shut down and enter a state in which you can display a game-over screen.

Now, not only do you want to keep track of the number of lives that a player has, but the player also needs to be able to see how many lives he has left at any given time.

Tip

Why show the number of lives remaining on the screen?

Again, this comes down to trying to make the game a more enjoyable experience for the player. If the player has to constantly keep track of the number of lives she has left on her own, it will detract from the gameplay experience. Anything you can do to help the player out by displaying important data (such as the score and the number of lives remaining) will go a long way toward letting her focus on the most important thing: having fun playing your game.

To display the number of lives remaining, you’ll draw one animated three rings sprite in the upper-left corner of the screen (below the score) for each life that the player has remaining.

To avoid confusion, you won’t want the sprites to be the same size as the actual sprite being controlled by the player, so you’ll have to add some code that will allow you to scale the sprites. Because these sprites won’t move on their own and the player won’t interact with them, you can use the AutomatedSprite class and specify a speed of (0, 0) to draw these objects.

In the Sprite class, add a class-level variable to represent the scale at which the sprite is supposed to be drawn:

protected float scale = 1;

Specifying a scale value of 1 will cause the object to be drawn at the original size of the sprite, so you should initialize it to that value. Next, you’ll need to change the Draw method in your Sprite class to use your newly added Scale variable for the scale parameter. Your Draw method should look like this:

public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
    spriteBatch.Draw(textureImage,
        position,
        new Rectangle(currentFrame.X * frameSize.X,
            currentFrame.Y * frameSize.Y,
            frameSize.X, frameSize.Y),
        Color.White, 0, Vector2.Zero,
        scale, SpriteEffects.None, 0);
}

Finally, you’ll need to add to the Sprite class a new constructor that will accept a scale value as a parameter:

public Sprite(Texture2D textureImage, Vector2 position, Point frameSize,
    int collisionOffset, Point currentFrame, Point sheetSize, Vector2 speed,
    string collisionCueName, int scoreValue, float scale)
    : this(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, defaultMillisecondsPerFrame, collisionCueName,
    scoreValue)
{
    this.scale = scale;
}

and add to the AutomatedSprite class a new constructor that will accept a scale parameter and pass that value on to the base class:

public AutomatedSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame, Point sheetSize,
    Vector2 speed, string collisionCueName, int scoreValue, float scale)
    : base(textureImage, position, frameSize, collisionOffset, currentFrame,
    sheetSize, speed, collisionCueName, scoreValue, scale)
{
}

Your AutomatedSprite class is now ready to be used to create the sprites that will display the number of lives remaining for the player. In the SpriteManager class, add a class-level variable to keep track of the sprites used for player lives:

List<AutomatedSprite> livesList = new List<AutomatedSprite>(  );

In the SpriteManager’s LoadContent method, you’ll need to fill the livesList list with a number of sprites equaling the number of lives a player begins with. In each frame, you’ll draw the list of items in the livesList variable in the upper-left corner of the screen. This will be a visual indicator to show the player how many lives she has remaining. To fill the list, create a loop that runs as many times as the player has lives, adding a new AutomatedSprite object to the list each time through the loop:

for (int i = 0; i < ((Game1)Game).NumberLivesRemaining; ++i)
{
    int offset = 10 + i * 40;
    livesList.Add(new AutomatedSprite(
        Game.Content.Load<Texture2D>(@"images	hreerings"),
        new Vector2(offset, 35), new Point(75, 75), 10,
        new Point(0, 0), new Point(6, 8), Vector2.Zero,
        null, 0, .5f));
}

The only complex thing going on in this code is the second parameter, which represents the position of the sprite. The parameter is of the type Vector2. Your goal in this list is to create a set of sprites that do not move and that are staggered in a row across the upper-left corner of the screen. The X portion of the parameter is first offset by 10 (so that the leftmost image is offset slightly from the edge of the screen) and then multiplied by 40 (so each image is drawn 40 units to the right of the previous image). The Y portion of the parameter is set to 35, to offset it just below the score text.

Now all that’s left to do is update your livesList objects each time Update is called in the SpriteManager class and draw your objects each time Draw is called.

To do this, add the following code at the end of the UpdateSprites method in your SpriteManager class:

foreach (Sprite sprite in livesList)
    sprite.Update(gameTime, Game.Window.ClientBounds);

and add the following code to the Draw method, just above the call to spriteBatch.End:

foreach (Sprite sprite in livesList)
    sprite.Draw(gameTime, spriteBatch);

Compile and run the game at this point, and you’ll see three sprites in the upper-left corner of the screen, indicating the number of lives the player has left (see Figure 8-10).

Three lives left!
Figure 8-10. Three lives left!

Good job! Now you just need to add some logic to remove a life when the player collides with a blade sprite and to display a game-over screen when the game ends.

Removing one of the life sprites is pretty straightforward. You have code that detects collisions between the player and the moving sprites on the screen. When such a collision occurs, you need to check the type of the sprite that collided with the player: if the type is AutomatedSprite, you’ll remove a life sprite from the end of the list of life sprites. Make sure you remove the sprite from the end of the list, because you inserted them in order from left to right.

In addition to removing a sprite from the list of sprites representing lives remaining, you’ll need to decrement the value of the numberLivesRemaining variable from the Game1 class by using its accessor.

The code for the collision checks is located in your SpriteManager’s UpdateSprites method. In that method, you have logic to remove sprites in two cases: when a sprite collides with the player and when a sprite leaves the game screen. Both cases use the spriteList.RemoveAt method to remove the sprite from the game. Search for the two instances of spriteList.RemoveAt within the UpdateSprites method of the SpriteManager class, and find the one used for collisions (you’ll see code used to play collision sounds nearby). Add the following code to the method, just before the code to remove the sprite when a collision occurs:

if (s is AutomatedSprite)
{
    if (livesList.Count > 0)
    {
        livesList.RemoveAt(livesList.Count - 1);
        --((Game1)Game).NumberLivesRemaining;
    }
}

For clarity, the entire UpdateSprites method is posted here, with the added code marked in bold so you can see exactly where to put that code:

protected void UpdateSprites(GameTime gameTime)
{
    // Update player
    player.Update(gameTime, Game.Window.ClientBounds);

    // Update all nonplayer sprites
    for (int i = 0; i < spriteList.Count; ++i)
    {
        Sprite s = spriteList[i];

        s.Update(gameTime, Game.Window.ClientBounds);

        // Check for collisions
        if (s.collisionRect.Intersects(player.collisionRect))
        {            // Play collision sound
            if (s.collisionCueName != null)
                ((Game1)Game).PlayCue(s.collisionCueName);

            // If collided with AutomatedSprite,
            // remove a life from the player
            if (s is AutomatedSprite)
            {
                if (livesList.Count > 0)
                {
                    livesList.RemoveAt(livesList.Count - 1);
                    --((Game1)Game).NumberLivesRemaining;
                }
            }

            // Remove collided sprite from the game
            spriteList.RemoveAt(i);
            −−i;
        }

        // Remove object if it is out of bounds
        if (s.IsOutOfBounds(Game.Window.ClientBounds))
        {
            ((Game1)Game).AddScore(spriteList[i].scoreValue);
            spriteList.RemoveAt(i);
            −−i;
        }

    }

    // Update lives-list sprites
    foreach (Sprite sprite in livesList)
        sprite.Update(gameTime, Game.Window.ClientBounds);

}

If you run the game now, you’ll notice that a life is removed every time you run into a three- or four-blade sprite. When all your lives are used up, the game will appear to freeze. It actually isn’t frozen, though; it’s simply entered a game state in which you aren’t doing anything (GameState.GameOver). The last step in this section is to create a game-over screen, similar to the splash screen you created earlier.

First, in the Update method of the Game1 class, add some code that will allow the player to close the game window when the game is in the game-over state. Here, you’ll close the game when the player presses the Enter key. Add the following code to detect when the Enter key is pressed and to call the Exit( ) method, which will shut down the game entirely (if you added support for starting the game by pressing a mouse or gamepad button, you should probably add similar input support here to close the game as well):

case GameState.GameOver:
    if (Keyboard.GetState(  ).IsKeyDown(Keys.Enter))
        Exit(  );
    break;

To clarify, your Game1 class’s Update method should now look something like this (changes are in bold):

protected override void Update(GameTime gameTime)
{
    // Only perform certain actions based on
    // the current game state
    switch (currentGameState)
    {
        case GameState.Start:
            if (Keyboard.GetState().GetPressedKeys().Length > 0)
            {
                currentGameState = GameState.InGame;
                spriteManager.Enabled = true;
                spriteManager.Visible = true;
            }
            break;
        case GameState.InGame:
            break;
        case GameState.GameOver:
            if (Keyboard.GetState().IsKeyDown(Keys.Enter))
                Exit();
            break;
    }

    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
      ButtonState.Pressed)
        this.Exit();

    //Update audio
    audioEngine.Update();

    base.Update(gameTime);
}

Now add some code that will draw a game-over message when the game is in the game-over state. Of course, because you’re going to be drawing, you’ll do this in the Draw method of the Game1 class. In the game-over case of the switch statement, add the following code:

case GameState.GameOver:
    GraphicsDevice.Clear(Color.AliceBlue);

    spriteBatch.Begin(  );
    string gameover = "Game Over! The blades win again!";
    spriteBatch.DrawString(scoreFont, gameover,
        new Vector2((Window.ClientBounds.Width / 2)
        - (scoreFont.MeasureString(gameover).X / 2),
        (Window.ClientBounds.Height / 2)
        - (scoreFont.MeasureString(gameover).Y / 2)),
        Color.SaddleBrown);    gameover = "Your score: " + currentScore;
    spriteBatch.DrawString(scoreFont, gameover,
        new Vector2((Window.ClientBounds.Width / 2)
        - (scoreFont.MeasureString(gameover).X / 2),
        (Window.ClientBounds.Height / 2)
        - (scoreFont.MeasureString(gameover).Y / 2) + 30),
        Color.SaddleBrown);

    gameover = "(Press ENTER to exit)";
    spriteBatch.DrawString(scoreFont, gameover,
        new Vector2((Window.ClientBounds.Width / 2)
        - (scoreFont.MeasureString(gameover).X / 2),
        (Window.ClientBounds.Height / 2)
        - (scoreFont.MeasureString(gameover).Y / 2) + 60),
        Color.SaddleBrown);

    spriteBatch.End(  );
    break;

This code will draw three lines of text on the screen: a message indicating that the game is over, a message indicating that to exit the player should press the Enter key, and a message showing the player’s final score. The game-over screen will look something like Figure 8-11.

100 points—not too shabby!
Figure 8-11. 100 points—not too shabby!

Fine-Tuning Gameplay

With any game that you develop, you will want to tweak things during game testing to ensure that the game plays the way that you intend and is challenging but fun at the same time. The biggest factor is to make sure that the game is entertaining to play. If you’re just making the game for yourself, obviously that will be your call. If, however, you’re developing it for a wider audience, it’s important to get feedback from that user base sooner rather than later.

In this case, one thing you might want to tweak is related to the mouse movement that you’ve built into the game. You may have noticed that playing with the mouse is much easier than playing with the keyboard keys. To make the game more challenging and to force the user to use an input form that maintains a constant speed for the player sprite, try removing mouse support (I’d recommend leaving support for the gamepad and keyboard input in place).

To remove support for the mouse, comment out or delete the mouse-movement code located in the Update method of the UserControlledSprite class:

// COMMENTED-OUT MOUSE SUPPORT
// If the mouse moved, set the position of the sprite to the mouse position
// MouseState currMouseState = Mouse.GetState(  );
// if (currMouseState.X != prevMouseState.X ||
//     currMouseState.Y != prevMouseState.Y)
// {
//     position = new Vector2(currMouseState.X, currMouseState.Y);
// }
// prevMouseState = currMouseState;

You should also comment out the class-level prevMouseState variable in the UserControlledSprite class:

// COMMENTED-OUT MOUSE SUPPORT
// MouseState prevMouseState;

Prior to removing the mouse support for the game, the initial player sprite position was set to the position of the mouse cursor. That won’t work anymore, so you’ll want to start the player in the middle of the screen. You create the player object in the LoadContent method of the SpriteManager class, and in the constructor for the player object you pass in Vector2.Zero as the parameter for the position of the object (the second parameter in the list). Change that code so you pass in the middle of the screen as the initial position of the player object. Your initialization code for the player object in the LoadContent method of the SpriteManager class should now look like this:

player = new UserControlledSprite(
    Game.Content.Load<Texture2D>(@"Images/threerings"),
    new Vector2(Game.Window.ClientBounds.Width / 2,
        Game.Window.ClientBounds.Height / 2),
    new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), new Vector2(6, 6));

Another aspect of the gameplay experience that you’ll probably want to tweak is to make the game increasingly more difficult to play. As the game stands at this point, players can play virtually forever because the game just isn’t very challenging.

How do you make the game more difficult? Well, there are a lot of ways. You could make the blade sprites in the game move progressively faster, or you could spawn different types of sprites that are more and more difficult to avoid. Or you could use a combination of those approaches, or do something totally different. The key here is to be creative. This is video game development, and fresh and new ideas are what make great games. Feel free to play with the game and think about what you could do to make the experience more entertaining.

For the purposes of this book, we’re going to make the sprites spawn more and more often in order to make the game progressively harder. You already have two variables that determine a minimum and maximum spawn time for each new sprite (enemySpawnMinMilliseconds and enemySpawnMaxMilliseconds in the Game1 class). These variables are set to 1,000 and 2,000 milliseconds, respectively (in other words, a new sprite is spawned every 1 to 2 seconds).

You don’t want to decrease the spawn times every frame, because with the game running at 60 frames per second, the rate of change would be too quick to make things interesting. Instead, create a couple of new class-level variables in the SpriteManager class that you can use to decrease the spawn time every so often (in this case, every second):

int nextSpawnTimeChange = 5000;
int timeSinceLastSpawnTimeChange = 0;

These variables may look familiar because this is the same concept you used when experimenting with animation speeds. Basically, you’ll add some code in the Update method of the Game1 class that will add the elapsed time to the timeSinceLastSpawnTimeChange variable. When that variable’s value is greater than the value of the nextSpawnTimeChange variable (which will occur after every 5 seconds of gameplay because nextSpawnTimeChange is set to 5,000 milliseconds), you’ll decrease the values of both of the spawn timer variables (enemySpawnMinMilliseconds and enemySpawnMaxMilliseconds).

However, you don’t want to decrease these values indefinitely. If the spawn time values reached zero, a new sprite would be generated every frame—that’s 60 sprites generated every second. There’s no way anybody could ever keep up with that. To avoid this scenario, you’ll cap off the spawn time at 500 milliseconds.

Create a new method in the SpriteManager class that will adjust the spawning frequency variables, making enemy sprites spawn more and more frequently as the game progresses:

protected void AdjustSpawnTimes(GameTime gameTime)
{
    // If the spawn max time is > 500 milliseconds,
    // decrease the spawn time if it is time to do
    // so based on the spawn-timer variables
    if (enemySpawnMaxMilliseconds > 500)
    {
        timeSinceLastSpawnTimeChange += gameTime.ElapsedGameTime.Milliseconds;
        if (timeSinceLastSpawnTimeChange > nextSpawnTimeChange)
        {
            timeSinceLastSpawnTimeChange −= nextSpawnTimeChange;
            if (enemySpawnMaxMilliseconds > 1000)
            {
                enemySpawnMaxMilliseconds −= 100;
                enemySpawnMinMilliseconds −= 100;
            }
            else
            {
                enemySpawnMaxMilliseconds −= 10;
                enemySpawnMinMilliseconds −= 10;
            }
        }
    }
}

In this code, the interior if/else statement causes the spawning increments to be decreased rapidly (subtracting 100 each time the spawn times are decremented) until the max spawn time is less than 1 second (1,000 milliseconds). After that, the spawn time continues to decrease until it reaches a max spawn time of 500 milliseconds, but it decreases at a much slower rate (subtracting only 10 each time the spawn times are decremented). Again, this is just another part of tweaking the gameplay experience. This particular method of changing the spawn time will cause the game to get tougher and more interesting fairly quickly, and then slowly get harder and harder until the game is tough enough to pose a challenge to most players.

You’ll need to add a call to the new AdjustSpawnTimes method from within the Update method of your SpriteManager class. Add the following line to the Update method immediately before the call to base.Update:

AdjustSpawnTimes(gameTime);

Compile and run the game at this point, and you’ll find that the longer you stay alive, the harder the game will become. At some point, you will no longer be able to keep up, and the game will end. See Figure 8-12 for a view of how convoluted the game can get once it really starts rolling.

Ahhh!!! It’s getting crowded in here!
Figure 8-12. Ahhh!!! It’s getting crowded in here!

Creating Power-Ups

We’ll add one last thing to the game in this chapter, and then it will be up to you to fine-tune and tweak it further. You have three sprites that don’t do anything at this point: the skull ball, the plus, and the bolt. These sprites are meant not to take away a player’s life when they collide with the player’s object, but rather to have some positive or negative effect on the player.

As in previous sections in this chapter, we’re really getting into the creative aspect of game development here, and if there’s something you feel would add a great deal of entertainment to the game, you should look at adding that functionality. As you consider implementing things like power-ups, there really is no limit to what you can do.

For the purposes of this book, the effects that these objects will have when they collide with the player object are as follows:

  • The bolt causes the player object to move at 200% of its current speed for 5 seconds.

  • The skull ball causes the player object to move at 50% of its current speed for 5 seconds.

  • The plus causes the player object to be 200% larger than its current size for 5 seconds.

Essentially, a power-up (or power-down, if the effect is negative to the player) will run for a given number of seconds, and then the effect will wear off. In this game, the effects will stack, meaning that you can be influenced by more than one effect at a given time, and you can be influenced by the same effect multiple times (for instance, if you hit two skull balls back to back, you’ll be running at 25% of your normal speed).

Tip

Should the effects always stack? That’s completely your call. You’re the developer, and you own this virtual world. You want to make the game fun to play and challenging. Again, experiment with different effects and decide what you like and what works for you.

To create these effects, you’ll need to add some code to your Sprite base class. First, you’ll need to add a variable that will hold the initial value of the scale property. Currently, you’re initializing scale to 1, but if the player collides with a plus sprite, the value of scale will double. After that power-up expires, you’ll have to set scale back to its original value. Rather than hardcoding the value 1 in at that point, it would be better to use a variable representing the original value. Add the following class-level variable to the Sprite class:

protected float originalScale = 1;

Next, you’ll need to add a method that will allow the SpriteManager to increase or decrease the value of the scale variable, as well as a method that the SpriteManager can call to reset the scale variable to its original value. Add the following methods to the Sprite class:

public void ModifyScale(float modifier)
{
    scale *= modifier;
}
public void ResetScale(  )
{
    scale = originalScale;
}

Pause here for a second and answer this question: what effect will changing the scale have on your collision detection? That’s right, it’s going to get messed up. You’ll have changed the scale, but the collision detection will still be using the frameSize and collisionOffset properties, which hold the original values for the size of the frame and the collision offset. What can you do about this? Luckily, your SpriteManager uses an accessor called CollisionRect that builds a Rectangle for collision detection. You can modify that accessor to use the scale property to correctly build the Rectangle when a different scale factor is applied. Change the collisionRect property in your Sprite base class as follows:

public Rectangle collisionRect
{    get
    {
        return new Rectangle(
            (int)(position.X + (collisionOffset * scale)),
            (int)(position.Y + (collisionOffset * scale)),
            (int)((frameSize.X - (collisionOffset * 2)) * scale),
            (int)((frameSize.Y - (collisionOffset * 2)) * scale));
    }
}

Now you’ll need to do the same sort of thing for the Speed variable. First, add to the Sprite class a class-level variable that will hold the original speed:

Vector2 originalSpeed;

Because you’re initializing speed in the constructors via a parameter rather than instantiating it to a constant value, you don’t need to initialize the originalSpeed variable to anything specific. You will, however, need to initialize it in the constructor that sets the speed variable. Add the following code just below the line in the constructor that reads this.speed = speed:

originalSpeed = speed;

Next, add two methods (just as you did for the scale variable) that will allow the SpriteManager to modify and reset the Speed variable of the Sprite class:

public void ModifySpeed(float modifier)
{
    speed *= modifier;
}
public void ResetSpeed(  )
{
    speed = originalSpeed;
}

Oh yeah, that’s good stuff so far. Now you’re ready to make the final changes. Open the SpriteManager class, as you’ll be making some changes in that class next.

Every time the player hits one of these special sprites, a power-up timer needs to be set to let that power-up run for 5 seconds. Add a class-level variable to the SpriteManager class to keep track of when power-ups should expire:

int powerUpExpiration = 0;

Next, find the UpdateSprites method. In that method, you check to see whether the player has collided with a sprite. Previously, you added a check to see whether a collision with an AutomatedSprite object had occurred and, if so, you removed a life from the player. Now you’re going to handle the other types of collisions that are possible. The easiest way to determine the type of sprite that was hit if it wasn’t an AutomatedSprite is by examining the CollisionCue property of the sprite. Remember that a sprite’s CollisionCue property determines which sound is played in a collision with that object, and therefore that sound property is unique for each different type of sprite.

Your code should have an if statement that looks like the following:

if (s is AutomatedSprite)
{
    if (livesList.Count > 0)
    {
        livesList.RemoveAt(livesList.Count - 1);
        --((Game1)Game).NumberLivesRemaining;
    }
}

Add the following code after that first if statement (the if(s is AutomatedSprite) statement, not the if(LivesList.Count > 0) statement) to create the power-up effect and set the power-up expiration timer to 5 seconds (5,000 milliseconds):

else if (s.collisionCueName == "pluscollision")
{
    // Collided with plus - start plus power-up
    powerUpExpiration = 5000;
    player.ModifyScale(2);
}
else if (s.collisionCueName == "skullcollision")
{
    // Collided with skull - start skull power-up
    powerUpExpiration = 5000;
    player.ModifySpeed(.5f);
}
else if (s.collisionCueName == "boltcollision")
{
    // Collided with bolt - start bolt power-up
    powerUpExpiration = 5000;
    player.ModifySpeed(2);
}

This code will perform some action that starts a power-up effect on the player based on the collision cue of the colliding sprite. In addition, it will set the powerUpExpiration variable to 5000 to indicate that the power-up timer has started and has 5 seconds remaining.

The last thing you’ll need to do is decrement the expiration timer every time the Update method of the SpriteManager class is called. Add the following method to the SpriteManager class:

protected void CheckPowerUpExpiration(GameTime gameTime)
{
    // Is a power-up active?
    if (powerUpExpiration > 0)
    {
        // Decrement power-up timer
        powerUpExpiration −=
            gameTime.ElapsedGameTime.Milliseconds;
        if (powerUpExpiration <= 0)
        {
            // If power-up timer has expired, end all power-ups
            powerUpExpiration = 0;
            player.ResetScale(  );
            player.ResetSpeed(  );
        }
    }
}

Then, call that method from within your Update method, just before the base.Update call:

CheckPowerUpExpiration(gameTime);

This new method will subtract the elapsed game time from the power-up timer at every frame, and when the power-up timer reaches zero, it will reset the scale and speed of the player.

Compile and run the game now, and you’ll find that you have a complete 2D game written in XNA. It’s basic, but pretty fun. Again, you can tweak gameplay issues such as speeds, spawn rates, power-ups, and so on to your liking and customize the game to make it play the way you feel it should.

While playing, notice how the power-up effects stack. For example, if you hit a plus sprite and then hit a skull sprite before the plus effect wears off, both effects are added to your player sprite. The same thing happens with similarly typed sprites; for instance, if you hit a plus and then another plus before the effect from the first plus wears off, your player sprite will now be at 400% of its normal size (normal size × 2 × 2).

The tricky thing is that even though you can stack power-up effects, there is only one power-up timer. This means that if you have two or three or more effects on your player sprite, they won’t expire at different times. Instead, every time you hit a power-up, the power-up timer will be reset to 5 seconds. Only if 5 seconds pass without you hitting any other power-ups will all the accumulated power-up effects expire.

This is by design, but again, this is a great place for you to examine how you think the game should be played. If you think a different rule for power-ups should be in place, make it happen! You’re the developer, and you’re the king of this virtual world, right? You might as well take advantage of that and make the game play exactly how you think it should.

What You Just Did

Wow. Maybe this should say, “What didn’t you do?” This was a long chapter, but you did some great stuff. Let’s take a look:

  • You learned how to draw 2D text on the screen.

  • You randomly generated sprites of different types.

  • You added a background image.

  • You fleshed out a system to keep score.

  • You experimented with game states and implemented three states (start, in-game, and end).

  • You added splash and game-over screens using game states.

  • You added power-up effects and fine-tuning logic to your game.

Summary

  • 2D fonts are drawn on the screen just the same as any Texture2D object.

  • 2D fonts are built using the SpriteFont object.

  • Background images are added to games by using a sprite that covers the entire screen.

  • Game states are breaks in gameplay or transitions in gameplay from one state to another (for example, moving from one level to another, accomplishing a mission, losing the game, or something similar in concept).

  • Game development is a very creative business. While the mechanics are very scientific, as in all programming, the actual fine-tuning of the game is a very art-oriented craft that is heavily centered on how the gameplay feels to a user. As a developer, you should play with your game as early and often as possible so that you can tweak the experience into something that you enjoy and that you envision others will enjoy as well.

  • “Our mental discipline is matched only by our skill in XNA…I only hope these are enough to withstand this awful trial.” —Akara

Test Your Knowledge: Quiz

  1. What type of object is used to draw 2D text in XNA?

  2. How is a background image different from an image used to represent a player or object in the game?

  3. What are game states and how are they used?

  4. In the Flight of the Conchords episode “Mugged,” what do the muggers steal from Jemaine?

Test Your Knowledge: Exercise

Change the behavior of the skull power-up (or power-down, if you prefer) to freeze the player for 2 seconds rather than reduce the player’s speed by 50% for 5 seconds. Use different power-up timers for the skull, bolt, and plus sprites.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.60.62