UPDATE: Check out the most recent update to this post.
The Flash Player provides vector-perfect collision detection with the hitTest() method. The drawback of this method is that if you use Bitmaps it sees just the bounding-box of the Bitmap, i.e. it ignores alpha. This is pretty frustrating when it comes to a sprite-based game.
A bit of digging on the web found a quick and simple solution from Andre Michelle [.zip]. A slightly more robust version was written for AS2 by Grant Skinner, then ported to AS3 by Boulevart. I grabbed their version to start with...
First, I cleaned up the code and commented it. It's pretty straightforward: you render two display objects to separate color channels, combine the color channels, then search the resulting image for any overlapping color (the previous two sites have deeper explanations).
I found two problems with the code. First, it unions the bounding boxes of the two display objects. This is unnecessary, we only care about the pixels in the intersecting rectangle. And since performance hit of this technique grows with the size of the overlap, this is a critical optimization (you're allocating significantly less BitmapData each test).
Second, the code doesn't account for a display list of any complexity: it assumes the two display objects have the same parent (and are thus in the same coordinate space), and it assumes the display objects do not have any scale or rotation (it only considers linear transformation, i.e. the objects' positions). I fixed both of these.
So, the new code will determine if two DisplayObjects are visibly overlapping with pixel precision. Enjoy!
-
package
-
{
-
import flash.display.BitmapData;
-
import flash.display.BitmapDataChannel;
-
import flash.display.BlendMode;
-
import flash.display.DisplayObject;
-
import flash.display.DisplayObjectContainer;
-
import flash.geom.Matrix;
-
import flash.geom.Point;
-
import flash.geom.Rectangle;
-
-
public class PixelPerfectCollisionDetection
-
{
-
/** Get the collision rectangle between two display objects. **/
-
public static function getCollisionRect(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Rectangle
-
{
-
// get bounding boxes in common parent's coordinate space
-
var rect1:Rectangle = target1.getBounds(commonParent);
-
var rect2:Rectangle = target2.getBounds(commonParent);
-
-
// find the intersection of the two bounding boxes
-
var intersectionRect:Rectangle = rect1.intersection(rect2);
-
-
if (intersectionRect.size.length> 0)
-
{
-
if (pixelPrecise)
-
{
-
// size of rect needs to integer size for bitmap data
-
intersectionRect.width = Math.ceil(intersectionRect.width);
-
intersectionRect.height = Math.ceil(intersectionRect.height);
-
-
// get the alpha maps for the display objects
-
var alpha1:BitmapData = getAlphaMap(target1, intersectionRect, BitmapDataChannel.RED, commonParent);
-
var alpha2:BitmapData = getAlphaMap(target2, intersectionRect, BitmapDataChannel.GREEN, commonParent);
-
-
// combine the alpha maps
-
alpha1.draw(alpha2, null, null, BlendMode.LIGHTEN);
-
-
// calculate the search color
-
var searchColor:uint;
-
if (tolerance <= 0)
-
{
-
searchColor = 0x010100;
-
}
-
else
-
{
-
if (tolerance> 1) tolerance = 1;
-
var byte:int = Math.round(tolerance * 255);
-
searchColor = (byte <<16) | (byte <<8) | 0;
-
}
-
-
// find color
-
var collisionRect:Rectangle = alpha1.getColorBoundsRect(searchColor, searchColor);
-
collisionRect.x += intersectionRect.x;
-
collisionRect.y += intersectionRect.y;
-
-
return collisionRect;
-
}
-
else
-
{
-
return intersectionRect;
-
}
-
}
-
else
-
{
-
// no intersection
-
return null;
-
}
-
}
-
-
/** Gets the alpha map of the display object and places it in the specified channel. **/
-
private static function getAlphaMap(target:DisplayObject, rect:Rectangle, channel:uint, commonParent:DisplayObjectContainer):BitmapData
-
{
-
// calculate the transform for the display object relative to the common parent
-
var parentXformInvert:Matrix = commonParent.transform.concatenatedMatrix.clone();
-
parentXformInvert.invert();
-
var targetXform:Matrix = target.transform.concatenatedMatrix.clone();
-
targetXform.concat(parentXformInvert);
-
-
// translate the target into the rect's space
-
targetXform.translate(-rect.x, -rect.y);
-
-
// draw the target and extract its alpha channel into a color channel
-
var bitmapData:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
-
bitmapData.draw(target, targetXform);
-
var alphaChannel:BitmapData = new BitmapData(rect.width, rect.height, false, 0);
-
alphaChannel.copyChannel(bitmapData, bitmapData.rect, new Point(0, 0), BitmapDataChannel.ALPHA, channel);
-
-
return alphaChannel;
-
}
-
-
/** Get the center of the collision's bounding box. **/
-
public static function getCollisionPoint(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Point
-
{
-
var collisionRect:Rectangle = getCollisionRect(target1, target2, commonParent, pixelPrecise, tolerance);
-
-
if (collisionRect != null && collisionRect.size.length> 0)
-
{
-
var x:Number = (collisionRect.left + collisionRect.right) / 2;
-
var y:Number = (collisionRect.top + collisionRect.bottom) / 2;
-
-
return new Point(x, y);
-
}
-
-
return null;
-
}
-
-
/** Are the two display objects colliding (overlapping)? **/
-
public static function isColliding(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Boolean
-
{
-
var collisionRect:Rectangle = getCollisionRect(target1, target2, commonParent, pixelPrecise, tolerance);
-
-
if (collisionRect != null && collisionRect.size.length> 0) return true;
-
else return false;
-
}
-
}
-
}
This article really helped me to grasp collision detection principles. Are there any examples or hints on catching the collision point/s between the two objects and getting its angle?
Many thanks.
I think, a way we use could use above code to hints on catching the collision point/s between the two objects and getting its angle is that we design submovie in main movies and apply this code for between submovies rather than between main movies. I 've try and test succesffully
just wanted to point out that cacheAsBitmap must be set to false for the parent object, or you'll get strange/inconsistent results.
Hi im a bit lost on how to use this class.
could you post an example?
Hi im a bit lost on how to use this class.
could you post an example?
Thank you very much, this is nice article.
Does not work, just as none of the others like it. Tried using this and collision is still on bounding box just like with HitTesetObject()