baldwin.jpg

Programming with XNA Game Studio

A Simple Game Program with On-Screen Text

Learn how to write a simple game program involving user input from the keyboard and the mouse. Also learn how to create on-screen text.

Published: January 27, 2010
Validated with Amaya

By Richard G. Baldwin

XNA Programming Notes # 0132


Preface

General

XNA Game Studio 3.1
Note that all references to XNA in this lesson are references to version 3.1 or later.

This tutorial lesson is part of a continuing series dedicated to programming with the XNA Game Studio. I am writing this series of lessons primarily for the benefit of students enrolled in an introductory XNA game programming course that I teach. However, everyone is welcome to study and benefit from the lessons.

An earlier lesson titled Getting Started provided information on how to get started programming with Microsoft's XNA Game Studio. (See Baldwin's XNA programming website in Resources.)

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

Listings

Supplemental material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

General background information

When we finished the previous lesson (see Resources), we were missing one critical element needed to write a simple game program: user input. We were also lacking some other tools that would make it possible for us to create more interesting 2D arcade-style games including the ability to create on-screen text.

In this lesson, you will learn how to program for user input with the keyboard and the mouse. You will also learn how to create on-screen text.

Preview

The program that I will explain in this lesson is a simple game in which ten spiders attempt to traverse their web from top to bottom. Two large ladybugs are there to eat them if possible. (See Figure 1.) The objective of the game is to prevent as many of the spiders as possible from making it to the bottom of the game window.

Figure 1 shows a reduced view of the game window shortly after the game is started.

Figure 1 Reduced view of the game window.
Reduced view of the game window.

Spider behavior

The spiders move at different speeds in different directions but generally towards the southeast. If a spider makes it to the bottom, it is counted as having crossed the finish line. That spider wraps back up to the top and starts the journey again. If a spider hits the right edge of the window, it wraps around to the left edge and continues the journey.

The ladybugs

The movements of the ladybugs are under the control of the player. If the player causes a ladybug to collide with a spider, the spider is eaten and removed from the spider population.

Using the keyboard

The player can move the ladybugs using either the keyboard or the mouse. Pressing the four arrow keys causes one of the ladybugs to move in the corresponding directions. Pressing two arrow keys at the same time causes the ladybug to move in a diagonal direction.

Holding down the A key and pressing the arrow keys causes the other ladybug to move.

Using the mouse

Pressing the left mouse button makes it possible to drag a ladybug with the mouse. Pressing the right mouse button makes it possible to drag the other ladybug with the mouse. Pressing both mouse buttons makes it possible to drag both ladybugs with the mouse.

Difficulty level

The game has only one level. I personally find it very difficult to prevent all ten spiders from making it to the bottom using the arrow keys only. On the other hand, it is easy to defeat the spiders by dragging a ladybug with the mouse.

A useful student project

A useful student project would be to upgrade the program to provide multiple levels at varying levels of difficulty. This could be accomplished by changing the speed of the spiders, changing the number of spiders, or a combination of the two.

Alternatives to the arrow keys

Another useful student project would be to define alternatives to the arrow keys, such as the keys labeled 4, 8, 6, and 2 on the keypad, or four keys in a diamond pattern on the left side of the keyboard.

Discussion and sample code

Will discuss in fragments

As usual, I will break this program down and explain it in fragments. However, I will only explain those portions of the code that are substantially different from code that I have explained in earlier lessons.

Complete listings of the Sprite class and the Game1 class are provided in Listing 13 and Listing 14 near the end of the lesson.

Before getting into the game program, however, I will explain a very short program that illustrates how to draw on-screen text. I personally found the documentation on this topic to be somewhat confusing.

How to draw on-screen text

The difficulty that I encountered in drawing on-screen text was not in the writing of program code. Rather, it was in getting ready to write program code.

Font availability

Before getting into the details, I want to mention that fonts are typically copyrighted items of intellectual property and you must be careful when distributing fonts for which you don't have distribution rights. To assist you in this regard, the information shown in Figure 2 currently appears in the documentation.

Figure 2 Information on fonts from the XNA documentation.

The following list of fonts are installed by XNA Game Studio and are redistributable:
  • Kooten.ttf
  • Linds.ttf
  • Miramo.ttf
  • Bold Miramob.ttf
  • Peric.ttf
  • Pericl.ttf
  • Pesca.ttf
  • Pescab.ttf

These OpenType fonts, created by Ascender Corporation and licensed by Microsoft, are free for you to use in your XNA Game Studio game. You may redistribute these fonts in their original format as part of your game. These fonts were previously available only on the XNA Creators Club Online website.

Identifying the font families

You should be able to select Fonts in the WindowsControl Panel and see a list of the font families that are installed on your machine. The problem is that the names of the font families are not an exact match for the file names listed in Figure 2.

Matching font families to font files

Figure 3 shows my best guess as to the correspondence between font-family names and the file names listed in Figure 2. Font family names are on the left and font file names are on the right.

Figure 3 Correspondence between font-family names and font file names.

  • Kootenay - Kooten.ttf
  • Lindsey - Linds.ttf
  • Miramonte - Miramo.ttf
  • Miramonte Bold - Bold Miramob.ttf
  • Pericles - Peric.ttf
  • Pericles Light - Pericl.ttf
  • Pescadero - Pesca.ttf
  • Pescadero Bold -Pescab.ttf

You will learn shortly why we care about the font-family names.

Getting ready to draw on-screen text

Before you can draw on-screen text you must add a Sprite Font to your project from the IDE during the design process.

Add a Sprite Font to your project

Here is my interpretation of the steps required to add the Sprite Font to your project:

Drawing the on-screen text

I will explain the code in the Game1 class of the project named XNA0132ProjA that produced the screen output shown in reduced form in Figure 4.

Figure 4 Demonstration of on-screen text with the Lindsey font.
Demonstration of onscreen text

As usual, I will explain the code in fragments. A complete listing of the Game1 class is provided in Listing 12 near the end of the lesson.

Programming steps for displaying on-screen text

Here is my interpretation of the coding steps for displaying on-screen text in the game window.

Beginning of the Game1 class and the overridden LoadContent method

Listing 1 shows the beginning of the Game1 class and the overridden LoadContent method.

Listing 1. Beginning of the Game1 class and the overridden LoadContent method for the project named XNA0132ProjA.

