baldwin.jpg

Programming with XNA Game Studio

Collision Detection

Learn how to design and create a Sprite class that provides collision detection. Also learn how to write an XNA program that takes advantage of that collision detection capability.

Published: January 27, 2010f
Validated with Amaya

By Richard G. Baldwin

XNA Programming Notes # 0130


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

While studying the past couple of lessons, you have learned how to create and then to improve a class named Sprite from which you can instantiate objects that behave as sprites.

Another improvement

In this lesson, we will once again improve the Sprite class, this time adding the capability for a sprite to determine if it has collided with another sprite. This capability is critical for game development in many areas.

Definition of a collision

Our definition of a collision is based on the bounding rectangles for the images that represent the two sprites. If the rectangles intersect in their current positions, a collision is deemed to have occurred. If they don't intersect, there is no collision.

Preview

Not a game

This program is not yet a game because it doesn't provide player interaction. Instead it is a demonstration program that demonstrates sprite collision detection is a rather interesting way.

The program output

Figure 1 shows a screen snapshot at three different points in time while the program was running.

An epic battle

The demonstration program chronicles a battle between spiders and ladybugs. When the program starts, there are 200 black widow spiders and five ladybugs on the web in the game window. (See the top image in Figure 1.)

Spider and ladybug motion

The spiders move at different speeds in different directions but generally toward the southeast. When a spider goes outside the game window on the right side or the bottom, it reappears on the left side or the top.

The ladybugs also move at different speeds in different directions but generally toward the northwest. When a ladybug goes outside the game window on the left side or the top, it reappears on the right side or on the bottom.

The spiders don't die easily

When a ladybug collides with a black widow spider, the spider disappears but is reincarnated as a green spider 128 pixels to the right of its original position.

When a ladybug collides with a green spider, the spider disappears but is reincarnated again as a small brown spider 128 pixels to the right of its original position.

The death blow

Finally, when a ladybug collides with a brown spider, the spider is eaten and is removed from the population of spiders. Therefore, all of the spiders eventually disappear and the ladybugs continue marching on victorious.

The top image

The top image in Figure 1 was taken shortly after the program began running. Therefore, the game window was mostly populated with ladybugs and black widow spiders. There are a few green spiders and I believe I see one brown spider at the very bottom border near the right side.

Figure 1 Screen output at three different times while the program was running.
Program output

The middle image

The middle image in Figure 1 shows the program output after the program has been running for awhile. At this point in time, the game window is mainly populated with ladybugs, green spiders, and brown spiders. However, there are a few black widow spiders still in the picture.

The bottom image

The bottom image shows the program output at an even later point in time. At this point, the game window is populated with ladybugs, one green spider and a few brown spiders. Given enough time and a little luck, the ladybugs will collide with and destroy even these remaining spiders.

Discussion and sample code

Will discuss in fragments

As usual, I will discuss the program in fragments, beginning with the Sprite class. Furthermore, I will discuss only those portions of the Sprite class that are different from the versions of the Sprite class that I explained in earlier lessons.

A complete listing of the Sprite class is provided in Listing 14 and a complete listing of the Game1 class is provided in Listing 15 near the end of the lesson.

The Sprite class

A new Image property accessor method

Scanning down the Sprite class definition in Listing 14, the first thing that we find that is new to this version is a new read-only Image property accessor method. The method is shown in Listing 1.

Listing 1. A new Image property accessor method in the Sprite class.

    //Image property accessor - new to this version.
    public Texture2D Image {
      get {
        return image;
      }//end get
    }//end Image property accessor

There is nothing unusual about this property accessor method, so it shouldn't require further explanation. You will learn why it is needed later when I explain the Game1 class.

A modified constructor

The statement highlighted in yellow in Listing 2 was added to the constructor from the previous version of the Sprite class.

Listing 2. A modified constructor.

    public Sprite(String assetName,
                  ContentManager contentManager,
                  Random random) {
      image = contentManager.Load<Texture2D>(assetName);
      image.Name = assetName;
      this.random = random;
    }//end constructor

The new statement assigns the assetName to the Name property of the Texture2D object that represents the sprite's image. You will see why this modification was needed later.

A modified SetImage method

As with the modified constructor, the statement highlighted in yellow in Listing 3 was added to the SetImage method from the previous version of the Sprite class.

Listing 3. A modified SetImage method.

    public void SetImage(String assetName,
                         ContentManager contentManager) {
      image = contentManager.Load<Texture2D>(assetName);
      image.Name = assetName;
    }//end SetImage

Once again, the new statement assigns the assetName to the Name property of the Texture2D object that represents the sprite's image. You will also see why this modification was needed later.

A new method named GetRectangle

Listing 4 shows a new method named GetRectangle.

Listing 4. A new method named GetRectangle.

    public Rectangle GetRectangle() {
      return new Rectangle((int)(position.X),
                           (int)(position.Y),
                           image.Width,
                           image.Height);
    }//end GetRectangle

This method returns the current rectangle occupied by the sprite's image as type Rectangle. This rectangle is needed for the collision detection process. The code in Listing 4 is straightforward and shouldn't require further explanation.

A new method named IsCollision

This version of the Sprite class defines a new method named IsCollision. The purpose of this new method is to detect s collision between this sprite and some other sprite.

What is a collision?

A collision is called if the rectangle containing this Sprite object's image intersects the rectangle containing a target sprite's image.

What is the overall behavior?

This method receives a list of Sprite objects as an incoming parameter. It tests for a collision with each sprite in the list beginning with the sprite at the head of the list. If it detects a collision with a sprite, 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.

Beginning of a new IsCollision method

The new method named IsCollision begins in Listing 5.

Listing 5. Beginning of a new IsCollision method.

    public Sprite IsCollision(List<Sprite> target) {
      Rectangle thisRectangle = 
                         new Rectangle((int)(position.X),
                                       (int)(position.Y),
                                       image.Width,
                                       image.Height);

The code in Listing 5 constructs a new Rectangle object that describes the rectangular area currently occupied by the sprite's image relative to the upper left corner of the game window. The Rectangle object's reference is saved in the local variable named thisRectangle.

Test for a collision with other sprites

Listing 6 begins by declaring a pair of local variables named targetRectangle and cnt.

The variable named targetRectangle will be used to store a reference to a rectangular area that currently contains a target sprite. The variable named cnt will be used as a loop counter.

Listing 6. Test for a collision with other sprites.

      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

A while loop

Then Listing 6 executes a while loop that:

The first return statement

Executing a return statement in the middle of the while loop terminates the loop and also terminates the method.

The second return statement

If no collision is detected, the loop terminates when all of the Sprite objects in the list have been tested.

At that point, Listing 6 executes a different return statement to return null and terminate the method. The null value is returned in place of a reference to a Sprite object to signal that no collision was detected.

The end of the Sprite class

Listing 6 signals the end of the IsCollision method, which is also the end of the changes made to the Sprite class. Once again, you will find a complete listing of the new Sprite class in Listing 14 near the end of the lesson.

The Game1 class

I will explain only those portions of the Game1 class that are substantially new to this program. You will find a complete listing of the Game1 class in Listing 15 near the end of the lesson.

The overridden LoadContent method

The only code that is substantially new prior to the LoadContent method is the declaration of an instance variable of type Sprite named spiderWeb. Therefore, I will skip down and begin my discussion with the overridden LoadContent method, which begins in Listing 7.

Listing 7. Beginning of the overridden LoadContent method of the Game1 class.

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

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

        

A background sprite with a spider web image

The code with the yellow highlight in Listing 7 instantiates a new Sprite object and loads it with an image of the spider web shown in Figure 1.

The purpose of this sprite is to serve as a background image. Before adding the image of the spider web to the Content folder during the design phase, I used an external program to scale the image to the same size as the game window established by the constructor in Listing 15.

The code in Listing 7 positions the upper left corner of the sprite at the upper left corner of the game window so that it just fills the game window as shown in Figure 1.

Instantiate the spider and ladybug Sprite objects

The code in Listing 8 instantiates all of the spider and ladybug Sprite objects and sets their properties.

Listing 8. Instantiate the spider and ladybug Sprite objects.

      //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.
        spiders[cnt].Position = new Vector2(
           (float)(windowWidth * random.NextDouble()),
           (float)(windowHeight * 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

      //Use the same process to instantiate all of the
      // ladybugs and cause them to move from right to
      // left, bottom to top.
      for(int cnt = 0;cnt < numLadybugs;cnt++) {
        ladybugs.Add(
                   new Sprite("ladybug",Content,random));
        ladybugs[cnt].Position = new Vector2(
            (float)(windowWidth * random.NextDouble()),
            (float)(windowHeight * random.NextDouble()));
        ladybugs[cnt].Direction = DirectionVector(
         (float)maxVectorLength,
         (float)(maxVectorLength * random.NextDouble()),
         true,//xNeg
         true);//yNeg
        ladybugs[cnt].WindowSize =
                     new Point(windowWidth,windowHeight);
        ladybugs[cnt].Speed = maxLadybugSpeed / 2
             + maxLadybugSpeed * random.NextDouble() / 2;
      }//end for loop

    }//end LoadContent

The code in Listing 8 is essentially the same as code that I explained in an earlier lesson so no explanation beyond the embedded comments should be necessary.

The overridden Update method

The overridden Update method begins in Listing 9.

Listing 9. Beginning of the overridden Update method.

    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 all the ladybugs in the list to move.
      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {
        ladybugs[cnt].Move(gameTime);
      }//end for loop

The code in Listing 9 is essentially the same as code that I have explained in earlier lessons, so no further explanation should be necessary.

Beginning of a for loop that controls the collision testing

Listing 10 shows the beginning of a for loop that causes each ladybug Sprite object to test for a collision with the spiders in the list of spiders once during each iteration of the game loop.

If a ladybug detects a collision with a spider, the action described earlier is taken.

Not every spider is tested during each iteration

Note that if a ladybug detects a collision with a spider, the remaining spiders in the list are not tested by that ladybug during that iteration of the game loop. In other words, if a ladybug's rectangle intersects the rectangles belonging to two or more spiders, only the spider closest to the top of the list will register a collision.

Listing 10. Beginning of a for loop that controls the collision testing.

      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {

Test for a collision

The code that tests for a collision is shown in Listing 11.

Listing 11. Test for a collision.

        //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. Cause the spider to
          // move 128 pixels to the right.
          target.Position = 
                    new Vector2(target.Position.X + 128,
                                target.Position.Y);

The code with the yellow highlight in Listing 11 calls the IsCollision method on the current ladybug object, passing a reference to the list of spiders as a parameter. As you learned earlier, this will cause the ladybug object to test each spider in the list for a collision until either a collision is found or the list is exhausted.

A reference to a spider or null

When the IsCollision method returns and control moves to the beginning of the code with the cyan highlight, the variable named target will either contain null, (meaning that no collision was detected), or will contain a reference to the spider object involved in a collision.

If a collision was detected...

The body of the if statement highlighted in cyan is executed if a collision was detected and the target variable does not contain null. In this case, the X component of the spider's position vector is increased by a value of 128. This will cause the spider to move 128 pixels to the right the next time it is drawn.

Why change the spider's position?

This change is position is necessary to prevent the same ladybug from registering a collision with the same spider during the next iteration of the game loop. The forward movement of the spiders and the ladybugs each time they move is less than the dimensions of the intersecting rectangles. Therefore, without a purposeful shift in position of either the ladybug or the spider, the pair would continue to register collisions until the rectangles finally separate from one another.

Spiders don't die easily

As you read in the Preview section, if a black widow spider collides with a ladybug, it simply moves 128 pixels to the right and changes into a green spider. If a green spider collides with a ladybug, it simply moves 128 pixels to the right and changes into a brown spider. Finally, if a brown spider collides with a ladybug, it is eaten and removed from the population of spiders. This is accomplished by the code in Listing 12.

Listing 12. Spiders don't die easily.

          //If the collision was with a black widow
          // spider, cause it to reincarnate into a
          // green spider.
          if(target.Image.Name == "blackWidowSpider") {
            target.SetImage("greenspider",Content);
          }
           //If the collision was with a green spider,
           // cause it to reincarnate into a brown
           // spider.
          else if(target.Image.Name == "greenspider") {
            target.SetImage("brownSpider",Content);
          }
           //If the collision was with a brown spider,
           // it gets eaten. Remove it from the list of
           // spiders.
          else if(target.Image.Name == "brownSpider") {
            spiders.Remove(target);
          }// end else-if
        }//end if
      }//end for loop

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

The code in Listing 12 is relatively straightforward.

The Name property

The code with the yellow highlight in Listing 12 shows why I needed to set an identifiable string value into the Name property of the Texture2D object referred to by image in Listing 2 and Listing 3. I needed a way to determine the type of spider involved in each collision so that I could take the appropriate action when a collision was detected.

Killing the brown spider

The code with the cyan highlight in Listing 12 removes the target Sprite object's reference from the list of spiders. Since this is the only reference to the Sprite object, this action should make the memory occupied by the Sprite object eligible for garbage collection.

Is a Dispose Method needed?

However, I'm not certain that garbage collection is sufficient to free up all of the resources owned by the now defunct object and its image. It might also be advisable to use a Dispose Method. At the time I am writing this, I simply don't know.

The end of the Update method

Listing 12 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 13.

Listing 13. 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

      spriteBatch.End();

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

There is nothing new in Listing 13. However, the code with the yellow highlight is a little different from the code that you have seen in the overridden Game.Draw methods in the last couple of lessons. Therefore, I decided to highlight it.

The code highlighted in yellow calls the Sprite.Draw method, which in turn calls the SpriteBatch.Draw method to draw the image of the spider web as a background image in Figure 1.

The end of the program

Listing 13 also 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 14 and Listing 15.  Use that code to create an XNA project.  You should be able to find and download suitable image files from the web. Any small images can be used as substitutes for the spiders and the ladybugs. A larger image can be used for the background.

Compile and run the project.  Experiment with the code, making changes, and observing the results of your changes.  Make sure that you can explain why your changes behave as they do.

Summary

You learned how to design and create a Sprite class that provides collision detection. You also learned how to write an XNA program that takes advantage of that collision detection capability.

What's next?

The one critical element that is preventing us from using the Sprite class to create a 2D arcade style game is the ability of the user to control the motion of one or more sprites using keyboard and/or mouse input. That will be the topic for the next lesson.

Once we have that tool, we can write a game where the challenge is to prevent the spiders from successfully navigating across the game window without being eaten by a ladybug. Or, we could write a game where the challenge is to cause the ladybugs to successfully navigate across the game window without being bitten by a spider.

Beyond that, there are several other tools that will make it possible for us to create more sophisticated and interesting games:

I will show you how to create those tools later in this series of lessons.

Complete program listings

Complete listings of the program files discussed in this lesson are provided in Listing 14 and Listing 15 below.

Listing 14. The Sprite class for the project named XNA0130Proj.

/*Project XNA0130Proj
 * This file defines a Sprite class from which a Sprite
 * object can be instantiated. This version supports
 * collision detection based on intersecting rectangles.
 *******************************************************/

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

namespace XNA0130Proj {
  class Sprite {
    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;
    //-------------------------------------------------//

    //Image property accessor - new to this version.
    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;//new to this version
      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;//new to this version
    }//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) {
      //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.
        if(position.X < -image.Width){
          position.X = windowSize.X;
          NewDirection();
        }//end if

        if(position.X > windowSize.X){
          position.X = -image.Width/2;
          NewDirection();
        }//end if

        if(position.Y < -image.Height) {
          position.Y = windowSize.Y;
          NewDirection();
        }//end if

        if(position.Y > windowSize.Y){
          position.Y = -image.Height / 2;
          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
    //-------------------------------------------------//

    //This method is new to this version of the Sprite
    // class.
    //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 is new to this version of the Sprite
    // class.
    //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 15. The Game1 class for the project named XNA0130Proj.

/*Project XNA0130Proj
 * This project demonstrates how to integrate  
 * spiders, and ladybugs in a program using
 * objects of a Sprite class with collision
 * detection.
 * *****************************************************/
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNA0130Proj;

namespace XNA0130Proj {

  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 = 200;//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 = 5;//Max number of ladybugs
    double maxLadybugSpeed = 15;

    //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();
    //-------------------------------------------------//

    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);

      //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.
        spiders[cnt].Position = new Vector2(
           (float)(windowWidth * random.NextDouble()),
           (float)(windowHeight * 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

      //Use the same process to instantiate all of the
      // ladybugs and cause them to move from right to
      // left, bottom to top.
      for(int cnt = 0;cnt < numLadybugs;cnt++) {
        ladybugs.Add(
                   new Sprite("ladybug",Content,random));
        ladybugs[cnt].Position = new Vector2(
            (float)(windowWidth * random.NextDouble()),
            (float)(windowHeight * random.NextDouble()));
        ladybugs[cnt].Direction = DirectionVector(
         (float)maxVectorLength,
         (float)(maxVectorLength * random.NextDouble()),
         true,//xNeg
         true);//yNeg
        ladybugs[cnt].WindowSize =
                     new Point(windowWidth,windowHeight);
        ladybugs[cnt].Speed = maxLadybugSpeed / 2
             + maxLadybugSpeed * random.NextDouble() / 2;
      }//end for loop

    }//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 all the ladybugs in the list to move.
      for(int cnt = 0;cnt < ladybugs.Count;cnt++) {
        ladybugs[cnt].Move(gameTime);
      }//end for loop

      //Tell each ladybug to test for a collission 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. Cause the spider to
          // move 128 pixels to the right.
          target.Position = 
                    new Vector2(target.Position.X + 128,
                                target.Position.Y);

          //If the collision was with a black widow
          // spider, cause it to reincarnate into a
          // green spider.
          if(target.Image.Name == "blackWidowSpider") {
            target.SetImage("greenspider",Content);
          }//If the collision was with a green spider,
           // cause it to reincarnate into a brown
           // spider.
          else if(target.Image.Name == "greenspider") {
            target.SetImage("brownSpider",Content);
          }//If the collision was with a brown spider,
           // it gets eaten. Remove it from the list of
           // spiders.
          else if(target.Image.Name == "brownSpider") {
            spiders.Remove(target);
          }// end else-if
        }//end if
      }//end for loop

      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

      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-