获取像素完美的BitmapData碰撞的上,下,最右和最左点

时间:2014-07-18 12:48:55

标签: actionscript-3 flash actionscript 2d collision-detection

如何获得像素完美的BitmapData碰撞的上,下,最右和最左点?这是我的碰撞检测代码:

public static function checkCollision(object1:*, object2:*, debug:Boolean = false):Boolean{
    var object1Rect:Rectangle = object1.getRect(stage);
    var object2Rect:Rectangle = object2.getRect(stage);
    var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
    var object2Point:Point = new Point(object2Rect.x, object2Rect.y);

    var bitmapData1:BitmapData = new BitmapData(
        object1Rect.width, 
        object1Rect.height, 
        true, 
        0
    );
    var bitmapData2:BitmapData = new BitmapData(
        object2Rect.width, 
        object2Rect.height, 
        true, 
        0
    );

    var clr:ColorTransform = new ColorTransform();
    if(debug)
        clr.color = 0x00ff00;

    bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y), clr);
    bitmapData2.draw(object2, null, clr);

    if(debug){
        if(bmp1.stage)
            stage.removeChild(bmp1);
        bmp1 = new Bitmap(bitmapData1);
        bmp1.x = object1Point.x;
        bmp1.y = object1Point.y;
        stage.addChild(bmp1);

        if(bmp2.stage)
            stage.removeChild(bmp2);
        bmp2 = new Bitmap(bitmapData2);
        bmp2.x = object2Point.x;
        bmp2.y = object2Point.y;
        stage.addChild(bmp2);
    }

    var bCollide:Boolean = bitmapData1.hitTest(
        object1Point,
        255,
        bitmapData2,
        object2Point,
        255
    );

    if(!debug){
        bitmapData1.dispose();
        bitmapData2.dispose();
    }
        return bCollide;
}

它完美无缺。但是,我用来检测顶部生命值的代码不能正常工作。这是代码:

public static function getHitPoint(object1:*, object2:*):Point{
    var point:Point = new Point();

    var object1Rect:Rectangle = object1.getRect(stage);
    var object2Rect:Rectangle = object2.getRect(stage);
    var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
    var object2Point:Point = new Point(object2Rect.x, object2Rect.y);

    var bitmapData1:BitmapData = new BitmapData(
        object1.width, 
        object1.height, 
        true, 
        0
    );
    var bitmapData2:BitmapData = new BitmapData(
        object2.width, 
        object2.height, 
        true, 
        0
    );

    bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y));
    bitmapData2.draw(object2);

    var bitmap1:Bitmap = new Bitmap(bitmapData1);
    var bitmap2:Bitmap = new Bitmap(bitmapData2);

    bitmap1.x = object1Point.x;
    bitmap1.y = object1Point.y;
    bitmap2.x = object2Point.x;
    bitmap2.y = object2Point.y;

    var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
    var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);

    var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(
        new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1Rect.width, object1Rect.height)
    );

    for(var i:String in overlappingPixels){
        var index:uint = uint(i);
        if(overlappingPixels[i] != 0){
            point.x = (index % object1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
            point.y = (uint(index / bitmap1.height)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);
            break;
        }
    }

    return point;
}

我不知道为什么,但getHitPoint()函数有时会返回错误的坐标。任何人都可以解释为什么会这样吗?我怎样才能检测最底部,最左边和最右边的生命值?

修改
我现在知道为什么getHitPoint()有时会返回错误的值:point.y = (uint(index / bitmap1.height)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);应为point.y = (uint(index/bitmap1. width )) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);

编辑2
我发现了如何获得最低点:

