12
\$\begingroup\$

Is there a well-known way (or perhaps reusable bit of code) for pixel-perfect collision detection in XNA?

I assume this would also use polygons (boxes/triangles/circles) for a first-pass, quick-test for collisions, and if that test indicated a collision, it would then search for a per-pixel collision.

This can be complicated, because we have to account for scale, rotation, and transparency.

WARNING: If you're using the sample code from the link from the answer below, be aware that the scaling of the matrix is commented out for good reason. You don't need to uncomment it out to get scaling to work.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Polygons or sprites? \$\endgroup\$ Commented Jul 25, 2011 at 14:38
  • \$\begingroup\$ I'm not sure. Xna supports both? My edit mentions a combination of both, since bounding-box tests are a fast first-pass. \$\endgroup\$
    – ashes999
    Commented Jul 26, 2011 at 3:31
  • 1
    \$\begingroup\$ The collision detection will differ depending on whether you are using 3D/2D models or sprites. One has pixels. The other has vertices and edges. \$\endgroup\$ Commented Jul 26, 2011 at 11:17
  • \$\begingroup\$ Okay, I see what you're getting at now. I'm using sprites. Although I believe XNA implements them as a textured polygons. \$\endgroup\$
    – ashes999
    Commented Jul 26, 2011 at 11:22

3 Answers 3

22
\$\begingroup\$

I see that you tagged the question as 2d, so I'll go ahead and dump my code:

class Sprite {

    [...]

    public bool CollidesWith(Sprite other)
    {
        // Default behavior uses per-pixel collision detection
        return CollidesWith(other, true);
    }

    public bool CollidesWith(Sprite other, bool calcPerPixel)
    {
        // Get dimensions of texture
        int widthOther = other.Texture.Width;
        int heightOther = other.Texture.Height;
        int widthMe = Texture.Width;
        int heightMe = Texture.Height;

        if ( calcPerPixel &&                                // if we need per pixel
            (( Math.Min(widthOther, heightOther) > 100) ||  // at least avoid doing it
            ( Math.Min(widthMe, heightMe) > 100)))          // for small sizes (nobody will notice :P)
        {
            return Bounds.Intersects(other.Bounds) // If simple intersection fails, don't even bother with per-pixel
                && PerPixelCollision(this, other);
        }

        return Bounds.Intersects(other.Bounds);
    }

    static bool PerPixelCollision(Sprite a, Sprite b)
    {
        // Get Color data of each Texture
        Color[] bitsA = new Color[a.Texture.Width * a.Texture.Height];
        a.Texture.GetData(bitsA);
        Color[] bitsB = new Color[b.Texture.Width * b.Texture.Height];
        b.Texture.GetData(bitsB);

        // Calculate the intersecting rectangle
        int x1 = Math.Max(a.Bounds.X, b.Bounds.X);
        int x2 = Math.Min(a.Bounds.X + a.Bounds.Width, b.Bounds.X + b.Bounds.Width);

        int y1 = Math.Max(a.Bounds.Y, b.Bounds.Y);
        int y2 = Math.Min(a.Bounds.Y + a.Bounds.Height, b.Bounds.Y + b.Bounds.Height);

         // For each single pixel in the intersecting rectangle
         for (int y = y1; y < y2; ++y)
         {
             for (int x = x1; x < x2; ++x)
             {
                 // Get the color from each texture
                 Color a = bitsA[(x - a.Bounds.X) + (y - a.Bounds.Y)*a.Texture.Width];
                 Color b = bitsB[(x - b.Bounds.X) + (y - b.Bounds.Y)*b.Texture.Width];

                 if (a.A != 0 && b.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
                 {
                     return true;
                 }
             }
         }
        // If no collision occurred by now, we're clear.
        return false;
    }

    private Rectangle bounds = Rectangle.Empty;
    public virtual Rectangle Bounds
    {
        get
        {
            return new Rectangle(
                (int)Position.X - Texture.Width,
                (int)Position.Y - Texture.Height,
                Texture.Width,
                Texture.Height);
        }

    }

Edit: While this code is almost self explanatory, I did feel bad for not having comments, so I added some ;) I also got rid of the bitwise operations since it was doing basically what the Color.A property does in a more complicated way ;)

\$\endgroup\$
8
  • \$\begingroup\$ Down-vote for a code dump with no comments or explanation. \$\endgroup\$ Commented Jul 25, 2011 at 19:31
  • 12
    \$\begingroup\$ Any explanation would just be restating the code, and the code is fairly straightforward. \$\endgroup\$
    – user744
    Commented Jul 25, 2011 at 19:45
  • 2
    \$\begingroup\$ I know I mentioned this in my question, but I need to state it again: does this handle scaling and rotation? Can two scaled, rotated sprites correctly intersect? (I can handle adding a quick bounding-box first pass though.) Or is this covered with calls to Bounds? \$\endgroup\$
    – ashes999
    Commented Jul 26, 2011 at 3:33
  • 1
    \$\begingroup\$ Yes, I forgot about that! The code does not take transformation into consideration. Right now I don't have the time to edit, but you can have a look at the Collision Transformed sample until I do: create.msdn.com/en-US/education/catalog/tutorial/… \$\endgroup\$
    – pek
    Commented Jul 26, 2011 at 6:40
  • 1
    \$\begingroup\$ Just being pedantic, but you only need : CollidesWith(Sprite other, bool calcPerPixel = true); :) \$\endgroup\$ Commented Aug 8, 2011 at 21:16
4
\$\begingroup\$

On the App Hub, there is a very old sample that walks you through 2D collision detection from simple bounding boxes to pixel-tested on rotated and scaled sprites. It has been fully updated to 4.0. The whole series is worth a read if you are new to the topic.

http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed

I also found Riemer Grootjans approach interesting in his 2D shooters game. http://www.riemers.net/eng/Tutorials/XNA/Csharp/series2d.php

(It takes him a little while to get to it... http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Coll_Detection_Overview.php ... but you may want to follow along to see the problem he's solving)

But I caution you that Riemers sample is not XNA 4.0 and you may have to do a little work to get it running. It's not difficult or mysterious work, however.

\$\endgroup\$
2
  • \$\begingroup\$ Great links, but old links; I've used these already for my solution. \$\endgroup\$
    – ashes999
    Commented Sep 28, 2011 at 14:02
  • 1
    \$\begingroup\$ Awesome. I just figure when someone searches they can find your question and they'll have more resources. \$\endgroup\$ Commented Sep 30, 2011 at 14:46
0
\$\begingroup\$

I recommend creating a black and white collision map. Program it so the black pixels are collision points. Give the character a collision map too; When processing maps, use a algorithm that will turn big squares of black pixels into collision rectangles. Save this rectangle data in an array. You can the use the Rectangle intersects function to look for collisions. You can also transform the collision map with the texture.

This is a lot like using a collision matrix but more advanced and you can transform it! consider building a collision map generator tool if you need it. If you make it work, please share the code with others!

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .