“AS3.0高级动画编程”学习:第一章高级碰撞检测

AdvancED ActionScript 3.0 Animation 是Keith Peters大师继"Make Things Move"之后的又一力作,网上已经有中文翻译版本了,打算下一阶段开始啃这本书。

今天开始学习高级碰撞检测,所用到的预备知识:

原作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 

1、BitmapData的透明与不透明区别

位图数据(BitmapData)有二种模式,一种支持透明(即每个像素的值采用AARRGGBB这种32位颜色表示);另一种不支持透明度(即传统的RRGGBB这种24位颜色表示,简单点讲就是alpha分量默认为FF,且不能修改),下面这个示例说明了区别:

 1 package {
 2     import flash.display.Bitmap;
 3     import flash.display.BitmapData;
 4     import flash.display.Sprite;
 5     import flash.display.StageAlign;
 6     import flash.display.StageScaleMode;
 7     import flash.geom.Rectangle;
 8      
 9     [SWF(height="400",width="300")]
10     public class BitmapCompare extends Sprite {
11         public function BitmapCompare() {
12             stage.align=StageAlign.TOP_LEFT;
13             stage.scaleMode=StageScaleMode.NO_SCALE;
14              
15             //随机在舞台上划一些线条
16             graphics.lineStyle(0);
17             for (var i:int=0; i<100; i++) {
18                 graphics.lineTo(Math.random()*300,Math.random()*400);
19             }
20              
21             //创建一个不透明的位图
22             var bmpd1:BitmapData=new BitmapData(300,200,false,0xffff99);
23             bmpd1.fillRect(new Rectangle(100,50,100,100),0xff0000);//注意:因为不透明的,所以颜色是24位的,没有alpha分量
24             var bmp1:Bitmap=new Bitmap(bmpd1);
25             addChild(bmp1);
26              
27             //创建一个支持透明的位图
28             var bmpd2:BitmapData=new BitmapData(300,200,true,0x80ffff99);//注:默认为50%透明的ff9颜色
29             bmpd2.fillRect(new Rectangle(100,50,100,100),0x80ff0000);//注:此处为32位颜色
30             var bmp2:Bitmap=new Bitmap(bmpd2);
31             bmp2.y=200;
32             addChild(bmp2);         
33         }
34     }
35 }
View Code

可以看到,上半部分的位图因为不支持透明,所以将背后的线条全部挡住了。

2、五角星的画法

先来看一个beginFill方法的神奇之处

1 graphics.lineStyle(0);
2 graphics.beginFill(0xffff99);
3 graphics.moveTo(10,10);
4 //注意下面只划了二条边
5 graphics.lineTo(10,100);
6 graphics.lineTo(100,100);
7 //graphics.lineTo(10,10); //注:正是因为上面的graphics.beginFill(0xffff99);所以这条线flash会为我们自动补齐
View Code

注意:虽然只画了二条线,但由于应用了begeinFill方法,flash自动生成了第三条线,形成了一个封闭的三角形.

回到正题,将一个圆周等分为10份,然后交替用不同的半径值结合三角函数,就能画出一个五角星

 1 var angleBase:Number=Math.PI*2/10;
 2 var radius:uint=100;
 3 var r2:uint;
 4 var i:uint;
 5 var px,py:Number;
 6  
 7 var starline:Sprite = new Sprite();
 8 starline.graphics.lineStyle(0);
 9 for (i=0; i<10; i++) {
10     r2=radius;
11     if (i%2==0) {
12         r2=radius/2;
13     }
14     starline.graphics.moveTo(0,0);
15     px=r2*Math.cos(angleBase*i);
16     py=r2*Math.sin(angleBase*i);
17     starline.graphics.lineTo(px,py);
18 }
19  
20 addChild(starline);
21 starline.x=stage.stageWidth/2;
22 starline.y=stage.stageHeight/2;
23  
24 var star:Sprite = new Sprite();
25 star.graphics.lineStyle(1,0xff0000);
26 star.graphics.beginFill(0xffff99);
27 star.graphics.moveTo(radius/2,0);
28 for (i=0; i<10; i++) {
29     r2=radius;
30     if (i%2==0) {
31         r2=radius/2;
32     }
33     px=r2*Math.cos(angleBase*i);
34     py=r2*Math.sin(angleBase*i);    
35     star.graphics.lineTo(px,py);
36      
37 }
38 //star.graphics.lineTo(radius/2,0);
39 addChild(star);
40 star.x=stage.stageWidth/2;
41 star.y=stage.stageHeight/2;
42 star.alpha = 0.5;
View Code

当然,封装成一个单独的类会更好,下面是Star.as的完整代码,以后会经常用到这个类

3、矩阵的运用(将上面的五角星转化为BitmapData)

可能有人注意到了,上面的五角星图形,其注册中心点是五角星正中心,所以直接用bitmapData的draw把它画出来,将只能显示一部分:

 1 var bmd1:BitmapData = new BitmapData(100,100,false,0xffefefef);
 2 bmd1.draw(star1);
 3 var bmp1:Bitmap = new Bitmap(bmd1);
 4 addChild(bmp1);
 5 bmp1.x = bmp1.y = 10;
 6  
 7 var bmd2:BitmapData = new BitmapData(100,100,false,0xffefefef);
 8 var m:Matrix = new Matrix();
 9 trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 0 0
10 m.tx = 50;
11 m.ty = 50;
12 trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 50 50
13 bmd2.draw(star1,m);
14 //等效于
15 //bmd2.draw(star1,new Matrix(1,0,0,1,50,50))
16 var bmp2:Bitmap = new Bitmap(bmd2);
17 addChild(bmp2);
18 bmp2.x = bmp1.x + 110;
19 bmp2.y = bmp1.y;
View Code

如上,为了能完整的用位图"画"出五星,需要将星星向左、向下移动一定的位置,即前面提到的矩阵变换

