Musings by Generator

Development, Life and everything else in S.A.

Per-Pixel Collision Detection in 2D games

I was following the tutorial on the XNA website on how to go about creating a 2D game using the framework.

Once I had gone through it and completed it and then ran the game, I noticed that the collision detection is not entirely perfect since the tutorial uses a rectangle on each object to detect collision, so if your two objects are not a perfect rectangle, you could end up with a false positive as indicated by the picture below:

2d Collision Rectangles

Even though the rectangles are intersecting, the two objects are not, so therefore, we do not have a collision yet, we only want to detect a collision to be detected when the two objects actually touch each other.

So, with a game plan in mind, I hit the XNA website looking at what they had there. I wanted something simple and I found lots of resources on 2D per Pixel Collision detection.

I found one that I really liked. It is actually one in a series of Collision articles (from 2D to 3D). I went through it a couple of times. the actual code that is implemented in the game to detect the collision seemed simple enough but I wanted to understand it. Below is the code that checks for a collision:

public static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
{
    // Find the bounds of the rectangle intersection
    int top = Math.Max(rectangleA.Top, rectangleB.Top);
    int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
    int left = Math.Max(rectangleA.Left, rectangleB.Left);
    int right = Math.Min(rectangleA.Right, rectangleB.Right);

    // Check every point within the intersection bounds
    for (int y = top; y < bottom; y++)
    {
           for (int x = left; x < right; x++)
        {
            // Get the color of both pixels at this point
            Color colorA = dataA[(x - rectangleA.Left) +
                                 (y - rectangleA.Top) * rectangleA.Width];
            Color colorB = dataB[(x - rectangleB.Left) +
                                 (y - rectangleB.Top) * rectangleB.Width];

            // If both pixels are not completely transparent,
            if (colorA.A != 0 && colorB.A != 0)
            {
                // then an intersection has been found
                return true;
            }
        }
    }

    // No intersection found
    return false;
}

All that really happens is that the area of intersection, a Rectangle, is found (lines 4 – 7), and then for every position within that rectangle, the pixels are checked for each object, if they are both not transparent then a collision has happened, otherwise keep checking and return false if no collision was found.

Implementation

To Implement this method in the completed 2D tutorial (if you don’t have it, it is available here) I created a new Project as a Windows Game Library, which compiles into a DLL, called Common. I then added a static class called Common, and added the above code to that class.

To actually use that method I modified the GameObject class to hold the Texture2D data of each object by adding a new member variable:

public Color[] TextureData;

Then in the loadContent method, I modified the two for loops to get the data into this array for each CannonBall and enemy object:

for (int i = 0; i < maxCannonBalls; i++)
{
	cannonBalls[i] = new GameObject(Content.Load<Texture2D>("Sprites\\cannonball"));
	cannonBalls[i].TextureData = new Color[cannonBalls[i].sprite.Width * cannonBalls[i].sprite.Height];
    cannonBalls[i].sprite.GetData(cannonBalls[i].TextureData);
}

for (int i = 0; i < maxEnemies; i++)
{
	enemies[i] = new GameObject(Content.Load<Texture2D>("Sprites\\enemy"));
    enemies[i].TextureData = new Color[enemies[i].sprite.Width * enemies[i].sprite.Height];
    enemies[i].sprite.GetData(enemies[i].TextureData);
}

For each object, once it is created, I calculate the size of the array and at the same time initialise the array, and I then use the GetData method of the Texture2D object to fill the array.

To detect the collision between a cannon ball and an enemy, I modified the UpdateCannonBall Method to use the above method in the Common class:

public void UpdateCannonBalls()
{
	foreach (GameObject ball in cannonBalls)
    {
    	if (ball.alive)
        {
        	ball.position += ball.velocity;
			if (!viewportRect.Contains(new Point(
				(int)ball.position.X, (int)ball.position.Y)))
			{
            	ball.alive = false;
                continue;
			}

			Rectangle cannonBallRect = new Rectangle(
			(int)ball.position.X, (int)ball.position.Y,
			ball.sprite.Width, ball.sprite.Height);

			foreach (GameObject enemy in enemies)
            {
            	Rectangle enemyRect = new Rectangle(
				(int)enemy.position.X, (int)enemy.position.Y,
				enemy.sprite.Width, enemy.sprite.Height);

				if (Common.Common.IntersectPixels(
				    cannonBallRect, ball.TextureData, enemyRect,
					enemy.TextureData))
                {
					ball.alive = false;
					enemy.alive = false;
                    score += 1;
                    break;
               	}
			}
      	}
	}
}

The line we are really interested is line 25 to 27. This is the call to the static method to check if there is an intersection between the cannon ball and enemy.

This is not perfect either but it is a lot better than the Rectangle bound detection used in the example. I see there is a further expansion on this code in the next article in the section so I will be looking at that soon.

Catch you on the flip side.

NB: Requirements for the above code:

« Newer Posts