namespace XNA0132ProjA {

  public class Game1 : Microsoft.Xna.Framework.Game {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont Font1;
    Vector2 FontPos;
    //-------------------------------------------------//

    protected override void LoadContent() {
      spriteBatch = new SpriteBatch(GraphicsDevice);

      //Create a new SpriteFont object.
      Font1 = Content.Load<SpriteFont>("Lindsey");

      //Create a new Vector2 object to center the text
      // in the game window.
      FontPos = new Vector2(
            graphics.GraphicsDevice.Viewport.Width / 2,
            graphics.GraphicsDevice.Viewport.Height / 2);
    }//end LoadContent method

No unusual code

There is no unusual code in Listing 1. However, if my memory serves me correctly, this is the first time in this series of lessons that I have called the Content.Load method to load a resource other than an image.

Beginning of the overridden Game.Draw method

The Game.Draw method begins in Listing 2.

Listing 2. Beginning of the overridden Game.Draw method.

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

      spriteBatch.Begin();

      // Draw Lindsey Font
      string output = "Lindsey Font";

      // Find the center of the string
      Vector2 FontOrigin = 
                         Font1.MeasureString(output) / 2;

There is nothing new or unusual in Listing 2.

Remainder of the overridden Game.Draw method

The remainder of the Game.Draw method is shown in Listing 3.

Listing 3. Remainder of the overridden Game.Draw method.

      //Draw the string with the FontOrigin at the 
      // position specified by FontPos.
      spriteBatch.DrawString(
          Font1,//font to use
          output,//output text
          FontPos,//position re upper left corner
          Color.LightGreen,//color of text
          -45,//rotation of text
          FontOrigin,//place this at FontPos
          2.5f,//scale
          SpriteEffects.None,
          0.5f);//z-order layer

      spriteBatch.End();

      base.Draw(gameTime);
    }//end Draw method

There is nothing unusual in the code in Listing 3. However, this is the first time in this series of lessons that I have called the SpriteBatch.DrawString method.

The SpriteBatch.DrawString method

There are six overloaded versions of the SpriteBatch.DrawString method. The different versions depend on the features provided by the parameters. The version called in Listing 3 is one of the most complex. Here is part of what the documentation has to say about this version of the DrawString method:

"Adds a mutable sprite string to the batch of sprites to be rendered, specifying the font, output text, screen position, color tint, rotation, origin, scale, and effects.

Parameters

Purpose of the origin parameter

Everything in this description is reasonably clear except for the description of the origin parameter so I will attempt to clarify it. The origin specifies a point within the text string relative to its upper left corner that will be drawn at the point in the game window specified by the position parameter.

As much as I need to say

That is probably as much as I need to say about the on-screen display of text. It's time to move on to an explanation of our simple game program beginning with the Sprite class.

The Sprite class

The Sprite class that I used in this program is very similar to the class that I explained in the previous lesson. Therefore, I won't have a lot to say about the Sprite class in this lesson. A complete listing of the Sprite class is provided in Listing 13 near the end of the lesson.

Behavior of the Sprite class

This version of the Sprite class supports collision detection based on intersecting rectangles. It also provides a new Edge property that records and returns the edge number (1, 2, 3, or 4) if a sprite collides with an edge of the game window. However, the edge information is available for only one iteration of the game loop before it changes back to the normal value of 0.

The value of the Edge property temporarily changes to 1, 2, 3, or 4 if a sprite collides with the top, right, bottom, or left edge of the game window respectively.

Since the information regarding the collision with an edge is the only change to this Sprite class relative to the version that I explained in a previous lesson, that is all I will discuss about the Sprite class in this lesson.

The new code

The first manifestation of change in this version of the Sprite class is the simple declaration of an instance variable of type int named edge near the beginning of the class definition. You can view that statement in Listing 13.

A new read-only property accessor method for the Edge property

The next manifestation is the read-only property accessor method for the new Edge property shown in Listing 4.

Listing 4. A new read-only property accessor method for the Edge property for the Sprite class.

    public int Edge {
      get {
        return edge;
      }//end get
    }//end Edge property accessor

New version of the Move method

Listing 5 shows the new version of the Move method. The additions that were made to the method are highlighted in yellow.

Listing 5. New version of the Move method.

    //This method causes the sprite to move in the 
    // direction of the direction vector if the elapsed
    // time since the last move exceeds the elapsed
    // time target based on the specified speed.
    public void Move(GameTime gameTime) {

      //New to this version
      //Clear the Edge property value. Edge information
      // is available for only one iteration of the 
      // game loop.
      edge = 0;

      //Accumulate elapsed time since the last move.
      elapsedTime +=
                  gameTime.ElapsedGameTime.Milliseconds;

      if(elapsedTime > elapsedTimeTarget) {
        //It's time to make a move. Set the elapsed 
        // time to a value that will attempt to produce
        // the specified speed on the average.
        elapsedTime -= elapsedTimeTarget;

        //Add the direction vector to the position
        // vector to get a new position vector.
        position = Vector2.Add(position,direction);

        //Check for a collision with an edge of the game
        // window. If the sprite reaches an edge, cause 
        // the sprite to wrap around and reappear at the 
        // other edge, moving at the same speed in a 
        // different direction within the same quadrant 
        // as before. Also set the Edge property to
        // indicate which edge was involved. 1 is top, 2
        // is right, 3 is bottom, and 4 is left.
        // Note that the Edge property will be cleared
        // to 0 the next time the Move method is called.
        if(position.X < -image.Width) {
          position.X = windowSize.X;
          edge = 4;//collision with the left edge - new
          NewDirection();
        }//end if

        if(position.X > windowSize.X) {
          position.X = -image.Width / 2;
          edge = 2;//collision with the right edge - new
          NewDirection();
        }//end if

        if(position.Y < -image.Height) {
          position.Y = windowSize.Y;
          edge = 1;//collision with the top - new
          NewDirection();
        }//end if

        if(position.Y > windowSize.Y) {
          position.Y = -image.Height / 2;
          edge = 3;//collision with the bottom - new
          NewDirection();
        }//end if on position.Y
      }//end if on elapsed time
    }//end Move

There is nothing unusual about the code that was added to the Move method, so it shouldn't require further explanation beyond the embedded comments.

End of discussion of the Sprite class

There were no additional changes made to the Sprite class other than those identified above, so that will end the discussion of the Sprite class.

The Game1 class

This project demonstrates how to write a simple 2D arcade style game.

Ten spiders try to make it from the top to the bottom of a spider web.

The bottom of the web is guarded by two large ladybugs that are intent on eating the spiders.

If a ladybug collides with a spider, the spider gets eaten by the ladybug and removed from the population of spiders. If the player plays long enough, all of the spiders should eventually be eaten.

The objective of the game

The objective of the game is for the player to cause as many spiders as possible to be eaten by the ladybugs before they make it to the bottom of the game window.

Ladybug control

The ladybugs can be moved by the player using either the keyboard or the mouse. Pressing the arrow keys moves one of the ladybugs. Holding down the A key and pressing the arrow keys moves the other ladybug.

The player can drag one of the ladybugs with the mouse pointer by pressing the left mouse button. The player can drag the other ladybug by pressing the right mouse button.

Spiders are recycled

Even though the game begins with only ten spiders, those that are not eaten before they reach the bottom of the game window are recycled. If a spider makes it from the top to the bottom of the game window without being eaten, the spider wraps around and appears back at the top of the game window ready to start the trip over.

Keeping score

A counter keeps track of the number of spiders that cross the bottom of the game window. On-screen text displays that number as the game progresses as shown in Figure 1. Note, however, that if two or more spiders cross the bottom edge of the game window during the same iteration of the game loop, only one will be counted. In hindsight, therefore, the elapsed time required to cause the ladybugs to eat all of the spiders might be a better scorekeeping mechanism than counting spider crossings.

A good student project

Modifying the program to keep score on the basis of elapsed time might make a good student project.

Beginning of the Game1 Class

Listing 6 shows the beginning of the Game1 class along with the constructor in abbreviated form. By abbreviated form, I mean that much of the code is very similar to code that I have explained in earlier lessons so I deleted that code from Listing 6 for brevity. You can view all of the code for the Game1 class in Listing 14.

Listing 6. Abbreviated beginning of the Game1 Class for the XNA0132Proj project

namespace XNA0132Proj {

  public class Game1 : Microsoft.Xna.Framework.Game {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    //code deleted for brevity

    //The following variable is used to count the number
    // of spiders that make it past the ladybugs and
    // reach the bottom of the game window.
    int spiderCount = 0;

    SpriteFont Font1;//font for on-screen text
    Vector2 FontPos;//position of on-screen text
    //-------------------------------------------------//

    public Game1() {//constructor
      //code deleted for brevity
    }//end constructor

The code with the yellow highlight in Listing 6 is new to this lesson.

The overridden LoadContent method

An abbreviated listing of the LoadContent method is shown in Listing 7. Code that is new to this lesson is highlighted in magenta, yellow, orange, or cyan.

Listing 7. Abbreviated overridden LoadContent method.

    protected override void Update(GameTime gameTime) {
      spriteBatch = new SpriteBatch(GraphicsDevice);
      Font1 = Content.Load<SpriteFont>(@"Kootenay");
      IsMouseVisible = true;//make mouse visible

      //code deleted for brevity

      //Position the on-screen text.
      FontPos = new Vector2(windowWidth / 2, 50);

      //Position the mouse pointer in the center of the
      // game window.
      Mouse.SetPosition(windowWidth / 2,windowHeight /2);

    }//end LoadContent
        

The font family

To begin with, note that this program displays text on the screen using the font family named Kootenay (see Figure 3 for a reference to this font-family name).

Make the mouse pointer visible

By default, the mouse pointer is not visible when it is in the game window. The code highlighted in yellow, which sets the inherited variable named IsMouseVisible to true, causes the mouse pointer to be visible.

Position the on-screen text

The code highlighted in cyan causes the text to be centered horizontally, 50 pixels down from the top of the game window as shown in Figure 1.

Set the initial position of the mouse pointer

The code that is highlighted in orange causes the initial position of the mouse pointer to be centered in the game window. (I will have more to say about this later.)

End of the LoadContent method

Listing 7 also signals the end of the overridden LoadContent method.

The overridden Update method

An abbreviated listing of the overridden Update method begins in Listing 8.

Listing 8. Beginning of an abbreviated overridden Update method.

    protected override void Update(GameTime gameTime) {

      //code deleted for brevity

      //Check to see if any spiders have made it to the
      // bottom edge.
      for(int cnt = 0;cnt < spiders.Count;cnt++) {
        if(spiders[cnt].Edge == 3)
          //One or more made it to the bottom edge.
          spiderCount += 1;
      }//end for loop

The code that is highlighted in yellow counts the number of spiders that make it to the bottom and collide with the bottom edge of the game window.

Moving the ladybug objects using the keyboard

The code required to service the keyboard is long, tedious, and repetitive, but is not complicated. That code is shown in Listing 9.

Listing 9. Service the keyboard.

      //The following code is used to move one or the
      // other of two ladybugs using the arrow keys.
      // Press only the arrow keys to move one of the
      // ladybugs. Press the A plus the arrow keys
      // to move the other ladybug. 
      //The ladybugs cannot be moved outside the game
      // window using the keyboard. 
      //While an arrow key is pressed, the ladybug moves
      //five pixels per call to the Update method.

      //Get the state of the keyboard.
      KeyboardState keyboardState = Keyboard.GetState();

      //Execute moves on one ladybug with arrow keys plus
      // the A or a key.
      if(keyboardState.IsKeyDown(Keys.Left) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.X > 0)) {
        ladybugs[0].Position = new Vector2(
                              ladybugs[0].Position.X - 5,
                              ladybugs[0].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Right) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.X < 
            (windowWidth - ladybugs[1].Image.Width))) {
        ladybugs[0].Position = new Vector2(
                              ladybugs[0].Position.X + 5,
                              ladybugs[0].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Up) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.Y > 0)) {
        ladybugs[0].Position = new Vector2(
                             ladybugs[0].Position.X,
                             ladybugs[0].Position.Y - 5);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Down) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.Y < 
            (windowHeight - ladybugs[1].Image.Height))) {
        ladybugs[0].Position = new Vector2(
                             ladybugs[0].Position.X,
                             ladybugs[0].Position.Y + 5);
      }//end if

      //Execute moves on the other ladybug with arrow
      // keys pressed but the A key not pressed.
      if(keyboardState.IsKeyDown(Keys.Left) && 
            !(keyboardState.IsKeyDown(Keys.A)) && 
            (ladybugs[1].Position.X > 0)) {
        ladybugs[1].Position = new Vector2(
                              ladybugs[1].Position.X - 5,
                              ladybugs[1].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Right) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.X < 
            (windowWidth - ladybugs[1].Image.Width))) {
        ladybugs[1].Position = new Vector2(
                              ladybugs[1].Position.X + 5,
                              ladybugs[1].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Up) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.Y > 0)) {
        ladybugs[1].Position = new Vector2(
                             ladybugs[1].Position.X,
                             ladybugs[1].Position.Y - 5);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Down) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.Y < 
            (windowHeight - ladybugs[1].Image.Height))) {
        ladybugs[1].Position = new Vector2(
                             ladybugs[1].Position.X,
                             ladybugs[1].Position.Y + 5);
      }//end if

        