public static function getHitPoint(object1:*, object2:*, direction:int = 0):*{
    var point:Point = new Point();

    var object1Rect:Rectangle = object1.getRect(stage);
    var object2Rect:Rectangle = object2.getRect(stage);
    var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
    var object2Point:Point = new Point(object2Rect.x, object2Rect.y);

    var bitmapData1:BitmapData = new BitmapData(
        Math.round(object1Rect.width), 
        Math.round(object1Rect.height), 
        true, 
        0
    );
    var bitmapData2:BitmapData = new BitmapData(
        Math.round(object2Rect.width), 
        Math.round(object2Rect.height), 
        true, 
        0
    );

    bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y));
    bitmapData2.draw(object2);

    var bitmap1:Bitmap = new Bitmap(bitmapData1);
    var bitmap2:Bitmap = new Bitmap(bitmapData2);

    bitmap1.x = object1Point.x;
    bitmap1.y = object1Point.y;
    bitmap2.x = object2Point.x;
    bitmap2.y = object2Point.y;

    var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
    var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);

    var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(
        new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1Rect.width, object1Rect.height)
    );

    switch(direction){
        case 0: //top
            for(var i:String in overlappingPixels){
                var index:uint = uint(i);
                if(overlappingPixels[i] != 0){
                    point.x = (index % bitmap1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
                    point.y = (uint((index)/bitmap1.width)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);
                    return point;
                }
            }

        case 1: //right
            // I still need this

        case 2: //bottom
            overlappingPixels.reverse();

            for(var i:String in overlappingPixels){
                var index:uint = uint(i);
                if(overlappingPixels[i] != 0){
                    point.x = bitmap1.width - (index % bitmap1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
                    point.y =  (bitmap2.y + bitmap2.height > bitmap1.y + bitmap1.height ? bitmap1.y + bitmap1.height : bitmap2.y + bitmap2.height) - (uint(index/bitmap1.width));
                    return point;
                }
            }

        case 3: //left
            // I still need this too
    }
    return false;
}

我仍然需要一种方法来获得最左边和最右边的生命值

1 个答案:

答案 0 :(得分:0)

你不需要像在那里那样去做。您可以在单个函数中完成所有操作,这将正确返回所有内容。我在下面添加了评论。请注意我已经改变的内容,就像你现在正在尝试的那样,使用你改变的代码,这是不可能的。

这适用于任何形状,任何方向。它会给你确切的碰撞X和Y.

不要将其变为静态功能。将它放入一个全局类中,然后使用Singleton来管理它。当您使用静态函数并引用阶段时,事情开始变得非常严重。

此外,如果您要使用小于1的像素值(即99.75),则需要进行一些调整以适应这一点。考虑到你的Math.round用法,我假设你正在使用整个像素。

     /**
     * 
     * @param   object1
     * @param   object2
     * @return
     */
    private function getHitPoint(object1:*, object2:*):*{
        var point:Point;

        // X and Y where we hit
        // do NOT change this to a stage location or it does NOT work
        var object1Point:Point = new Point(object1.x, object1.y);
        var object2Point:Point = new Point(object2.x, object2.y);

        var bitmapData1:BitmapData = new BitmapData(
            Math.round(object1.width), 
            Math.round(object1.height), 
            true, 
            0
        );
        var bitmapData2:BitmapData = new BitmapData(
            Math.round(object2.width), 
            Math.round(object2.height), 
            true, 
            0
        );

        // Draw
        bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1.x, -object1.y));
        bitmapData2.draw(object2);

        // Create BMP's
        var bitmap1:Bitmap = new Bitmap(bitmapData1);
        var bitmap2:Bitmap = new Bitmap(bitmapData2);

        // Set X and Y and BMP
        bitmap1.x = object1Point.x;
        bitmap1.y = object1Point.y;
        bitmap2.x = object2Point.x;
        bitmap2.y = object2Point.y;

        // BMP origin is the object1 X and Y
        var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
        // Create a local version of the bitmap2 so we can see what is overlapping
        var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);

        // Create a rectangle from what we now know
        var rect:Rectangle = new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1.width, object1.height);

        // The overlapping pixels are within the rectangle, so get them all
        var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(rect);

        // Run through all the overlapping pixels until we find a colourful one
        for (var i:uint = 0; i < overlappingPixels.length; i++ ) {
            var index:uint = overlappingPixels[i];

            // If the colour is not 0, we have found it
            if(index != 0){
                point = new Point();

                // Basically, instead of using width and getting 100, we're working out how
                // many pixles across the overlap is. The Vector doesn't tell us this, so we need to work it out
                var overlappingWidth:uint = object1.width - Math.abs(bitmap2OriginLocal.x);

                // The Y is object1.y, minus the local y, plus object1's width minus the X from the local
                point.y = object1.y - bitmap2OriginLocal.y + uint(i / overlappingWidth);
                // The X is the same as above, but % of the width
                point.x = object1.x - bitmap2OriginLocal.x + (i % overlappingWidth);

                // Found it, we're done
                break;
            }

        }

        // Only fires when you've got a collision that is less than 1 pixel from the width or height
        // Just a fail safe
        if (!point) {
            point = new Point(object1.width, object1.height);
        }

        return point;
    }

对于上下文,我的整个课程在下面显示了我如何使用此功能。您可以复制/粘贴此类,它将起作用。它显示了如何在屏幕上移动精灵,一旦发现碰撞,它就可以解决碰撞发生的位置。

此类用于绝对像素完美碰撞检测,包括一个示例。

