Pixel perfect collision in Unity 2D

  • Post author:
  • Post category:Gamedev

I’m going to give a short overview of how pixel perfect collision works, and how I implemented it in Unity using C#.
The source files and usage are at the bottom.

Overview:
Pixel perfect collision assumes that sprites are never rotated.
1. Start with checking if the two rectangles of the sprites are colliding, if they are not, there is no point in testing per pixel collision. This is a simple AABB collision test. For this, we use the Bounds2D class. Some code from it:

public bool overlaps(Bounds2D other) {
    return isHorizontalOverlap(other) && isVerticalOverlap(other);
}

public bool isHorizontalOverlap(Bounds2D other) {
    return !(x > other.x + other.width || other.x > x + width);
}

public bool isVerticalOverlap(Bounds2D other) {
    return !(y > other.y + other.height || other.y > y + height);
}

This code checks if other is on the left or right of this. If it is, then it’s not colliding.
Then does the same for top and bottom.

2. We need the intersection of the two rectangles, to know which section of the texture we need to compare:

public Bounds2D intersection(Bounds2D other) {
    Bounds2D ret = new Bounds2D();
    ret.x = Mathf.Max(x, other.x);
    ret.y = Mathf.Max(y, other.y);
    ret.width = Mathf.Abs(ret.x - Mathf.Min(x+width,other.x+other.width));
    ret.height = Mathf.Abs(ret.y - Mathf.Min(y+height,other.y+other.height));
    return ret;
}

We then need to translate that intersection to a local position of the specific texture:

public Bounds2D toLocal(Bounds2D global) {
    Bounds2D ret = new Bounds2D();
    ret.x = global.x - x;
    ret.y = global.y - y;
    ret.width = global.width;
    ret.height = global.height;
    return ret;
}

3. We want to get the pixel data from the Texture2D, using the intersection we found:

private void getBits() {
    //Intersection of the two bounds in global space.
    Bounds2D intersection = boundsA.intersection(boundsB);
		
    //Intersections in local space.
    Bounds2D intersectionA = boundsA.toLocal(intersection);
    Bounds2D intersectionB = boundsB.toLocal(intersection);
		
    bitsA = getBitArray(intersectionA, a.collisionMask);
    bitsB = getBitArray(intersectionB, b.collisionMask);
}
	
private static Color[] getBitArray(Bounds2D section, Texture2D texture) {
    return texture.GetPixels(section.x, section.y, section.width,section.height);
}

One important thing to note, is that you’ll need to set the texture to readable in order to access the pixel data. You do this by changing a texture to advanced, and checking the readable checkbox.

4. Now that we have the pixel data, we simply compare the alpha of every pixel:

private bool perPixelCollision() {
    getBits();
    for (int i = 0; i < bitsA.Length; i++) {						
        // If both colors are not transparent (the alpha channel is not 0), then there is a collision
        if (bitsA[i].a != 0 && bitsB[i].a != 0)	{
            return true;
        }
    }
    return false;
}

Usage:
Here you can download the full code. The usage is fairly simple.
SpriteCollider is the only MonoBehavior. you can either place it onto an object with a Sprite Renderer and it will get the texture automatically.
Or you can use a custom mask by placing a texture into the collisionMask field in the inspector.
If you uncheck pixelCollision, it will only check bounds, which is good if your sprite is a full rectangle.

This collision does not work with Unity’s physics engine, and does not trigger any collision methods. You have to manually check for collisions where you want to.

Don’t forget you’ll need to set the texture to readable in order to access the pixel data. You do this by changing a texture to advanced, and checking the readable checkbox.

You can download the files here

Credit to http://rylgh.com/pixelshitter/ from whom I learned how to make pixel perfect collision.

Aurore

Aurore is a competitive KeyForge player and the founder of Timeshapers. She's a content writer by trade and aspiring game designer. Follow @Timeshapers1