Get the state of the keyboard

The code with the yellow highlight in Listing 9 calls the static GetState method of the Keyboard class to get a reference to an object of the KeyboardState structure. Here is part of what the documentation for KeyboardState has to say:

"Represents a state of keystrokes recorded by a keyboard input device."

Polling instead of handling events

Most business and scientific programs written using C# would sit idle and wait for a keyboard event to occur. Then the event would be handled, and the program would sit idle and wait for the next event to occur.

Game programs written using XNA don't sit idle. Such programs are always executing the game loop. Therefore, XNA game programs poll the keyboard and the mouse instead of waiting to handle events fired by the keyboard or the mouse. This represents a major difference in programming philosophy.

Key up or down queries

An object of the KeyboardState structure has several methods, including the following two which are of particular interest:

In this program, our primary interest is in the first of the two methods.

An incoming parameter of type Keys

Both methods require an incoming parameter of type Keys, which is an "Enumerated value that specifies the key to query."

The Keys Enumeration has many members, each one of which "Identifies a particular key on a keyboard." In this program, we are interested in the following enumerated values:

These are the enumerated values that we will use to determine if the player is pressing one or more arrow keys and the A key.

Make the test

The code that is highlighted in orange in Listing 9 tests to determine if the player is currently pressing both the LEFT ARROW key and the A key. In addition the code tests to confirm that the current position of the ladybug is not outside the left edge of the game window.

Move the ladybug

If this test returns true, the code with the cyan background in Listing 9 sets the horizontal position for the ladybug to five pixels to the left of its current position. This change in position will be reflected visually in the game window the next time the ladybug is drawn.

Three more tests

Three more similar if statements are executed testing for RIGHT ARROW, UP ARROW, and DOWN ARROW and moving the ladybug appropriately if the tests return true.

Not mutually exclusive

Note that these tests are not mutually exclusive. For example, the player can be pressing both the LEFT ARROW key and the UP ARROW key, which will result in the ladybug moving diagonally up and to the left.

Applies to a particular ladybug

The tests performed above and the corresponding moves apply only to the ladybug whose reference is stored at index [1] in the list of ladybugs.

Four more similar tests

Four more very similar tests are applied beginning with the code with the magenta highlight in Listing 9. These tests and the resulting moves apply only to the ladybug whose reference is stored at index [0] in the list of ladybugs.

Testing for the A key not pressed

Note that in these four tests, I used the logical not operator (!) to test for the arrow keys pressed but the A key not pressed. (I also could have called the IsKeyUp method instead of using the logical not operator for these tests.)

Dragging the ladybug objects with the mouse

Listing 10 shows the code required to drag the ladybug objects with the mouse.

Listing 10. Moving the ladybug objects with the mouse.

      //Get the state of the mouse.
      MouseState mouseState = Mouse.GetState();
      //Press the left mouse button to move one ladybug.
      if(mouseState.LeftButton == ButtonState.Pressed) {
        ladybugs[0].Position = 
                  new Vector2(mouseState.X,mouseState.Y);
      }//end if

      //Press the right mouse button to move the other
      // ladybug.
      if(mouseState.RightButton == ButtonState.Pressed) {
        ladybugs[1].Position = 
                  new Vector2(mouseState.X,mouseState.Y);
      }//end if


      base.Update(gameTime);
    }//end Update method

Relatively simple code

The code in Listing 10 is used to drag one or the other of two ladybug objects with the mouse. As you can see from the amount of code involved, dragging the ladybug objects with the mouse is much simpler than moving them from the keyboard.

You can drag one of the ladybug objects by pressing the left mouse button and you can drag the other object by pressing the right mouse button. You can drag them both, one on top of the other by pressing both mouse buttons at the same time.

Get the state of the mouse

As is the case with the keyboard, the code in Listing 10 polls the mouse once during each iteration of the game loop to determine its state.

Listing 10 begins by calling the GetState method of the Mouse class to get a reference to a MouseState object that describes the current state of the mouse.

A MouseState object

According to the documentation, a MouseState object "Represents the state of a mouse input device, including mouse cursor position and buttons pressed."

The object contains several properties including the following:

The ButtonState enumeration

According to the documentation, ButtonState is an Enumeration that "Identifies the state of a mouse button or Xbox 360 Controller button." It has the following members:

Test for a left mouse button pressed

The code highlighted in orange in Listing 10 tests to determine if the left mouse button is pressed. If so, it sets the position of the ladybug object referred to by index [0] in the list of ladybugs to the current position of the mouse pointer.

A change in ladybug position

This change in position becomes visible the next time the ladybug is drawn. This has the effect of causing the ladybug object to follow the mouse pointer throughout the game window. It is even possible for the mouse to drag a ladybug outside the game window and leave it there.

Do the same for the other ladybug object

The code highlighted in cyan in Listing 10 does essentially the same thing, but applies the operation to the ladybug object referred to by index [1] in the list of ladybugs.

Methods of the Mouse class

In addition to the GetState method mentioned earlier, the Mouse class has one other static method that is not inherited from the Object class. It is named SetPosition, and it "Sets the position of the mouse cursor relative to the upper-left corner of the window." You saw this method used to place the mouse cursor in the center of the game window in Listing 7.

The end of the overridden Update method

Listing 10 also signals the end of the overridden Update method.

The overridden Game.Draw method

The overridden Game.Draw method is shown in its entirety in Listing 11.