ok,下面才是真正的开始,先来看下位图之间的碰撞检测:

 1 package {
 2     import flash.display.Bitmap;
 3     import flash.display.BitmapData;
 4     import flash.display.Sprite;
 5     import flash.display.StageAlign;
 6     import flash.display.StageScaleMode;
 7     import flash.events.MouseEvent;
 8     import flash.filters.GlowFilter;
 9     import flash.geom.Matrix;
10     import flash.geom.Point;
11     public class BitmapCollision1 extends Sprite {
12          
13         private var bmpd1:BitmapData;
14         private var bmp1:Bitmap;
15         private var bmpd2:BitmapData;
16         private var bmp2:Bitmap;
17          
18         public function BitmapCollision1() {
19             stage.align=StageAlign.TOP_LEFT;
20             stage.scaleMode=StageScaleMode.NO_SCALE;
21             var matrix:Matrix = new Matrix();
22             var radius:uint = 50;
23             matrix.tx = radius;
24             matrix.ty = radius;         
25             var star:Star=new Star(radius);
26              
27             bmpd1=new BitmapData(100,100,true,0);           
28             bmpd1.draw(star,matrix);
29             bmp1=new Bitmap(bmpd1);
30             bmp1.x=200;
31             bmp1.y=200;
32             addChild(bmp1);
33              
34             bmpd2=new BitmapData(100,100,true,0);           
35             bmpd2.draw(star,matrix);
36             bmp2=new Bitmap(bmpd2);
37             addChild(bmp2);
38              
39             stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMoving);
40         }
41          
42         private function onMouseMoving(event:MouseEvent):void {
43              
44             bmp2.x=mouseX-50;
45             bmp2.y=mouseY-50;
46              
47             if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),255,bmpd2,new Point(bmp2.x,bmp2.y),255)) {
48                 bmp1.filters=[new GlowFilter];
49                 bmp2.filters=[new GlowFilter];
50             } else {
51                 bmp1.filters=[];
52                 bmp2.filters=[];
53             }
54         }
55     }
56 }
View Code

这里我们用二个BitmapData“画”出二个星星,再进一步得到二个Bitmap,并加入舞台上。然后调用BitmapData的hitTest方法,检测二个星星之间的碰撞。

注意这里的:if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),255,bmpd2,new Point(bmp2.x,bmp2.y),255)) {

对于这二个星星而言,画到的地方便是完整不透明,没画到的空白地方即是完整透明(不存在类似渐变中的过渡情况),这里的二个255,代表检测时的alpha分量依据,通俗点讲:即只有完全不透明的地方碰到了,才返回true。

为了对histTest方法中“alpha分量参数”有更好的理解,上面的示例可改进为下面这样:

 1 package {
 2     import flash.display.Bitmap;
 3     import flash.display.BitmapData;
 4     import flash.display.GradientType;
 5     import flash.display.Sprite;
 6     import flash.display.StageAlign;
 7     import flash.display.StageScaleMode;
 8     import flash.events.MouseEvent;
 9     import flash.filters.GlowFilter;
10     import flash.geom.Matrix;
11     import flash.geom.Point;
12     import fl.events.SliderEvent;
13      
14     public class BitmapCollision2 extends Sprite {
15         private var bmpd1:BitmapData;
16         private var bmp1:Bitmap;
17         private var bmpd2:BitmapData;
18         private var bmp2:Bitmap;
19         public function BitmapCollision2() {
20             stage.align=StageAlign.TOP_LEFT;
21             stage.scaleMode=StageScaleMode.NO_SCALE;
22              
23             var star:Star=new Star(50);
24              
25             var matrix:Matrix = new Matrix();
26             matrix.createGradientBox(100, 100, 0, -50, -50);
27              
28             var circle:Sprite = new Sprite();
29             //画一个渐变填充的圆
30             circle.graphics.beginGradientFill(GradientType.RADIAL,[0, 0],[1, 0],[0, 255],matrix);
31             circle.graphics.drawCircle(0, 0, 50);
32             circle.graphics.endFill();
33              
34             bmpd1=new BitmapData(100,100,true,0);
35             bmpd1.draw(star, new Matrix(1, 0, 0, 1, 50, 50));
36             bmp1=new Bitmap(bmpd1);
37             bmp1.x=stage.stageWidth/2 - bmp1.width/2;
38             bmp1.y=stage.stageHeight/2 - bmp1.height/2;
39             addChild(bmp1);
40              
41             bmpd2=new BitmapData(100,100,true,0);
42             bmpd2.draw(circle, new Matrix(1, 0, 0, 1, 50, 50));
43             bmp2=new Bitmap(bmpd2);
44             addChild(bmp2);
45             stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving);
46              
47             //slider1,slider2是舞台上用设计工具拖出来的二个滑动控件
48             slider2.addEventListener(SliderEvent.THUMB_DRAG,slider2Change);
49             slider1.addEventListener(SliderEvent.THUMB_DRAG,slider1Change);
50              
51         }
52          
53         private function slider1Change(e:SliderEvent):void
54         {
55             txt1.text = e.value.toString();
56         }
57          
58         private function slider2Change(e:SliderEvent):void
59         {
60             txt2.text = e.value.toString();
61         }
62          
63         private function onMouseMoving(event:MouseEvent):void {
64              
65              
66             if (mouseY>320){return;}//防止小球拖到太下面,挡住了滑块
67              
68             bmp2.x=mouseX-50;
69             bmp2.y=mouseY-50;
70              
71             if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),slider1.value,bmpd2,new Point(bmp2.x,bmp2.y),slider2.value)) {
72                 bmp1.filters = [new GlowFilter()];
73                 bmp2.filters = [new GlowFilter()];
74             } else {
75                 bmp1.filters=[];
76                 bmp2.filters=[];
77             }
78         }
79     }
80 }
View Code

调整第二个滑块,然后再测试碰撞效果,体会alpha参数在其中的作用,值得一提的是:因为星星没有类似渐变的填充,要么透明,要么不透明,所以第一个滑块在1-255之间的值,对碰撞结果没有影响,除非设置为0才会有变化.(设置为0时,相当于把星星所对应的矩形边界当做整体在检测)

通常在实际应用中,可能舞台上更多的是movieClip或sprite,而不是bitmap对象,如果您已经看懂了上面的二个示例,相信“对于MovieClip/Sprite之间的精确碰撞检测”也一定有思路了:构造对应的BitmapData,然后将movieclip或sprite,draw到bitmapData中,然后参考上面的代码处理。

不过,这里有一个小技巧:因为我们最终需要的可能只是碰撞检测的结果,而并不是真的想要在舞台上显示Bitmap,所以在实际操作中,bitmapData甚至都不用加入到显示列表

 1 package {
 2     import flash.display.BitmapData;
 3     import flash.display.Sprite;
 4     import flash.display.StageAlign;
 5     import flash.display.StageScaleMode;
 6     import flash.events.MouseEvent;
 7     import flash.filters.GlowFilter;
 8     import flash.geom.Matrix;
 9     import flash.geom.Point;
10     public class BitmapCollision3 extends Sprite {
11         private var bmpd1:BitmapData;
12         private var bmpd2:BitmapData;
13         private var star1:Star;
14         private var star2:Star;
15         public function BitmapCollision3() {
16             stage.align=StageAlign.TOP_LEFT;
17             stage.scaleMode=StageScaleMode.NO_SCALE;            
18             star1=new Star(50);
19             addChild(star1);
20             star2=new Star(50);
21             star2.x=200;
22             star2.y=200;
23             addChild(star2);            
24             bmpd1=new BitmapData(stage.stageWidth,stage.stageHeight,true,0);
25             bmpd2=bmpd1.clone();
26             stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving);
27             //注:这里bmpd1,bmpd2都没被转成bitmap,更没有加入到舞台中.
28         }
29         private function onMouseMoving(event:MouseEvent):void {
30              
31             star1.x=mouseX;
32             star1.y=mouseY;
33             //先清空bitmapData中的数据,准备一个完全透明的黑"底板"。
34             bmpd1.fillRect(bmpd1.rect, 0);
35             bmpd2.fillRect(bmpd2.rect, 0);
36             //再把要检测的(movieclip或sprite)对象,画到里面.
37             bmpd1.draw(star1, new Matrix(1, 0, 0, 1, star1.x, star1.y));
38             bmpd2.draw(star2, new Matrix(1, 0, 0, 1, star2.x, star2.y));
39             //碰撞检测
40             //注意:因为bmpd1,bmpd2都没被加入到舞台上,所以默认都在同样的0坐标位置,因此下面的坐标,直接用默认的Point对象实例即可.
41             if (bmpd1.hitTest(new Point(), 255, bmpd2, new Point(), 255)) {
42                 star1.filters = [new GlowFilter()];
43                 star2.filters = [new GlowFilter()];
44             } else {
45                 star1.filters=[];
46                 star2.filters=[];
47             }
48         }
49     }
50 }
View Code

最终的运行效果,跟之前的示例没有区别,就不重复贴出了

继续,考虑更复杂的大量对象的碰撞问题,前一阵我们刚学习过“Flash/Flex学习笔记(41):碰撞检测”,但是没有考虑到大量对象时的性能问题。

计算一下:10个物体处理碰撞时,每个物体都要与其它物体做碰撞检测,最终需要的处理次数为 10*9/2 = 45次(数学中的组合问题) ;如果100个物体,就要处理 100*99/2 = 4950次!

这么大的计算量,每一帧都要处理一遍,AS3.0性能再强也是撑不住的!

但实际上,我们静下心来想想:大量对象随机分布在舞台上,实际上每个对象只有可能与自身附近的对象发生碰撞,对于那边离自己很远,甚至八杆子打不着的对象,根本没必要跟他们做碰撞检测计算。所以,其实真正需要的计算量应该可以减少很多!

网格碰撞检测

如上图,首先可以先将舞台看成一个网格(每个单元格的大小,至少要大于舞台上尺寸最大的对象,即至少要能容纳下块头最大的一个对象)

这样的话,每个对象都会被划分到对应的格子里,而且只有可能与“身处在同一个格子里的其它对象”以及“相临格子里的其它对象”发生碰撞。

我们用遍历的思路(从左向右,从上到下)来分析一下:

先从第一行第一列开始(如上图中的第一排第一个示例),黑色的表示当前要考虑的单元格,很明显:在行1列1的位置,可能与之发生碰撞只有相临的浅灰色单元格,其它白色单元格是不可能与它发生碰撞的。

继续向右走,到了上图中第一排第二个小图的位置,这里能够与它发生单元格的只有其它4个浅灰色单元格(注:左侧的单元格在前面的检测中已经处理过了,所以这里就可以无视左侧相临的单元格!)

同理,继续向右,直到第一行全部遍历完成。

再继续向下,考查第二行:

因为第一行已经全部处理过了,所以在考查第二行时,可以继续无视上面的单元格,同时再忽略左侧的单元格(道理与第一行相同)

如此这般... 直到最后一行最后一列全部考查完毕。

总结:从刚才的分析可以知道,不管在哪一行哪一列,最多只需要关注(包含自身的)5个单元格--自身、右侧、下侧、左下、右下。

为了方便起见,我们还是用小球来做为基本对象,下面是Ball.cs的代码(相对以前的写法而言,更加OO了)

 1 package {
 2     import flash.display.Sprite;
 3     public class Ball extends Sprite {
 4         private var _color:uint;
 5         private var _radius:Number;
 6         private var _vx:Number=0;
 7         private var _vy:Number=0;
 8         public function Ball(radius:Number, color:uint = 0xffffff) {
 9             _radius=radius;
10             _color=color;
11             draw();
12         }
13         private function draw():void {
14  
15             graphics.clear();
16             graphics.lineStyle(0);
17             graphics.beginFill(_color, 1);
18             graphics.drawCircle(0, 0, _radius);
19             graphics.endFill();
20             graphics.drawCircle(0, 0, 1);//在中心画一个点
21         }
22         public function update():void {
23             x+=_vx;
24             y+=_vy;
25         }
26         public function set color(value:uint):void {
27             _color=value;
28             draw();
29         }
30         public function get color():uint {
31             return _color;
32         }
33         public function set radius(value:Number):void {
34             _radius=value;
35             draw();
36         }
37         public function get radius():Number {
38             return _radius;
39         }
40         public function set vx(value:Number):void {
41             _vx=value;
42         }
43         public function get vx():Number {
44             return _vx;
45         }
46         public function set vy(value:Number):void {
47             _vy=value;
48         }
49         public function get vy():Number {
50             return _vy;
51         }
52     }
53 }
View Code

ok,下面是完整的代码,请大家在仔细阅读/调试后,重点比较一下100个小球处理完毕所用的总次数。

  1 package {
  2     import flash.display.Sprite;
  3     import flash.display.StageAlign;
  4     import flash.display.StageScaleMode;
  5     import flash.events.MouseEvent;
  6     import flash.text.TextField;
  7      
  8     public class GridCollision extends Sprite {
  9          
 10         private const GRID_SIZE:Number=30;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
 11         private const RADIUS:Number=15;//小球的半径
 12         private var _balls:Array;
 13         private var _grid:Array;
 14         private var _numBalls:int=100;//小球数量
 15         private var _numChecks:int=0;//检测次数
 16         private var _txt:TextField = new TextField();
 17  
 18         public function GridCollision() {
 19             stage.align=StageAlign.TOP_LEFT;
 20             stage.scaleMode=StageScaleMode.NO_SCALE;
 21             makeBalls();//创建一堆小球
 22             makeGrid();//
 23             drawGrid();
 24             assignBallsToGrid();
 25             checkGrid();
 26              
 27             //显示计数器
 28             trace(_numChecks);
 29             addChild(_txt);
 30             _txt.background = true;
 31             _txt.backgroundColor=0xffff99;
 32             _txt.height = 20;
 33             _txt.width = 30;
 34             _txt.alpha = 0.7;
 35              
 36             stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownClick);
 37         }
 38  
 39         private function mouseDownClick(e:MouseEvent):void{
 40             for (var i:int=0; i<_numBalls; i++) {
 41                 var ball:Ball=_balls[i];
 42                 ball.x=Math.random()*stage.stageWidth;
 43                 ball.y=Math.random()*stage.stageHeight; 
 44                 ball.color = 0xffffff;
 45             }
 46             _numChecks=0;
 47             makeGrid();//
 48             drawGrid();
 49             assignBallsToGrid();
 50             checkGrid();
 51              
 52         }
 53  
 54         //创建_numBalls个小球实例,并随机摆放到舞台上
 55         private function makeBalls():void {
 56             _balls=new Array  ;
 57             for (var i:int=0; i<_numBalls; i++) {
 58                 var ball:Ball=new Ball(RADIUS);
 59                 ball.x=Math.random()*stage.stageWidth;
 60                 ball.y=Math.random()*stage.stageHeight;
 61                 addChild(ball);
 62                 _balls.push(ball);
 63             }
 64         }
 65  
 66         private function makeGrid():void {
 67             _grid=new Array ;
 68  
 69             for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
 70                 _grid[i]=new Array  ;
 71  
 72                 for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
 73                     _grid[i][j]=new Array ;//每个单元格对应一个数组(用来存放该单元格中的小球)
 74                 }
 75             }
 76         }
 77  
 78         private function drawGrid():void {
 79             // 画出行列线
 80             graphics.lineStyle(0,.5);
 81             for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
 82                 graphics.moveTo(i,0);
 83                 graphics.lineTo(i,stage.stageHeight);
 84             }
 85             for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
 86                 graphics.moveTo(0,i);
 87                 graphics.lineTo(stage.stageWidth,i);
 88             }
 89         }
 90  
 91  
 92         private function assignBallsToGrid():void {
 93             for (var i:int=0; i<_numBalls; i++) {
 94                 // 球的位置除以格子大小,得到该球所在网格的行列数
 95                 var ball:Ball=_balls[i] as Ball;
 96                 var xpos:int=Math.floor(ball.x/GRID_SIZE);
 97                 var ypos:int=Math.floor(ball.y/GRID_SIZE);
 98                 _grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
 99             }
100         }
101  
102         private function checkGrid():void {
103             for (var i:int=0; i<_grid.length; i++) {
104                 for (var j:int=0; j<_grid[i].length; j++) {
105                      
106                     checkOneCell(i,j);//单元格cell_self自身的碰撞检测
107                     checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
108                     checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
109                     checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
110                     checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
111                 }
112             }
113         }
114      
115         //cellSelf与自身的检测
116         private function checkOneCell(x:int,y:int):void {
117             // 检测当前格子内所有的对象
118             var cell:Array=_grid[x][y] as Array;
119             for (var i:int=0; i<cell.length-1; i++) {
120                 var ballA:Ball=cell[i] as Ball;
121                 for (var j:int=i+1; j<cell.length; j++) {
122                     var ballB:Ball=cell[j] as Ball;
123                     checkCollision(ballA,ballB);
124                 }
125             }
126         }
127  
128         //cellSelf与其它单元格的检测
129         private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
130             //确保要检测的格子存在
131             if (x2<0) {
132                 return;
133             }
134             if (x2>=_grid.length) {
135                 return;
136             }
137             if (y2>=_grid[x2].length) {
138                 return;
139             }
140             var cell0:Array=_grid[x1][y1] as Array;
141             var cell1:Array=_grid[x2][y2] as Array;
142              
143             // 检测当前格子和邻接格子内所有的对象
144             for (var i:int=0; i<cell0.length; i++) {
145                 var ballA:Ball=cell0[i] as Ball;
146                 for (var j:int=0; j<cell1.length; j++) {
147                     var ballB:Ball=cell1[j] as Ball;
148                     checkCollision(ballA,ballB);
149                 }
150             }
151         }
152  
153         private function checkCollision(ballA:Ball,ballB:Ball):void {
154             // 判断距离的碰撞检测
155             _numChecks++;//计数器累加
156             _txt.text = _numChecks.toString();
157             var dx:Number=ballB.x-ballA.x;
158             var dy:Number=ballB.y-ballA.y;
159             var dist:Number=Math.sqrt(dx*dx+dy*dy);
160             if (dist<ballA.radius+ballB.radius) {
161                 //碰撞的小球变红色
162                 ballA.color=0xff0000;
163                 ballB.color=0xff0000;
164             }
165  
166         }
167     }
168 }
View Code


在线演示

上面的示例中,左上角的textField显示的是处理总次数(可以看到,大概在100-150次之间,这比优化前的理论值100*99/2 = 4950减少了90%都不止!)

需要指出的是:计算次数具体能减少多少,取决于网络(单元格)大小、flash舞台(场景)大小、对象个数、对象的大小;改变其中一个或几个参数,上面的测试结果都将改变。

再来认真的考虑一下性能问题,虽然用网格算法有效减少了计算次数,但是却多出了创建网格,把对象分配进单元格,遍历网络等操作,这些处理也同样要占用CPU资源,那么到底这些多余的操作影响多大?(或者也可能理解为在什么情况下,网络算法相对传统的(基于每两个对象之间的)两两检测更适用)

  1 package {
  2     import flash.display.Sprite;
  3     import flash.display.StageAlign;
  4     import flash.display.StageScaleMode;
  5     import flash.events.MouseEvent;
  6     import flash.text.TextField;
  7     import fl.controls.Slider;
  8     import flash.utils.getTimer;
  9     import fl.events.SliderEvent;
 10  
 11     public class GridCollision extends Sprite {
 12  
 13         private const GRID_SIZE:Number=20;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
 14         private const RADIUS:Number=10;//小球的半径
 15         private var _balls:Array;
 16         private var _grid:Array;
 17         private var _numBalls:int=50;//小球数量
 18         private var _txtGrid:TextField = new TextField();
 19         private var _txtBasic:TextField = new TextField();
 20         private var _slider:Slider = new Slider();
 21  
 22         public function GridCollision() {
 23             stage.align=StageAlign.TOP_LEFT;
 24             stage.scaleMode=StageScaleMode.NO_SCALE;
 25  
 26             makeGrid();
 27             drawGrid();
 28  
 29             addChild(_slider);
 30             addChild(_txtGrid);
 31             addChild(_txtBasic);
 32  
 33             test();
 34  
 35             _slider.addEventListener(SliderEvent.THUMB_DRAG,sliderGrag);
 36             stage.addEventListener(MouseEvent.CLICK,stageClick);
 37  
 38         }
 39  
 40         private function test(isClear:Boolean=false):void {
 41             var i:int=0;
 42             if (isClear) {
 43                 for (i=numChildren-1; i>=0; i--) {
 44                     removeChild(getChildAt(i));
 45                 }
 46                 _balls.length=0;
 47             }
 48             _txtGrid.background=_txtBasic.background=true;
 49             _txtGrid.backgroundColor=_txtBasic.backgroundColor=0xffff99;
 50             _txtBasic.height=_txtGrid.height=20;
 51             _txtBasic.width=_txtGrid.width=135;
 52             _txtBasic.alpha=_txtGrid.alpha=0.9;
 53             _txtBasic.x=stage.stageWidth-_txtBasic.width;
 54             _slider.maximum=300;
 55             _slider.minimum=30;
 56             _slider.snapInterval=10;
 57             _slider.y=10;
 58             _slider.value=_numBalls;
 59             _slider.width=200;
 60             _slider.x=stage.stageWidth/2-_slider.width/2;
 61  
 62             makeBalls();//创建一堆小球
 63  
 64             var startTime:int;
 65             var elapsed:int;
 66  
 67             startTime=getTimer();
 68             for (i=0; i<10; i++) {
 69                 makeGrid();
 70                 assignBallsToGrid();
 71                 checkGrid();
 72             }
 73             elapsed=getTimer()-startTime;
 74             trace("网格检测:",elapsed);
 75             _txtGrid.text=_numBalls+"个球网络检测:"+elapsed.toString();
 76  
 77             startTime=getTimer();
 78             for (i=0; i<10; i++) {
 79                 basicCheck();
 80             }
 81             elapsed=getTimer()-startTime;
 82             trace("两两检测:",elapsed);
 83             _txtBasic.text=_numBalls+"个球两两检测:"+elapsed.toString();
 84  
 85             if (isClear) {
 86                 addChild(_txtBasic);
 87                 addChild(_txtGrid);
 88                 addChild(_slider);
 89             }
 90         }
 91  
 92         private function sliderGrag(e:SliderEvent):void {
 93             _numBalls=e.value;
 94             trace("sliderGrag");
 95         }
 96  
 97         private function stageClick(e:MouseEvent):void {
 98             trace("stageClick");
 99             test(true);
100         }
101  
102         //创建_numBalls个小球实例,并随机摆放到舞台上
103         private function makeBalls():void {
104             _balls=new Array  ;
105             for (var i:int=0; i<_numBalls; i++) {
106                 var ball:Ball=new Ball(RADIUS);
107                 ball.x=Math.random()*stage.stageWidth;
108                 ball.y=Math.random()*stage.stageHeight;
109                 addChild(ball);
110                 ball.alpha=0.5;
111                 _balls.push(ball);
112             }
113         }
114  
115         private function makeGrid():void {
116             _grid=new Array  ;
117             for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
118                 _grid[i]=new Array  ;
119                 for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
120                     _grid[i][j]=new Array  ;//每个单元格对应一个数组(用来存放该单元格中的小球)
121                 }
122             }
123         }
124  
125         private function drawGrid():void {
126             // 画出行列线
127             graphics.lineStyle(0,0x999999);
128             for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
129                 graphics.moveTo(i,0);
130                 graphics.lineTo(i,stage.stageHeight);
131             }
132             for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
133                 graphics.moveTo(0,i);
134                 graphics.lineTo(stage.stageWidth,i);
135             }
136         }
137  
138  
139         private function assignBallsToGrid():void {
140             for (var i:int=0; i<_numBalls; i++) {
141                 // 球的位置除以格子大小,得到该球所在网格的行列数
142                 var ball:Ball=_balls[i] as Ball;
143                 var xpos:int=Math.floor(ball.x/GRID_SIZE);
144                 var ypos:int=Math.floor(ball.y/GRID_SIZE);
145                 _grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
146             }
147         }
148  
149         private function checkGrid():void {
150             for (var i:int=0; i<_grid.length; i++) {
151                 for (var j:int=0; j<_grid[i].length; j++) {
152                     checkOneCell(i,j);//单元格cell_self自身的碰撞检测
153                     checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
154                     checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
155                     checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
156                     checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
157                 }
158             }
159         }
160  
161         //cellSelf与自身的检测
162         private function checkOneCell(x:int,y:int):void {
163             // 检测当前格子内所有的对象
164             var cell:Array=_grid[x][y] as Array;
165             for (var i:int=0; i<cell.length-1; i++) {
166                 var ballA:Ball=cell[i] as Ball;
167                 for (var j:int=i+1; j<cell.length; j++) {
168                     var ballB:Ball=cell[j] as Ball;
169                     checkCollision(ballA,ballB);
170                 }
171             }
172         }
173  
174         //cellSelf与其它单元格的检测
175         private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
176             //确保要检测的格子存在
177             if (x2<0) {
178                 return;
179             }
180             if (x2>=_grid.length) {
181                 return;
182             }
183             if (y2>=_grid[x2].length) {
184                 return;
185             }
186             var cell0:Array=_grid[x1][y1] as Array;
187             var cell1:Array=_grid[x2][y2] as Array;
188  
189             // 检测当前格子和邻接格子内所有的对象
190             for (var i:int=0; i<cell0.length; i++) {
191                 var ballA:Ball=cell0[i] as Ball;
192                 for (var j:int=0; j<cell1.length; j++) {
193                     var ballB:Ball=cell1[j] as Ball;
194                     checkCollision(ballA,ballB);
195                 }
196             }
197         }
198  
199         private function checkCollision(ballA:Ball,ballB:Ball):void {
200             // 判断距离的碰撞检测
201             var dx:Number=ballB.x-ballA.x;
202             var dy:Number=ballB.y-ballA.y;
203             var dist:Number=Math.sqrt(dx*dx+dy*dy);
204             if (dist<ballA.radius+ballB.radius) {
205                 //碰撞的小球变红色
206                 ballA.color=0xff0000;
207                 ballB.color=0xff0000;
208             }
209         }
210  
211         //(最原始的)两两检测
212         private function basicCheck():void {
213             for (var i: int=0; i < _balls.length - 1; i++) {
214                 var ballA:Ball=_balls[i] as Ball;
215                 for (var j: int=i+1; j < _balls.length; j++) {
216                     var ballB:Ball=_balls[j] as Ball;
217                     checkCollision(ballA, ballB);
218                 }
219             }
220         }
221     }
222 }
View Code


在线演示

上面这个示例,我们把"网格检测算法"与传统的"两两检测算法"每个跑10次,然后输出所用的时间来进行比较,拖动滑块可以调整小球的数量,点击舞台可以重新计算。

反复比较可以发现,在小球数量接近100时,二种算法性能已经相差无已,在小球数量大于100的前提下,小球数量越多,网格算法性能越有优势。在对象数量较少的情况下,传统的两两检测算法反而更快!

所以网格算法仅适用于大量对象的碰撞检测!

如果考虑到代码重用,可以把这种算法封装一下:

  1 package {
  2     import flash.display.DisplayObject;
  3     import flash.display.Graphics;
  4     import flash.events.EventDispatcher;
  5      
  6     public class CollisionGrid extends EventDispatcher {
  7          
  8         private var _checks:Vector.<DisplayObject>;//用于保存需要碰撞检测的对象(注:Vector.<T>相当于c#中的泛型数组)
  9         private var _grid:Vector.<Vector.<DisplayObject>>;//网格(注:这里用“一维数组套一维数组”的方法替代了原来的二维数组)
 10         private var _gridSize:Number;
 11         private var _height:Number;
 12         private var _numCells:int;
 13         private var _numCols:int;
 14         private var _numRows:int;
 15         private var _Number;
 16          
 17         public function CollisionGrid(Number, height:Number, gridSize:Number) {
 18             _width=width;
 19             _height=height;
 20             _gridSize=gridSize;
 21              
 22             _numCols=Math.ceil(_width/_gridSize);//计算总列数            
 23             _numRows=Math.ceil(_height/_gridSize);//计算总行数
 24             _numCells=_numCols*_numRows;//单元格总数
 25         }
 26          
 27         //画格子
 28         public function drawGrid(graphics:Graphics):void {
 29             graphics.lineStyle(0, .5);
 30             for (var i:int = 0; i <= _width; i += _gridSize) {
 31                 graphics.moveTo(i, 0);
 32                 graphics.lineTo(i, _height);
 33             }
 34             for (i = 0; i <= _height; i += _gridSize) {
 35                 graphics.moveTo(0, i);
 36                 graphics.lineTo(_width, i);
 37             }
 38         }
 39          
 40         //将需要检测的对象(泛型)数组objects分配到网络
 41         public function assign(objects:Vector.<DisplayObject>):void {
 42             var numObjects:int=objects.length;
 43             _grid=new Vector.<Vector.<DisplayObject>>(_numCells);
 44             _checks = new Vector.<DisplayObject>();
 45             for (var i:int = 0; i < numObjects; i++) {
 46                 var obj:DisplayObject=objects[i];
 47                 //注意:这里用“Grid.[索引]”(定位)的方式,替换了原来的“Grid.[列][行]”(单元格的定位)方式--回想一下bitmap位图中的像素索引就更容易理解了
 48                 var index:int=Math.floor(obj.y/_gridSize)*_numCols+Math.floor(obj.x/_gridSize);
 49                 //“单元格”--延时实例化"
 50                 if (_grid[index]==null) {
 51                     _grid[index]=new Vector.<DisplayObject>  ;
 52                 }
 53                 //将对象推入"单元格"
 54                 _grid[index].push(obj);
 55             }
 56              
 57             //检测需要碰撞的对象,并保存到_checks数组
 58             checkGrid();
 59         }
 60          
 61         //"单元格"检测
 62         private function checkGrid():void {
 63             for (var i:int = 0; i < _numCols; i++) {
 64                 for (var j:int = 0; j < _numRows; j++) {
 65                     checkOneCell(i, j);
 66                     checkTwoCells(i, j, i + 1, j);
 67                     checkTwoCells(i, j, i - 1, j + 1);
 68                     checkTwoCells(i, j, i, j + 1);
 69                     checkTwoCells(i, j, i + 1, j + 1);
 70                 }
 71             }
 72         }
 73          
 74         //(自身)单个单元格的检测
 75         private function checkOneCell(x:int, y:int):void {
 76             var cell:Vector.<DisplayObject>=_grid[y*_numCols+x];
 77             if (cell==null) {
 78                 return;
 79             }
 80             var cellLength:int=cell.length;
 81              
 82             for (var i:int = 0; i < cellLength - 1; i++) {
 83                 var objA:DisplayObject=cell[i];
 84                 for (var j:int = i + 1; j < cellLength; j++) {
 85                     var objB:DisplayObject=cell[j];
 86                     _checks.push(objA, objB);
 87                 }
 88             }
 89         }
 90          
 91         //单元格(x1,y1)与单元格(x2,y2)的检测
 92         private function checkTwoCells(x1:int, y1:int, x2:int, y2:int):void {
 93             if (x2>=_numCols||x2<0||y2>=_numRows) {
 94                 return;
 95             }
 96             var cellA:Vector.<DisplayObject>=_grid[y1*_numCols+x1];
 97             var cellB:Vector.<DisplayObject>=_grid[y2*_numCols+x2];
 98             if (cellA==null||cellB==null) {
 99                 return;
100             }
101             var cellALength:int=cellA.length;
102             var cellBLength:int=cellB.length;
103             for (var i:int = 0; i < cellALength; i++) {
104                 var objA:DisplayObject=cellA[i];
105                 for (var j:int = 0; j < cellBLength; j++) {
106                     var objB:DisplayObject=cellB[j];
107                     _checks.push(objA, objB);
108                 }
109             }
110         }
111          
112         public function get checks():Vector.<DisplayObject> {
113             return _checks;
114         }
115     }
116 }
View Code

注:除了单纯的封装以外,上面的代码还有三个重要的优化措施

1.用Vector(泛型数组)代替了Array数组

2.用一维数组嵌套取代了原来的二维数组

3.延时实例化避免了创建无用的"单元格"

