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 }
可以看到,上半部分的位图因为不支持透明,所以将背后的线条全部挡住了。
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会为我们自动补齐
注意:虽然只画了二条线,但由于应用了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;
当然,封装成一个单独的类会更好,下面是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;
如上,为了能完整的用位图"画"出五星,需要将星星向左、向下移动一定的位置,即前面提到的矩阵变换。
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 }
这里我们用二个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 }
调整第二个滑块,然后再测试碰撞效果,体会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 }
最终的运行效果,跟之前的示例没有区别,就不重复贴出了
继续,考虑更复杂的大量对象的碰撞问题,前一阵我们刚学习过“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 }
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 }
上面的示例中,左上角的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 }
上面这个示例,我们把"网格检测算法"与传统的"两两检测算法"每个跑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 }
注:除了单纯的封装以外,上面的代码还有三个重要的优化措施
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 }
对比之前未封装的示例,可以发现:执行时间缩短了近一半!说明优化的效果还是很不错的。
静态的碰撞检测可能比较没劲,可以再结合以前学到的知识,让小球动起来。
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 }
当然这种网格算法不仅仅只能用于上面提供的"实打实"的碰撞,其中只要是基于距离的对象检测,它都适用。
回顾一下以前做过的节点花园 (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 }
留意一下现在的帧数,下面是采用网格算法后的代码:
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 }
如果用IE的朋友,貌似弹出窗口加载flash有些问题(偶尔会引发异常),建议用firefox或chrome浏览器浏览本文。
在firefox下加速效果最为明显,比较意外的是在chrome下居然二种算法帧数相差无已。(极度怀疑google与adobe协力对chrome浏览器上的flash插件做了极大的优化)
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。