Flash game中关于碰撞检测之Rectangle篇

来源:互联网 发布:南宁数据恢复 编辑:程序博客网 时间:2024/05/02 00:30

     yasu 发表了一段代码,比较了关于通过Rectangle的intersects,Sprite的hitTestObject以及他自己的Myrect这3种方法的效率。
代码及演示如下: 


//-------------------------------------------
// Collision Performance Test
// - [RECTANGLE] Rectangle.intersects
// - [MY RECT] calc orijinal
// - [HITTEST] Sprite.hitTestObject
//
//-----------------------------------------------
package
{
    import com.bit101.components.PushButton;
 
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.ColorTransform;
    import flash.geom.Rectangle;
 
    import net.hires.debug.Stats;
 
    [SWF(frameRate=60, width=465, height=465)]
    /**
     * Collision Performance Test
     * @author yasu
     */
    public class Main extends Sprite
    {
 
        private static const MAX:int = 400;
        private static const STAGE_W:int = 465;
        private static const STAGE_H:int = 465;
        private static const RECT_WH:int = 10;
        private static const COLOR_TRANS:ColorTransform =
                             new ColorTransform(0.6, 0.6, 0.6, 1);
 
        public function Main()
        {
            bmd = new BitmapData(STAGE_W, STAGE_H, false, 0xFF000000);
            addChild(new Bitmap(bmd));
 
            btnA = new PushButton(this, 80, STAGE_H - 50,
 "RECTANGLE", _onClick);
            btnB = new PushButton(this, 200, STAGE_H - 50,
 "MY RECT", _onClick);
            btnC = new PushButton(this, 320, STAGE_H - 50,
 "HITTEST", _onClick);
 
            addChild(new Stats);
        }
        private var MAX_SPEED:int = 20;
        private const RECT_BMD:BitmapData = new BitmapData(
                                 RECT_WH, RECT_WH, false, 0x666666);
        private const RECT_HIT_BMD:BitmapData = new BitmapData(
                                   RECT_WH, RECT_WH, false, 0x990000);
        private var bmd:BitmapData;
        private var btnA:PushButton;
        private var btnB:PushButton;
        private var btnC:*;
        private var myrects:Vector.<ObjMyRect>;
        private var objs:Array = [];
        private var rectangles:Vector.<ObjRectangle>;
        private var sprites:Vector.<ObjSprite>;
 
        private function _initMyRects():void
        {
            myrects = new Vector.<ObjMyRect>(MAX, true);
            for (var i:int = 0; i < MAX; i++)
            {
                myrects[ i ] = new ObjMyRect();
                var size:int = Math.random() * RECT_WH >> 0
                myrects[ i ].rect = new MyRect(
                    Math.random() * STAGE_W >> 0,
                    Math.random() * STAGE_H >> 0,
                    size,
                    size)
                myrects[ i ].vx = (Math.random() - 0.5) * MAX_SPEED >> 0;
                myrects[ i ].vy = (Math.random() - 0.5) * MAX_SPEED >> 0;
            }
        }
        private function _initRectangle():void
        {
            rectangles = new Vector.<ObjRectangle>(MAX, true);
            for (var i:int = 0; i < MAX; i++)
            {
                rectangles[ i ] = new ObjRectangle();
                var size:int = Math.random() * RECT_WH >> 0
                rectangles[ i ].rect = new Rectangle(
                    Math.random() * STAGE_W >> 0,
                    Math.random() * STAGE_H >> 0,
                    size,
                    size)
                rectangles[ i ].vx = (Math.random() - 0.5) * MAX_SPEED >> 0;
                rectangles[ i ].vy = (Math.random() - 0.5) * MAX_SPEED >> 0;
            }
        }
        private function _initSprites():void
        {
            sprites = new Vector.<ObjSprite>(MAX, true);
            for (var i:int = 0; i < MAX; i++)
            {
                sprites[ i ] = new ObjSprite();
                addChildAt(sprites[ i ], 1);
                var size:int = Math.random() * RECT_WH >> 0
                sprites[ i ].width = sprites[ i ].height = size;
                sprites[ i ].x = Math.random() * STAGE_W >> 0;
                sprites[ i ].y = Math.random() * STAGE_H >> 0;
                sprites[ i ].vx = (Math.random() - 0.5) * MAX_SPEED >> 0;
                sprites[ i ].vy = (Math.random() - 0.5) * MAX_SPEED >> 0;
            }
        }
 
        private function _onClick(e:Event):void
        {
            removeEventListener(Event.ENTER_FRAME, onEnterFrameA);
            removeEventListener(Event.ENTER_FRAME, onEnterFrameB);
            removeEventListener(Event.ENTER_FRAME, onEnterFrameC);
            rectangles = null;
            myrects = null;
            if (sprites)
            {
                for (var i:int = 0; i < sprites.length; i++)
                {
                    removeChild(sprites[ i ]);
                }
            }
            sprites = null;
            bmd.fillRect(bmd.rect, 0x0);
 
            switch (e.currentTarget)
            {
                case btnA:
                    _initRectangle();
                    addEventListener(Event.ENTER_FRAME, onEnterFrameA);
                    break;
                case btnB:
                    _initMyRects();
                    addEventListener(Event.ENTER_FRAME, onEnterFrameB);
                    break;
                case btnC:
                    _initSprites();
                    addEventListener(Event.ENTER_FRAME, onEnterFrameC);
                    break;
            }
        }
 
        private function onEnterFrameA(event:Event):void
        {
            var i:int, j:int;
 
            var elementA:ObjRectangle;
            var elementB:ObjRectangle;
            for (i = 0; i < rectangles.length; i++)
            {
                elementA = rectangles[ i ] as ObjRectangle;
                elementA.rect.x += elementA.vx;
                elementA.rect.y += elementA.vy;
 
                if (elementA.rect.x < 0 || elementA.rect.x > STAGE_W)
                    elementA.vx *= -1;
                if (elementA.rect.y < 0 || elementA.rect.y > STAGE_H)
                    elementA.vy *= -1;
 
                elementA.isHit = false;
            }
 
            for (i = 0; i < rectangles.length; i++)
            {
                elementA = rectangles[ i ] as ObjRectangle;
 
                for (j = 0; j < rectangles.length; j++)
                {
                    if (j <= i)
                        continue;
 
                    elementB = rectangles[ j ] as ObjRectangle;
 
                    if (elementA.rect.intersects(elementB.rect))
                    {
                        elementA.isHit = elementB.isHit = true;
                    }
                }
            }
 
            bmd.lock();
            bmd.colorTransform(bmd.rect, COLOR_TRANS);
            for (i = 0; i < rectangles.length; i++)
            {
                elementA = rectangles[ i ] as ObjRectangle;
                bmd.fillRect(elementA.rect, elementA.isHit ? 0x990000 : 0x666666);
            }
            bmd.unlock();
        }
 
        private function onEnterFrameB(event:Event):void
        {
            var i:int, j:int;
 
            var elementA:ObjMyRect;
            var elementB:ObjMyRect;
            for (i = 0; i < myrects.length; i++)
            {
                elementA = myrects[ i ] as ObjMyRect;
                elementA.rect.x += elementA.vx;
                elementA.rect.y += elementA.vy;
 
                if (elementA.rect.x < 0 || elementA.rect.x > STAGE_W)
                    elementA.vx *= -1;
                if (elementA.rect.y < 0 || elementA.rect.y > STAGE_H)
                    elementA.vy *= -1;
 
                elementA.isHit = false;
            }
 
            for (i = 0; i < myrects.length; i++)
            {
                elementA = myrects[ i ] as ObjMyRect;
 
                for (j = 0; j < myrects.length; j++)
                {
                    if (j <= i)
                        continue;
 
                    elementB = myrects[ j ] as ObjMyRect;
 
               if (elementA.rect.x + elementA.rect.width < elementB.rect.x)
                {
                 }
               else if (elementA.rect.x > elementB.rect.x + elementB.rect.width)
                    {
                    }
              else if (elementA.rect.y + elementA.rect.height < elementB.rect.y)
                    {
                    }
              else if (elementA.rect.y > elementB.rect.y + elementB.rect.height)
                    {
                    }
              else
                    {
                        elementA.isHit = elementB.isHit = true;
                    }
                }
            }
 
            bmd.lock();
            bmd.colorTransform(bmd.rect, COLOR_TRANS);
            for (i = 0; i < myrects.length; i++)
            {
                elementA = myrects[ i ] as ObjMyRect;
 
                bmd.fillRect(new Rectangle(elementA.rect.x, elementA.rect.y,
                elementA.rect.width, elementA.rect.height),
                elementA.isHit ? 0x990000 : 0x666666);
            }
            bmd.unlock();
        }
 
        private function onEnterFrameC(event:Event):void
        {
            var i:int, j:int;
 
            var elementA:ObjSprite;
            var elementB:ObjSprite;
            for (i = 0; i < sprites.length; i++)
            {
                elementA = sprites[ i ] as ObjSprite;
                elementA.x += elementA.vx;
                elementA.y += elementA.vy;
 
                if (elementA.x < 0 || elementA.x > STAGE_W)
                    elementA.vx *= -1;
                if (elementA.y < 0 || elementA.y > STAGE_H)
                    elementA.vy *= -1;
 
                elementA.isHit = false;
            }
 
            for (i = 0; i < sprites.length; i++)
            {
                elementA = sprites[ i ] as ObjSprite;
 
                for (j = 0; j < sprites.length; j++)
                {
                    if (j <= i)
                        continue;
 
                    elementB = sprites[ j ] as ObjSprite;
 
                    if (elementA.hitTestObject(elementB))
                    {
                        elementA.isHit = elementB.isHit = true;
                    }
                }
            }
        }
    }
}
 
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Rectangle;
 