用封装并优化后的代码重新测试下:

 1 package {
 2     import flash.display.Sprite;
 3     import flash.display.StageAlign;
 4     import flash.display.StageScaleMode;
 5     import flash.utils.getTimer;
 6     import flash.display.DisplayObject;
 7     import flash.events.MouseEvent;
 8     import flash.text.TextField;
 9  
10     public class GridCollision2 extends Sprite {
11         private const GRID_SIZE:Number=20;
12         private const RADIUS:Number=10;
13         private var _balls:Vector.<DisplayObject>;//这里用Vector代替了Array
14         private var _grid:CollisionGrid;
15         private var _numBalls:int=50;
16         private var _text:TextField;
17  
18         public function GridCollision2() {
19             stage.align=StageAlign.TOP_LEFT;
20             stage.scaleMode=StageScaleMode.NO_SCALE;
21             _text = new TextField();
22             _text.background = true;
23             _text.backgroundColor = 0xffff99;
24             _text.width = 135;
25             _text.height = 20;
26             _text.alpha = 0.9;
27              
28              
29             _grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
30             _grid.drawGrid(graphics);
31  
32             makeBalls();
33             addChild(_text);
34             test();
35  
36             stage.addEventListener(MouseEvent.CLICK,stageClick);
37         }
38  
39         private function stageClick(e:MouseEvent):void {
40             test(true);
41         }
42  
43         private function test(isRestart:Boolean=false):void {
44             if (isRestart) {
45                 for (var i:int=0; i<_numBalls; i++) {
46                     var ball:Ball=_balls[i] as Ball;
47                     ball.x=Math.random()*stage.stageWidth;
48                     ball.y=Math.random()*stage.stageHeight;
49                     ball.color = 0xffffff;
50                 }
51             }
52  
53             var startTime:int;
54             var elapsed:int;
55             startTime=getTimer();
56             for (i=0; i<10; i++) {
57                 _grid.assign(_balls);//将所有需要检测的ball放入_grid.checks
58                 var numChecks:int=_grid.checks.length;
59                 for (var j:int=0; j<numChecks; j+=2) {
60                     checkCollision(_grid.checks[j] as Ball,_grid.checks[j+1] as Ball);
61                 }
62             }
63             elapsed=getTimer()-startTime;
64             trace("Elapsed:",elapsed);
65             _text.text = _numBalls + "个小球碰撞检测:" + elapsed.toString();
66         }
67          
68         //初始化小球实例
69         private function makeBalls():void {
70             _balls=new Vector.<DisplayObject>(_numBalls);
71             for (var i:int=0; i<_numBalls; i++) {
72                 var ball:Ball=new Ball(RADIUS);
73                 ball.x=Math.random()*stage.stageWidth;
74                 ball.y=Math.random()*stage.stageHeight;
75                 ball.alpha = 0.8;
76                 addChild(ball);
77                 _balls[i]=ball;
78             }
79         }
80  
81         //检测碰撞
82         private function checkCollision(ballA:Ball,ballB:Ball):void {
83             var dx:Number=ballB.x-ballA.x;
84             var dy:Number=ballB.y-ballA.y;
85             var dist:Number=Math.sqrt(dx*dx+dy*dy);
86             if (dist<ballA.radius+ballB.radius) {
87                 //(碰撞后的小球变红色)
88                 ballA.color=0xff0000;
89                 ballB.color=0xff0000;
90             }
91         }
92     }
93 }
View Code


在线演示

对比之前未封装的示例,可以发现:执行时间缩短了近一半!说明优化的效果还是很不错的。

静态的碰撞检测可能比较没劲,可以再结合以前学到的知识,让小球动起来。

 1 package {
 2     import flash.display.Sprite;
 3     import flash.display.StageAlign;
 4     import flash.display.StageScaleMode;
 5     import flash.display.DisplayObject;
 6     import flash.events.Event;
 7      
 8     public class GridCollision3 extends Sprite {
 9         private const GRID_SIZE:Number=20;
10         private const RADIUS:Number=10;
11         private var _balls:Vector.<DisplayObject>;
12         private var _grid:CollisionGrid;
13         private var _numBalls:int=100;
14         public function GridCollision3() {
15             stage.align=StageAlign.TOP_LEFT;
16             stage.scaleMode=StageScaleMode.NO_SCALE;
17             _grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
18             _grid.drawGrid(graphics);
19             makeBalls();
20             addEventListener(Event.ENTER_FRAME, onEnterFrame);
21         }
22         function onEnterFrame(event:Event):void {
23             updateBalls();
24             _grid.assign(_balls);
25             var numChecks:int=_grid.checks.length;
26             for (var j:int = 0; j < numChecks; j += 2) {
27                 checkCollision(_grid.checks[j] as Ball, _grid.checks[j + 1] as Ball);
28             }
29         }
30         private function makeBalls():void {
31             _balls=new Vector.<DisplayObject>(_numBalls);
32             for (var i:int = 0; i < _numBalls; i++) {
33                 var ball:Ball=new Ball(RADIUS);
34                 ball.x=Math.random()*stage.stageWidth;
35                 ball.y=Math.random()*stage.stageHeight;
36                 ball.vx=Math.random()*4-2;
37                 ball.vy=Math.random()*4-2;
38                 addChild(ball);
39                 _balls[i]=ball;
40             }
41         }
42         private function updateBalls():void {
43             for (var i:int = 0; i < _numBalls; i++) {
44                  
45                 var ball:Ball=_balls[i] as Ball;
46                 ball.update();
47                 if (ball.x<RADIUS) {
48                     ball.x=RADIUS;
49                     ball.vx*=-1;
50                 } else if (ball.x > stage.stageWidth - RADIUS) {
51                     ball.x=stage.stageWidth-RADIUS;
52                     ball.vx*=-1;
53                 }
54                 if (ball.y<RADIUS) {
55                     ball.y=RADIUS;
56                     ball.vy*=-1;
57                 } else if (ball.y > stage.stageHeight - RADIUS) {
58                     ball.y=stage.stageHeight-RADIUS;
59                     ball.vy*=-1;
60                 }
61                 ball.color=0xffffff;
62             }
63         }
64         private function checkCollision(ballA:Ball, ballB:Ball):void {
65             var dx:Number=ballB.x-ballA.x;
66             var dy:Number=ballB.y-ballA.y;
67             var dist:Number=Math.sqrt(dx*dx+dy*dy);
68             if (dist<ballA.radius+ballB.radius) {
69                 ballA.color=0xff0000;
70                 ballB.color=0xff0000;
71             }
72         }
73     }
74 }
View Code


在线演示

当然这种网格算法不仅仅只能用于上面提供的"实打实"的碰撞,其中只要是基于距离的对象检测,它都适用。

回顾一下以前做过的节点花园 (http://www.cnblogs.com/yjmyzz/archive/2010/04/28/1723003.html)示例,当时因为粒子数量比较少,还看不出有什么性能问题,让我们把粒子数量弄得多一点,比如500,再来测试下:

 1 package {
 2     import flash.display.Sprite;
 3     import flash.display.StageScaleMode;
 4     import flash.display.StageAlign;
 5     import flash.events.Event;
 6     import flash.geom.Point;
 7      
 8     [SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
 9     public class NodeGardenLines extends Sprite {
10         private var particles:Array;
11         private var numParticles:uint=300;
12         private var minDist:Number=50;
13         private var springAmount:Number=.001;
14         public function NodeGardenLines() {
15             init();
16         }
17         private function init():void {
18             stage.scaleMode=StageScaleMode.NO_SCALE;
19             stage.align=StageAlign.TOP_LEFT;
20             particles = new Array();
21             for (var i:uint = 0; i < numParticles; i++) {
22                 var particle:Ball=new Ball(2,0x00ff00,false);
23                 particle.x=Math.random()*stage.stageWidth;
24                 particle.y=Math.random()*stage.stageHeight;
25                 particle.vx=Math.random()*6-3;
26                 particle.vy=Math.random()*6-3;
27                 addChild(particle);
28                 particles.push(particle);
29             }
30             addEventListener(Event.ENTER_FRAME, onEnterFrame);
31              
32             var fps:FPSshow = new FPSshow();
33             addChild(fps);
34         }
35         private function onEnterFrame(event:Event):void {
36             graphics.clear();
37             for (var i:uint = 0; i < numParticles; i++) {
38                 var particle:Ball=particles[i];
39                 particle.x+=particle.vx;
40                 particle.y+=particle.vy;
41                 if (particle.x>stage.stageWidth) {
42  
43                     particle.x=0;
44                 } else if (particle.x < 0) {
45                     particle.x=stage.stageWidth;
46                 }
47                 if (particle.y>stage.stageHeight) {
48                     particle.y=0;
49                 } else if (particle.y < 0) {
50                     particle.y=stage.stageHeight;
51                 }
52             }
53             for (i=0; i < numParticles - 1; i++) {
54                 var partA:Ball=particles[i];
55                 for (var j:uint = i + 1; j < numParticles; j++) {
56                     var partB:Ball=particles[j];
57                     spring(partA, partB);
58                 }
59             }
60         }
61         private function spring(partA:Ball, partB:Ball):void {
62             var dx:Number=partB.x-partA.x;
63             var dy:Number=partB.y-partA.y;
64             var dist:Number=Math.sqrt(dx*dx+dy*dy);
65             if (dist<minDist) {
66                 graphics.lineStyle(1, 0xffffff, 1 - dist / minDist);
67                 graphics.moveTo(partA.x, partA.y);
68                 graphics.lineTo(partB.x, partB.y);
69                 var ax:Number=dx*springAmount;
70                 var ay:Number=dy*springAmount;
71                 partA.vx+=ax;
72                 partA.vy+=ay;
73                 partB.vx-=ax;
74                 partB.vy-=ay;
75             }
76         }
77     }
78 }
View Code


在线演示

留意一下现在的帧数,下面是采用网格算法后的代码:

 1 package {
 2     import flash.display.DisplayObject;
 3     import flash.display.Sprite;
 4     import flash.display.StageScaleMode;
 5     import flash.display.StageAlign;
 6     import flash.events.Event;
 7     import flash.geom.Point;
 8      
 9     [SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
10     public class NodeGardenGrid extends Sprite {
11         private var particles:Vector.<DisplayObject>;
12         private var numParticles:uint=300;
13         private var minDist:Number=50;
14         private var springAmount:Number=.001;
15         private var grid:CollisionGrid;
16          
17         public function NodeGardenGrid() {
18             init();
19         }
20          
21         private function init():void {
22             stage.scaleMode=StageScaleMode.NO_SCALE;
23             stage.align=StageAlign.TOP_LEFT;
24             grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,52);
25             particles = new Vector.<DisplayObject>();
26             for (var i:uint = 0; i < numParticles; i++) {
27                 var particle:Ball=new Ball(2,0x00ff00,false);
28                 particle.x=Math.random()*stage.stageWidth;
29                 particle.y=Math.random()*stage.stageHeight;
30                 particle.vx=Math.random()*6-3;
31                 particle.vy=Math.random()*6-3;
32                 addChild(particle);
33                 particles.push(particle);
34             }
35             addEventListener(Event.ENTER_FRAME, onEnterFrame);
36  
37             var fps:FPSshow = new FPSshow();
38             addChild(fps);
39         }
40          
41         private function onEnterFrame(event:Event):void {
42             graphics.clear();
43             for (var i:uint = 0; i < numParticles; i++) {
44                 var particle:Ball=particles[i] as Ball;
45                 particle.x+=particle.vx;
46                 particle.y+=particle.vy;
47                 if (particle.x>stage.stageWidth) {
48                     particle.x=0;
49                 } else if (particle.x < 0) {
50                     particle.x=stage.stageWidth;
51                 }
52                 if (particle.y>stage.stageHeight) {
53                     particle.y=0;
54                 } else if (particle.y < 0) {
55                     particle.y=stage.stageHeight;
56                 }
57             }
58             grid.assign(particles);
59             var checks:Vector.<DisplayObject>=grid.checks;
60             trace(checks.length);
61             var numChecks:int=checks.length;
62             for (i=0; i < numChecks; i += 2) {
63                 var partA:Ball=checks[i] as Ball;
64                 var partB:Ball=checks[i+1] as Ball;
65                 spring(partA, partB);
66             }
67         }
68          
69         private function spring(partA:Ball, partB:Ball):void {
70             var dx:Number=partB.x-partA.x;
71             var dy:Number=partB.y-partA.y;
72             var dist:Number=Math.sqrt(dx*dx+dy*dy);
73             if (dist<minDist) {
74                 graphics.lineStyle(1, 0x00ff00, 1 - dist / minDist);
75                 graphics.moveTo(partA.x, partA.y);
76                 graphics.lineTo(partB.x, partB.y);
77                 var ax:Number=dx*springAmount;
78                 var ay:Number=dy*springAmount;
79                 partA.vx+=ax;
80                 partA.vy+=ay;
81                 partB.vx-=ax;
82                 partB.vy-=ay;
83             }
84         }
85     }
86 }
View Code


在线演示

如果用IE的朋友,貌似弹出窗口加载flash有些问题(偶尔会引发异常),建议用firefox或chrome浏览器浏览本文。

在firefox下加速效果最为明显,比较意外的是在chrome下居然二种算法帧数相差无已。(极度怀疑google与adobe协力对chrome浏览器上的flash插件做了极大的优化)

作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
原文地址:https://www.cnblogs.com/buerjiongjiong/p/4731099.html