Listing 11. The overridden Game.Draw method.

    protected override void Draw(GameTime gameTime) {

      spriteBatch.Begin();

      spiderWeb.Draw(spriteBatch);//draw background 

      //Draw all spiders.
      for(int cnt = 0;cnt < spiders.Count;cnt++) {
        spiders[cnt].Draw(spriteBatch);
      }//end for loop

      //Draw all ladybugs.
      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {
        ladybugs[cnt].Draw(spriteBatch);
      }//end for loop

      //Draw the output text.
      string output = "";
      if(spiderCount == 0){
        output = "Congratulations. No spiders made it to"
                 + " the bottom.";
      }else{
        output = "Oops, " + spiderCount + " or more "
                 + "spiders made it to the bottom.";
      }//end else
      
      // Find the center of the string
      Vector2 FontOrigin = 
                         Font1.MeasureString(output) / 2;
      // Draw the string
      spriteBatch.DrawString(Font1,
                             output,
                             FontPos,
                             Color.Yellow,
                             0,//angle
                             FontOrigin,
                             1.0f,//scale
                             SpriteEffects.None,
                             0.0f);//layer depth

      spriteBatch.End();

      base.Draw(gameTime);
    }//end Draw method
    //-------------------------------------------------//
  }//end class
}//end namespace

The MeasureString method

Other than the MeasureString method of the SpriteFont class, there is nothing in Listing 11 that I haven't explained before.

The MeasureString method "Returns the height and width of a given string as a Vector2."

Overloaded division operator

The highlighted statement in Listing 11 is very interesting. Recall that a Vector2 object has two fields, X and Y. Note that the code highlighted in orange in Listing 11 divides the object by 2. I didn't say that the code extracts the fields and divides them by 2. I said that the code divides the object by 2. This must mean that the division operator (/) is overloaded by the Vector2 class such that dividing a Vector2 object by a scalar value divides the fields by the scalar value.

If you know what I am talking about when I talk about operator overloading, that is good. If not, don't worry about it. That topic is far beyond the technical requirements for the introductory XNA game programming course that I teach.

The end of the program

Listing 11 signals the end of the overridden Game.Draw method, the end of the Game1 class, and the end of the program.

Run the program

I encourage you to copy the code from Listing 13 and Listing 14.  Use that code to create an XNA project.  You should be able to download suitable images from the Internet. Compile and run the project.  Experiment with the code, making changes, and observing the results of your changes.  Make certain that you can explain why your changes behave as they do. 

Summary

You learned how to write a simple game program involving user input from the keyboard and the mouse. You also learned how to create on-screen text.

What'snext?

At this point, the main tool that we are missing for creating more interesting 2D arcade style games is the ability to incorporate sound effects and music into the games. That will be the topic of the next lesson in this series.

Complete program listings

Complete listings of the XNA code discussed in this lesson are provided in Listing 12 through Listing 14 below.

Listing 12. Game1 class for project XNA0132ProjA.

/*Project XNA0132ProjA
********************************************************/
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace XNA0132ProjA {

  public class Game1 : Microsoft.Xna.Framework.Game {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont Font1;
    Vector2 FontPos;
    //-------------------------------------------------//

    protected override void LoadContent() {
      spriteBatch = new SpriteBatch(GraphicsDevice);

      //Create a new SpriteFont object.
      Font1 = Content.Load<SpriteFont>("Lindsey");

      //Create a new Vector2 object to center the text
      // in the game window.
      FontPos = new Vector2(
            graphics.GraphicsDevice.Viewport.Width / 2,
            graphics.GraphicsDevice.Viewport.Height / 2);
    }//end LoadContent method
    //-------------------------------------------------//

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

      spriteBatch.Begin();

      // Draw Lindsey Font
      string output = "Lindsey Font";

      // Find the center of the string
      Vector2 FontOrigin = 
                         Font1.MeasureString(output) / 2;
      //Draw the string with the FontOrigin at the 
      // position specified by FontPos.
      spriteBatch.DrawString(
          Font1,//font to use
          output,//output text
          FontPos,//position re upper left corner
          Color.LightGreen,//color of text
          -45,//rotation of text
          FontOrigin,//place this at FontPos
          2.5f,//scale
          SpriteEffects.None,
          0.5f);//z-order layer

      spriteBatch.End();

      base.Draw(gameTime);
    }//end Draw method
    //-------------------------------------------------//

    protected override void Update(GameTime gameTime) {
      // Allows the game to exit
      if(GamePad.GetState(PlayerIndex.One).
                     Buttons.Back == ButtonState.Pressed)
        this.Exit();

      //No special update code required.

      base.Update(gameTime);
    }//end Update method
    //-------------------------------------------------//

    protected override void Initialize() {
      //Not needed
      base.Initialize();
    }//end Initialize
    //-------------------------------------------------//

    public Game1() {//Typical constructor
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";
    }//end constructor
    //-------------------------------------------------//

    protected override void UnloadContent() {
      //Not needed
    }//end UnloadContent
    //-------------------------------------------------//
  }//end class
}//end namespace
        

Listing 13. Sprite class for project XNA0132Proj.