class ObjRectangle
{
    public var isHit:Boolean;
    public var rect:Rectangle;
    public var vx:Number;
    public var vy:Number;
}
 
class ObjMyRect
{
    public var isHit:Boolean;
    public var rect:MyRect;
    public var vx:Number;
    public var vy:Number;
}
 
class ObjSprite extends Sprite
{
 
    public function ObjSprite()
    {
        hitGr = new Shape();
        hitGr.graphics.beginFill(0x990000);
        hitGr.graphics.drawRect(0, 0, 10, 10);
        addChild(hitGr);
 
        defGr = new Shape();
        defGr.graphics.beginFill(0x666666);
        defGr.graphics.drawRect(0, 0, 10, 10);
        addChild(defGr);
    }
 
    public var defGr:Shape;
    public var hitGr:Shape;
 
    //--------------------------------------
    // isHit
    //--------------------------------------
 
    private var _isHit:Boolean;
 
    public function get isHit():Boolean
    {
        return _isHit;
    }
 
    public function set isHit(value:Boolean):void
    {
        _isHit = value;
 
        hitGr.visible = _isHit;
        defGr.visible = !_isHit;
    }
    public var vx:Number;
    public var vy:Number;
}
 
class MyRect
{
    public function MyRect(x:Number, y:Number, width:Number, height:Number)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
 
    public var height:Number;
    public var width:Number;
    public var x:Number;
    public var y:Number;
}




通过上面运行,400个高速运动粒子进行碰撞检测,有碰撞的显示为红色,在这同样的需求情况下,Myrect方式实现的结果帧频保持在60帧,而且CPU占用极低。Sprite的hit方式确实惨不忍睹。:{

OK,让我们看看他这个Myrect有啥特殊。看看代码或许你发现没有啥特别,就是按Rectangle方式定义了宽高及XY点而已。

重新定义并不是高效的原因,真正起作用的是这段代码:

if (elementA.rect.x + elementA.rect.width < elementB.rect.x)
{
}
 else if (elementA.rect.x > elementB.rect.x + elementB.rect.width)
 {
}
else if (elementA.rect.y + elementA.rect.height < elementB.rect.y)
 {
}
 else if (elementA.rect.y > elementB.rect.y + elementB.rect.height)
{
}
 else
 {
          elementA.isHit = elementB.isHit = true;
}


看来似乎老套传统的坐标判断一点也不输于现成的方法:)

既然如此,我们把方法onEnterFrameA中Rectangle的intersects方法,替换为上面onEnterFrameB中的这段代码,再次比较一下两者的效率及CPU占用呢?基于这个想法,我做了下面的这个测试,请看演示:

ok,通过上述测试,或许我们可以作出这样一个结论:
在采用Rectangle进行碰撞检测时,少量的碰撞判断,使用intersects方法相当的简单,不过在量多的日子里。。。或许采用老式的加减判断,更加高效。这个故事告诉我们,看似又老又笨的方法或许有时候又高效又好用:}
PS:不过很纳闷,intersects方法的核心难道不是坐标及宽高来判断的?


原文链接:http://www.fans8.com/?p=490