package kazo 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.geom.Matrix;

    /**
     * ...
     * @author KM
     */
    public class TestCases2 extends Sprite
    {

        private var rect    :Sprite;
        private var circle  :Sprite;

        /**
         * 
         */
        public function TestCases2() 
        {
            addEventListener(Event.ADDED_TO_STAGE, init);
        }

        public function init(e:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);

            trace('Starting test case');

            // Rectangle
            rect = new Sprite();
            // Circle
            circle = new Sprite();

            // Draw the rectangle. Center point must be TOP LEFT
            // If you're using Flash Professional, place everything at 0,0 inside the MC
            rect.graphics.beginFill(0xff0000);
            rect.graphics.drawRect(0, 0, 100, 100);
            rect.graphics.endFill();

            // Draw the circle. Center point is TOP LEFT, so the X and Y of the circle need to be equal to the radius
            circle.graphics.beginFill(0xffff00);
            circle.graphics.drawCircle(50, 50, 50);
            circle.graphics.endFill();

            // Add them
            addChild(rect);
            addChild(circle);

            // Position
            rect.x = 225;
            rect.y = 75;

            // Position
            circle.x = 225;
            circle.y = 225;

            // Frame loop
            addEventListener(Event.ENTER_FRAME, frameFunc);
        }

        /**
         * 
         * @param   e
         */
        private function frameFunc(e:Event):void {
            // move them around
            circle.y -= 2;
            circle.x += 0;
            rect.y += 1;
            rect.x += 0;

            // Check for collisions. If found, stop. Pass 'true' as the final param if you want it to draw to the screen
            if (checkCollision(rect, circle)) {
                var ref:Point = getHitPoint(rect, circle);

                // Draws where the collision hit
                var loc:Sprite = new Sprite();
                loc.graphics.beginFill(0x000000);
                loc.graphics.drawRect(0, 0, 10, 10);
                loc.graphics.endFill();
                addChild(loc);

                loc.x = ref.x;
                loc.y = ref.y;

                trace(ref);

                removeEventListener(Event.ENTER_FRAME, frameFunc);
            }
        }

         /**
         * 
         * @param   _obj1
         * @param   _obj2
         * @param   _debug
         * @return
         */
        private function checkCollision(_obj1:Sprite, _obj2:Sprite, _debug:Boolean = false):Boolean {
            // Draw the first item to bitmapdata
            var bmd1:BitmapData = new BitmapData(_obj1.width, _obj1.height, true, 0);
            // ..and the second
            var bmd2:BitmapData = new BitmapData(_obj2.width, _obj2.height, true, 0);

            // Now draw them
            bmd1.draw(_obj1);
            bmd2.draw(_obj2);

            // If we're in debug, also add the bitmap to the stage so we can see where we are
            if (_debug) {
                var bmp:Bitmap = new Bitmap(bmd1);
                bmp.x = _obj1.x;
                bmp.y = _obj1.y;
                addChild(bmp);

                var bmp2:Bitmap = new Bitmap(bmd2);
                bmp2.x = _obj2.x;
                bmp2.y = _obj2.y;
                addChild(bmp2);
            }

            // Hit test including alpha channel. Obj1 X/Y, Obj2 X/Y, alpha channel
            var rtn:Boolean = bmd1.hitTest(new Point(_obj1.x, _obj1.y), 255, bmd2, new Point(_obj2.x, _obj2.y), 255);

            // Dispose the bitmap data, we dont need it anymore
            if (!_debug) {
                bmd1.dispose();
                bmd2.dispose();
            }


            // Return the boolean
            return rtn;
        }

        /**
         * 
         * @param   object1
         * @param   object2
         * @return
         */
        private function getHitPoint(object1:*, object2:*):*{
            var point:Point;

            // X and Y where we hit
            // do NOT change this to a stage location or it does NOT work
            var object1Point:Point = new Point(object1.x, object1.y);
            var object2Point:Point = new Point(object2.x, object2.y);

            var bitmapData1:BitmapData = new BitmapData(
                Math.round(object1.width), 
                Math.round(object1.height), 
                true, 
                0
            );
            var bitmapData2:BitmapData = new BitmapData(
                Math.round(object2.width), 
                Math.round(object2.height), 
                true, 
                0
            );

            // Draw
            bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1.x, -object1.y));
            bitmapData2.draw(object2);

            // Create BMP's
            var bitmap1:Bitmap = new Bitmap(bitmapData1);
            var bitmap2:Bitmap = new Bitmap(bitmapData2);

            // Set X and Y and BMP
            bitmap1.x = object1Point.x;
            bitmap1.y = object1Point.y;
            bitmap2.x = object2Point.x;
            bitmap2.y = object2Point.y;

            // BMP origin is the object1 X and Y
            var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
            // Create a local version of the bitmap2 so we can see what is overlapping
            var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);

            // Create a rectangle from what we now know
            var rect:Rectangle = new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1.width, object1.height);

            // The overlapping pixels are within the rectangle, so get them all
            var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(rect);

            // Run through all the overlapping pixels until we find a colourful one
            for (var i:uint = 0; i < overlappingPixels.length; i++ ) {
                var index:uint = overlappingPixels[i];

                // If the colour is not 0, we have found it
                if(index != 0){
                    point = new Point();

                    // Basically, instead of using width and getting 100, we're working out how
                    // many pixles across the overlap is. The Vector doesn't tell us this, so we need to work it out
                    var overlappingWidth:uint = object1.width - Math.abs(bitmap2OriginLocal.x);

                    // The Y is object1.y, minus the local y, plus object1's width minus the X from the local
                    point.y = object1.y - bitmap2OriginLocal.y + uint(i / overlappingWidth);
                    // The X is the same as above, but % of the width
                    point.x = object1.x - bitmap2OriginLocal.x + (i % overlappingWidth);

                    // Found it, we're done
                    break;
                }

            }

            // Only fires when you've got a collision that is less than 1 pixel from the width or height
            // Just a fail safe
            if (!point) {
                point = new Point(object1.width, object1.height);
            }

            return point;
        }

    }

}