/*Project XNA0132Proj
 * This file defines a Sprite class from which a Sprite
 * object can be instantiated. This version supports
 * collision detection based on intersecting rectangles.
 * It also provides an Edge property that records and
 * returns the edge number if a sprite collides with an
 * edge. However, the edge information is available for
 * only one iteration of the game loop. Normally the
 * value of Edge is 0. However, it changes to 1,2,3,or4
 * if a sprite collides with the top, right, bottom, or
 * left edge of the game window.
 *******************************************************/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace XNA0132Proj {
  class Sprite {
    private int edge = 0;//new to this version
    private Texture2D image;
    private Vector2 position = new Vector2(0,0);
    private Vector2 direction = new Vector2(0,0);
    private Point windowSize;
    private Random random;
    double elapsedTime;//in milliseconds
    //The following value is the inverse of speed in
    // moves/msec expressed in msec/move.
    double elapsedTimeTarget;
    //-------------------------------------------------//

    //New to this version.
    //Edge property accessor
    public int Edge {
      get {
        return edge;
      }//end get
    }//end Edge property accessor
    //-------------------------------------------------//

    //Image property accessor
    public Texture2D Image {
      get {
        return image;
      }//end get
    }//end Image property accessor
    //-------------------------------------------------//

    //Position property accessor
    public Vector2 Position {
      get {
        return position;
      }
      set {
        position = value;
      }//end set
    }//end Position property accessor
    //-------------------------------------------------//

    //WindowSize property accessor
    public Point WindowSize {
      set {
        windowSize = value;
      }//end set
    }//end WindowSize property accessor
    //-------------------------------------------------//

    //Direction property accessor
    public Vector2 Direction {
      get {
        return direction;
      }
      set {
        direction = value;
      }//end set
    }//end Direction property accessor

    //-------------------------------------------------//

    //Speed property accessor. The set side should be
    // called with speed in moves/msec. The get side
    // returns speed moves/msec.
    public double Speed {
      get {
        //Convert from elapsed time in msec/move to
        // speed in moves/msec.
        return elapsedTimeTarget / 1000;
      }
      set {
        //Convert from speed in moves/msec to
        // elapsed time in msec/move.
        elapsedTimeTarget = 1000 / value;
      }//end set
    }//end Speed property accessor
    //-------------------------------------------------//

    //This constructor loads an image for the sprite
    // when it is instantiated. Therefore, it requires
    // an asset name for the image and a reference to a
    // ContentManager object.
    //Requires a reference to a Random object. Should 
    // use the same Random object for all sprites to
    // avoid getting the same sequence for different
    // sprites.
    public Sprite(String assetName,
                  ContentManager contentManager,
                  Random random) {
      image = contentManager.Load<Texture2D>(assetName);
      image.Name = assetName;
      this.random = random;
    }//end constructor
    //-------------------------------------------------//

    //This method can be called to load a new image
    // for the sprite.
    public void SetImage(String assetName,
                         ContentManager contentManager) {
      image = contentManager.Load<Texture2D>(assetName);
      image.Name = assetName;
    }//end SetImage
    //-------------------------------------------------//

    //This method causes the sprite to move in the 
    // direction of the direction vector if the elapsed
    // time since the last move exceeds the elapsed
    // time target based on the specified speed.
    public void Move(GameTime gameTime) {

      //New to this version
      //Clear the Edge property value. Edge information
      // is available for only one iteration of the 
      // game loop.
      edge = 0;

      //Accumulate elapsed time since the last move.
      elapsedTime +=
                  gameTime.ElapsedGameTime.Milliseconds;

      if(elapsedTime > elapsedTimeTarget) {
        //It's time to make a move. Set the elapsed 
        // time to a value that will attempt to produce
        // the specified speed on the average.
        elapsedTime -= elapsedTimeTarget;

        //Add the direction vector to the position
        // vector to get a new position vector.
        position = Vector2.Add(position,direction);

        //Check for a collision with an edge of the game
        // window. If the sprite reaches an edge, cause 
        // the sprite to wrap around and reappear at the 
        // other edge, moving at the same speed in a 
        // different direction within the same quadrant 
        // as before. Also set the Edge property to
        // indicate which edge was involved. 1 is top, 2
        // is right, 3 is bottom, and 4 is left.
        // Note that the Edge property will be cleared
        // to 0 the next time the Move method is called.
        if(position.X < -image.Width) {
          position.X = windowSize.X;
          edge = 4;//collision with the left edge - new
          NewDirection();
        }//end if

        if(position.X > windowSize.X) {
          position.X = -image.Width / 2;
          edge = 2;//collision with the right edge - new
          NewDirection();
        }//end if

        if(position.Y < -image.Height) {
          position.Y = windowSize.Y;
          edge = 1;//collision with the top - new
          NewDirection();
        }//end if

        if(position.Y > windowSize.Y) {
          position.Y = -image.Height / 2;
          edge = 3;//collision with the bottom - new
          NewDirection();
        }//end if on position.Y
      }//end if on elapsed time
    }//end Move
    //-------------------------------------------------//

    //This method determines the length of the current 
    // direction vector along with the signs of the X 
    // and Y components of the current direction vector.
    // It computes a new direction vector of the same 
    // length with the X and Y components having random
    // lengths and the same signs.
    //Note that random.NextDouble returns a 
    // pseudo-random value, uniformly distrubuted
    // between 0.0 and 1.0.
    private void NewDirection() {
      //Get information about the current direction
      // vector.
      double length = Math.Sqrt(
                            direction.X * direction.X +
                            direction.Y * direction.Y);
      Boolean xNegative = 
                        (direction.X < 0) ? true : false;
      Boolean yNegative = 
                        (direction.Y < 0) ? true : false;

      //Compute a new X component as a random portion of
      // the vector length.
      direction.X =
                  (float)(length * random.NextDouble());
      //Compute a corresponding Y component that will 
      // keep the same vector length.
      direction.Y = (float)Math.Sqrt(length * length -
                              direction.X * direction.X);

      //Set the signs on the X and Y components to match
      // the signs from the original direction vector.
      if(xNegative)
        direction.X = -direction.X;
      if(yNegative)
        direction.Y = -direction.Y;
    }//end NewDirection
    //-------------------------------------------------//

    public void Draw(SpriteBatch spriteBatch) {
      //Call the simplest available version of
      // SpriteBatch.Draw
      spriteBatch.Draw(image,position,Color.White);
    }//end Draw method
    //-------------------------------------------------//

    //Returns the current rectangle occupied by the
    // sprite.
    public Rectangle GetRectangle() {
      return new Rectangle((int)(position.X),
                           (int)(position.Y),
                           image.Width,
                           image.Height);
    }//end GetRectangle
    //-------------------------------------------------//

    //This method receives a list of Sprite objects as
    // an incoming parameter. It tests for a collision
    // with the sprites in the list beginning with the
    // sprite at the head of the list. If it detects a
    // collision, it stops testing immediately and
    // returns a reference to the Sprite object for
    // which it found the collision. If it doesn't find
    // a collision with any sprite in the list, it
    // returns null.
    //A collision is called if the rectangle containing
    // this object's image intersects the rectangle
    // containing a target sprite's image.
    public Sprite IsCollision(List<Sprite> target) {
      Rectangle thisRectangle =
                         new Rectangle((int)(position.X),
                                       (int)(position.Y),
                                       image.Width,
                                       image.Height);
      Rectangle targetRectangle;
      int cnt = 0;

      while(cnt < target.Count) {
        targetRectangle = target[cnt].GetRectangle();
        if(thisRectangle.Intersects(targetRectangle)) {
          return target[cnt];
        }//end if
        cnt++;
      }//end while loop

      return null;//no collision detected
    }//end IsCollision
    //-------------------------------------------------//

  }//end class
}//end namespace

Listing 14. Game1 class for project XNA0132Proj.

/*Project XNA0132Proj
 * This project demonstrates how to write a simple 2D
 * arcade style game. Ten spiders try to make it across
 * a web from top to bottom in opposition to two 
 * ladybugs. If a ladybug collides with a spider, the 
 * spider is eaten by the ladybug.
 * 
 * The ladybugs can be moved either with the keyboard or
 * the mouse. Press the arrow keys to move one of the
 * ladybugs. Press the A key plus the arrow keys to move
 * the other ladybug.
 * 
 * Press the left mouse button to drag one of the 
 * ladybugs with the mouse. Press the right arrow key
 * to drag the other ladybug with the mouse.
 * 
 * If a spider makes it from the top to the bottom of
 * the game window, it wraps back to the top and starts
 * the trip over.
 * 
 * On-screen text keeps track of the number of spider
 * crossings at the bottom of the game window. Note,
 * however, that if two spiders cross the bottom in
 * very close proximity, they may not both get counted.
 * 
 * This program demonstrates how to display on-screen
 * text. Note however that it is necessary to create
 * a font resource before you can display onscreen text.
 * *****************************************************/
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XNA0132Proj;

namespace XNA0132Proj {

  public class Game1 : Microsoft.Xna.Framework.Game {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    //Use the following values to set the size of the
    // client area of the game window. The actual window
    // with its frame is somewhat larger depending on
    // the OS display options. On my machine with its
    // current display options, these dimensions
    // produce a 1024x768 game window.
    int windowWidth = 1017;
    int windowHeight = 738;

    //This is the length of the greatest distance in
    // pixels that any sprite will move in a single
    // frame of the game loop.
    double maxVectorLength = 5.0;

    Sprite spiderWeb;//reference to a background sprite.

    //References to the spiders are stored in this
    // List object.
    List<Sprite> spiders = new List<Sprite>();
    int numSpiders = 10;//Number of spiders.
    //The following value should never exceed 60 moves
    // per second unless the default frame rate is also
    // increased to more than 60 frames per second.
    double maxSpiderSpeed = 30;//moves per second

    //References to the Ladybugs are stored in this List.
    List<Sprite> ladybugs = new List<Sprite>();
    int numLadybugs = 2;//Max number of ladybugs


    //Random number generator. It is best to use a single
    // object of the Random class to avoid the 
    // possibility of using different streams that
    // produce the same sequence of values.
    //Note that the random.NextDouble() method produces
    // a pseudo-random value where the sequence of values
    // is uniformly distributed between 0.0 and 1.0.
    Random random = new Random();

    //The following variable is used to count the number
    // of spiders that make it past the ladybugs and
    // reach the bottom of the game window.
    int spiderCount = 0;

    SpriteFont Font1;//font for on-screen text
    Vector2 FontPos;//position of on-screen text
    //-------------------------------------------------//

    public Game1() {//constructor
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";

      //Set the size of the game window.
      graphics.PreferredBackBufferWidth = windowWidth;
      graphics.PreferredBackBufferHeight = windowHeight;
    }//end constructor
    //-------------------------------------------------//

    protected override void Initialize() {
      //No initialization required.
      base.Initialize();
    }//end Initialize
    //-------------------------------------------------//


    protected override void LoadContent() {
      spriteBatch = new SpriteBatch(GraphicsDevice);
      Font1 = Content.Load<SpriteFont>(@"Kootenay");
      IsMouseVisible = true;//make mouse visible

      //Create a sprite for the background image.
      spiderWeb =
                 new Sprite("spiderwebB",Content,random);
      spiderWeb.Position = new Vector2(0f,0f);


      //Instantiate all of the spiders and cause them to
      // move from left to right, top to 
      // bottom. Pass a reference to the same Random
      // object to all of the sprites.
      for(int cnt = 0;cnt < numSpiders;cnt++) {
        spiders.Add(
          new Sprite("blackWidowSpider",Content,random));

        //Set the position of the current spider at a
        // random location within the game window but
        // near the top of the game window.
        spiders[cnt].Position = new Vector2(
           (float)(windowWidth * random.NextDouble()),
           (float)((windowHeight/10) *
                                   random.NextDouble()));

        //Get a direction vector for the current spider.
        // Make both components positive to cause the
        // vector to point down and to the right.
        spiders[cnt].Direction = DirectionVector(
          (float)maxVectorLength,
          (float)(maxVectorLength * random.NextDouble()),
          false,//xNeg
          false);//yNeg

        //Notify the spider object of the size of the
        // game window.
        spiders[cnt].WindowSize =
                     new Point(windowWidth,windowHeight);

        //Set the speed in moves per second for the
        // current spider to a random value between
        // maxSpiderSpeed/2 and maxSpiderSpeed.
        spiders[cnt].Speed = maxSpiderSpeed / 2
              + maxSpiderSpeed * random.NextDouble() / 2;
      }//end for loop

      //Instantiate all of the ladybugs.They move under
      // control of the keyboard or the mouse.
      for(int cnt = 0;cnt < numLadybugs;cnt++) {
        ladybugs.Add(
                   new Sprite("ladybug",Content,random));

        //Position the ladybugs at a random position
        // near the bottom of the game window.
        ladybugs[cnt].Position = new Vector2(
            (float)(windowWidth * random.NextDouble()),
            (float)(windowHeight - 
                            ladybugs[cnt].Image.Height));
      }//end for loop

      //Position the on-screen text.
      FontPos = new Vector2(windowWidth / 2, 50);

      //Position the mouse pointer in the center of the
      // game window.
      Mouse.SetPosition(windowWidth / 2,windowHeight /2);

    }//end LoadContent
    //-------------------------------------------------//

