Search code examples
actionscript-3bitmapcollision-detectionmoviecliphittest

ActionScript 3's Bitmap Hittest Collision Checking On Movieclips with Offset


I am having trouble getting the bitmap hittest method to work properly on my movieclips. I tried several times using information from different sources, but none worked. I came across a pre-made function that could deal with all the hittest confusion for me, and I tried to use it. I tried it first on a new program and discovered that in order for the function to work, the movieclips must be centered at the top left, but considering that I have to use a specific point of rotation on my movieclips, centering at the top left isn't an option. I also tried changing how the offsets are calculated to see if I could manipulate each to the top left, but it didn't seem to do any good.

I know this is a very specific question, but there isn't enough information on bitmap hittests for me to understand this myself and I think this could also be useful for others trying to use and understand precise collisions.

Here is the code for the pre-made function:

var _returnValue:Boolean;
var _onePoint:Point;
var _twoPoint:Point;
var _oneRectangle:Rectangle;
var _twoRectangle:Rectangle;
var _oneClipBmpData:BitmapData;
var _twoClipBmpData:BitmapData;
var _oneOffset:Matrix;
var _twoOffset:Matrix;
function complex(clip1:DisplayObjectContainer, clip2:DisplayObjectContainer):Boolean
{
            _returnValue = false;

            _twoRectangle = clip1.getBounds(clip1);
            _oneOffset = clip1.transform.matrix;
            _oneOffset.tx = clip1.x - clip2.x;
            _oneOffset.ty = clip1.y - clip2.y;

            _twoClipBmpData = new BitmapData(_twoRectangle.width, _twoRectangle.height, true, 0);
            _twoClipBmpData.draw(clip1, _oneOffset);        

            _oneRectangle = clip2.getBounds(clip2);
            _oneClipBmpData = new BitmapData(_oneRectangle.width, _oneRectangle.height, true, 0);

            _twoOffset = clip2.transform.matrix;
            _twoOffset.tx = clip2.x - clip2.x;
            _twoOffset.ty = clip2.y - clip2.y;

            _oneClipBmpData.draw(clip2, _twoOffset);

            _onePoint = new Point(_oneRectangle.x, _oneRectangle.y);
            _twoPoint = new Point(_twoRectangle.x, _twoRectangle.y);

            if(_oneClipBmpData.hitTest(_onePoint, 255, _twoClipBmpData, _twoPoint, 255))
            {
                _returnValue = true;
            }

            _twoClipBmpData.dispose();
            _oneClipBmpData.dispose();

            return _returnValue;
}

Here is the code to call the pre-made function:

var inVision:Boolean = complex(visionVector[controlBirds], birdVector[checkLRD]);

Here are screenshots of the movieclips:

Bird movieclip (The movieclip referenced by the birdVector)

Vision movieclip (The movieclip referenced by the visionVector)

Any help would be much appreciated.


Solution

  • The function you shared is fairly straightforward. It creates a bitmap from each of your MovieClips and checks the alpha channel for overlapping pixels. As you noted, the offsets are your problem, and you need to change the area you sample from.

    Reading the API for BitmapData.draw(), you can see that the second argument accepts a matrix. Using Matrix.translate(), we can offset the sample region from the default 0,0 coordinates (though matrix.tx and matrix.ty are the equivalent properties).

    The second thing to be aware of is that the Point objects being passed to hitTest() define where to begin the overlap of the two BitmapData objects being tested (since they don't have any relationship to the stage, we have to define where these fictional objects are sitting). That location needs to be both the [location of object] + [bounding offset].

    Below is the refactored function...

    function testCollision(a:DisplayObject, b:DisplayObject):Boolean {
        var aBmp:BitmapData = new BitmapData(a.width, a.height, true, 0);
        var aRect:Rectangle = a.getBounds(a);
        var aMatrix:Matrix = new Matrix();
        aMatrix.translate(-aRect.x, -aRect.y)
        aBmp.draw(a, aMatrix);
    
        var bBmp:BitmapData = new BitmapData(b.width, b.height, true, 0);
        var bRect:Rectangle = b.getBounds(b);
        var bMatrix:Matrix = new Matrix();
        bMatrix.translate(-bRect.x, -bRect.y)
        bBmp.draw(b, bMatrix);
    
        return (bBmp.hitTest(new Point(bRect.x + b.x, bRect.y + b.y), 255, aBmp, new Point(aRect.x + a.x, aRect.y + a.y), 255));
    }