    //This method returns a direction vector given the
    // length of the vector, the length of the
    // X component, the sign of the X component, and the
    // sign of the Y component. Set negX and/or negY to
    // true to cause them to be negative. By adjusting
    // the signs on the X and Y components, the vector
    // can be caused to point into any of the four
    // quadrants.
    private Vector2 DirectionVector(float vecLen,
                                    float xLen,
                                    Boolean negX,
                                    Boolean negY) {
      Vector2 result = new Vector2(xLen,0);
      result.Y = (float)Math.Sqrt(vecLen * vecLen
                                          - xLen * xLen);
      if(negX)
        result.X = -result.X;
      if(negY)
        result.Y = -result.Y;
      return result;
    }//end DirectionVector
    //-------------------------------------------------//

    protected override void UnloadContent() {
      //No content unload required.
    }//end unloadContent
    //-------------------------------------------------//

    protected override void Update(GameTime gameTime) {
      //Tell all the spiders in the list to move.
      for(int cnt = 0;cnt < spiders.Count;cnt++) {
        spiders[cnt].Move(gameTime);
      }//end for loop

      //Tell each ladybug to test for a collision with a
      // spider and to return a reference to the spider
      // if there is a collision. Return null if there is
      // no collision.
      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {

        //Test for a collision between this ladybug and
        // all of the spiders in the list of spiders.
        Sprite target =
                      ladybugs[cnt].IsCollision(spiders);
        if(target != null) {
          //There was a collision. The spider gets eaten.
          // Remove it from the list of spiders.
          spiders.Remove(target);
        }//end if
      }//end for loop

      //Check to see if any spiders have made it to the
      // bottom edge.
      for(int cnt = 0;cnt < spiders.Count;cnt++) {
        if(spiders[cnt].Edge == 3)
          //One or more made it to the bottom edge.
          spiderCount += 1;
      }//end for loop

      //The following code is used to move one or the
      // other of two ladybugs using the arrow keys.
      // Press only the arrow keys to move one of the
      // ladybugs. Press the A or a plus the arrow keys
      // to move the other ladybug. 
      //The ladybugs cannot be moved outside the game
      // window. 
      //When an arrow key is pressed, the ladybug moves
      //five pixels per call to the Update method.

      //Get the state of the keyboard.
      KeyboardState keyboardState = Keyboard.GetState();

      //Execute moves on one ladybug with arrow keys plus
      // the A or a key.
      if(keyboardState.IsKeyDown(Keys.Left) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.X > 0)) {
        ladybugs[0].Position = new Vector2(
                              ladybugs[0].Position.X - 5,
                              ladybugs[0].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Right) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.X < 
            (windowWidth - ladybugs[1].Image.Width))) {
        ladybugs[0].Position = new Vector2(
                              ladybugs[0].Position.X + 5,
                              ladybugs[0].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Up) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.Y > 0)) {
        ladybugs[0].Position = new Vector2(
                             ladybugs[0].Position.X,
                             ladybugs[0].Position.Y - 5);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Down) && 
          (keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[0].Position.Y < 
            (windowHeight - ladybugs[1].Image.Height))) {
        ladybugs[0].Position = new Vector2(
                             ladybugs[0].Position.X,
                             ladybugs[0].Position.Y + 5);
      }//end if

      //Execute moves on the other ladybug with arrow
      // keys pressed but the A key not pressed.
      if(keyboardState.IsKeyDown(Keys.Left) && 
            !(keyboardState.IsKeyDown(Keys.A)) && 
            (ladybugs[1].Position.X > 0)) {
        ladybugs[1].Position = new Vector2(
                              ladybugs[1].Position.X - 5,
                              ladybugs[1].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Right) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.X < 
            (windowWidth - ladybugs[1].Image.Width))) {
        ladybugs[1].Position = new Vector2(
                              ladybugs[1].Position.X + 5,
                              ladybugs[1].Position.Y);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Up) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.Y > 0)) {
        ladybugs[1].Position = new Vector2(
                             ladybugs[1].Position.X,
                             ladybugs[1].Position.Y - 5);
      }//end if

      if(keyboardState.IsKeyDown(Keys.Down) && 
          !(keyboardState.IsKeyDown(Keys.A)) && 
          (ladybugs[1].Position.Y < 
            (windowHeight - ladybugs[1].Image.Height))) {
        ladybugs[1].Position = new Vector2(
                             ladybugs[1].Position.X,
                             ladybugs[1].Position.Y + 5);
      }//end if


      //The following code is used to drag one or the
      // other of two ladybugs using the mouse. Press
      // the left mouse button to drag one of the
      // ladybugs. Press the right mouse button to drag
      // the other ladybug.

      //Get the state of the mouse.
      MouseState mouseState = Mouse.GetState();

      //Press the left mouse button to move one ladybug.
      if(mouseState.LeftButton == ButtonState.Pressed) {
        ladybugs[0].Position = 
                  new Vector2(mouseState.X,mouseState.Y);
      }//end if

      //Press the right mouse button to move the other
      // ladybug.
      if(mouseState.RightButton == ButtonState.Pressed) {
        ladybugs[1].Position = 
                  new Vector2(mouseState.X,mouseState.Y);
      }//end if


      base.Update(gameTime);
    }//end Update method
    //-------------------------------------------------//

    protected override void Draw(GameTime gameTime) {

      spriteBatch.Begin();

      spiderWeb.Draw(spriteBatch);//draw background 

      //Draw all spiders.
      for(int cnt = 0;cnt < spiders.Count;cnt++) {
        spiders[cnt].Draw(spriteBatch);
      }//end for loop

      //Draw all ladybugs.
      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {
        ladybugs[cnt].Draw(spriteBatch);
      }//end for loop

      //Draw the output text.
      string output = "";
      if(spiderCount == 0){
        output = "Congratulations. No spiders made it to"
                 + " the bottom.";
      }else{
        output = "Oops, " + spiderCount + " or more "
                 + "spiders made it to the bottom.";
      }//end else
      
      // Find the center of the string
      Vector2 FontOrigin = 
                         Font1.MeasureString(output) / 2;
      // Draw the string
      spriteBatch.DrawString(Font1,
                             output,
                             FontPos,
                             Color.Yellow,
                             0,//angle
                             FontOrigin,
                             1.0f,//scale
                             SpriteEffects.None,
                             0.0f);//layer depth

      spriteBatch.End();

      base.Draw(gameTime);
    }//end Draw method
    //-------------------------------------------------//
  }//end class
}//end namespace


Copyright

Copyright 2009, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is object-oriented programming using Java and other OOP languages.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

-end-