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

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

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

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

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

package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.geom.Rectangle;
	
	[SWF(height="400",width="300")]
	public class BitmapCompare extends Sprite {
		public function BitmapCompare() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			
			//随机在舞台上划一些线条
			graphics.lineStyle(0);
			for (var i:int=0; i<100; i++) {
				graphics.lineTo(Math.random()*300,Math.random()*400);
			}
			
			//创建一个不透明的位图
			var bmpd1:BitmapData=new BitmapData(300,200,false,0xffff99);
			bmpd1.fillRect(new Rectangle(100,50,100,100),0xff0000);//注意:因为不透明的,所以颜色是24位的,没有alpha分量
			var bmp1:Bitmap=new Bitmap(bmpd1);
			addChild(bmp1);
			
			//创建一个支持透明的位图
			var bmpd2:BitmapData=new BitmapData(300,200,true,0x80ffff99);//注:默认为50%透明的ff9颜色
			bmpd2.fillRect(new Rectangle(100,50,100,100),0x80ff0000);//注:此处为32位颜色
			var bmp2:Bitmap=new Bitmap(bmpd2);
			bmp2.y=200;
			addChild(bmp2);			
		}
	}
}


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

2、五角星的画法

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

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


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

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

var angleBase:Number=Math.PI*2/10;
var radius:uint=100;
var r2:uint;
var i:uint;
var px,py:Number;

var starline:Sprite = new Sprite();
starline.graphics.lineStyle(0);
for (i=0; i<10; i++) {
	r2=radius;
	if (i%2==0) {
		r2=radius/2;
	}
	starline.graphics.moveTo(0,0);
	px=r2*Math.cos(angleBase*i);
	py=r2*Math.sin(angleBase*i);
	starline.graphics.lineTo(px,py);
}

addChild(starline);
starline.x=stage.stageWidth/2;
starline.y=stage.stageHeight/2;

var star:Sprite = new Sprite();
star.graphics.lineStyle(1,0xff0000);
star.graphics.beginFill(0xffff99);
star.graphics.moveTo(radius/2,0);
for (i=0; i<10; i++) {
	r2=radius;
	if (i%2==0) {
		r2=radius/2;
	}
	px=r2*Math.cos(angleBase*i);
	py=r2*Math.sin(angleBase*i);	
	star.graphics.lineTo(px,py);
	
}
//star.graphics.lineTo(radius/2,0);
addChild(star);
star.x=stage.stageWidth/2;
star.y=stage.stageHeight/2;
star.alpha = 0.5;

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

package {
	import flash.display.Sprite;
	public class Star extends Sprite {
		public function Star(radius:Number,color:uint=0xFFFF00):void {
			graphics.lineStyle(0);
			graphics.moveTo(radius,0);
			graphics.beginFill(color);
			for (var i:int=1; i < 11; i++) {
				var radius2:Number=radius;
				if (i%2>0) {
					radius2=radius/2;
				}
				var angle:Number=Math.PI*2/10*i;
				graphics.lineTo(Math.cos(angle) * radius2,Math.sin(angle) * radius2);
			}
		}
	}
}

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

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

var bmd1:BitmapData = new BitmapData(100,100,false,0xffefefef);
bmd1.draw(star1);
var bmp1:Bitmap = new Bitmap(bmd1);
addChild(bmp1);
bmp1.x = bmp1.y = 10;

var bmd2:BitmapData = new BitmapData(100,100,false,0xffefefef);
var m:Matrix = new Matrix();
trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 0 0
m.tx = 50;
m.ty = 50;
trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 50 50
bmd2.draw(star1,m);
//等效于
//bmd2.draw(star1,new Matrix(1,0,0,1,50,50))
var bmp2:Bitmap = new Bitmap(bmd2);
addChild(bmp2);
bmp2.x = bmp1.x + 110;
bmp2.y = bmp1.y;

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

 

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

package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	public class BitmapCollision1 extends Sprite {
		
		private var bmpd1:BitmapData;
		private var bmp1:Bitmap;
		private var bmpd2:BitmapData;
		private var bmp2:Bitmap;
		
		public function BitmapCollision1() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			var matrix:Matrix = new Matrix();
			var radius:uint = 50;
			matrix.tx = radius;
			matrix.ty = radius;			
			var star:Star=new Star(radius);
			
			bmpd1=new BitmapData(100,100,true,0);			
			bmpd1.draw(star,matrix);
			bmp1=new Bitmap(bmpd1);
			bmp1.x=200;
			bmp1.y=200;
			addChild(bmp1);
			
			bmpd2=new BitmapData(100,100,true,0);			
			bmpd2.draw(star,matrix);
			bmp2=new Bitmap(bmpd2);
			addChild(bmp2);
			
			stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMoving);
		}
		
		private function onMouseMoving(event:MouseEvent):void {
			
			bmp2.x=mouseX-50;
			bmp2.y=mouseY-50;
			
			if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),255,bmpd2,new Point(bmp2.x,bmp2.y),255)) {
				bmp1.filters=[new GlowFilter];
				bmp2.filters=[new GlowFilter];
			} else {
				bmp1.filters=[];
				bmp2.filters=[];
			}
		}
	}
}

这里我们用二个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分量参数”有更好的理解,上面的示例可改进为下面这样:

package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.GradientType;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import fl.events.SliderEvent;
	
	public class BitmapCollision2 extends Sprite {
		private var bmpd1:BitmapData;
		private var bmp1:Bitmap;
		private var bmpd2:BitmapData;
		private var bmp2:Bitmap;
		public function BitmapCollision2() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			
			var star:Star=new Star(50);
			
			var matrix:Matrix = new Matrix();
			matrix.createGradientBox(100, 100, 0, -50, -50);
			
			var circle:Sprite = new Sprite();
			//画一个渐变填充的圆
			circle.graphics.beginGradientFill(GradientType.RADIAL,[0, 0],[1, 0],[0, 255],matrix);
			circle.graphics.drawCircle(0, 0, 50);
			circle.graphics.endFill();
			
			bmpd1=new BitmapData(100,100,true,0);
			bmpd1.draw(star, new Matrix(1, 0, 0, 1, 50, 50));
			bmp1=new Bitmap(bmpd1);
			bmp1.x=stage.stageWidth/2 - bmp1.width/2;
			bmp1.y=stage.stageHeight/2 - bmp1.height/2;
			addChild(bmp1);
			
			bmpd2=new BitmapData(100,100,true,0);
			bmpd2.draw(circle, new Matrix(1, 0, 0, 1, 50, 50));
			bmp2=new Bitmap(bmpd2);
			addChild(bmp2);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving);
			
			//slider1,slider2是舞台上用设计工具拖出来的二个滑动控件
			slider2.addEventListener(SliderEvent.THUMB_DRAG,slider2Change);
			slider1.addEventListener(SliderEvent.THUMB_DRAG,slider1Change);
			
		}
		
		private function slider1Change(e:SliderEvent):void
		{
			txt1.text = e.value.toString();
		}
		
		private function slider2Change(e:SliderEvent):void
		{
			txt2.text = e.value.toString();
		}
		
		private function onMouseMoving(event:MouseEvent):void {
			
			
			if (mouseY>320){return;}//防止小球拖到太下面,挡住了滑块
			
			bmp2.x=mouseX-50;
			bmp2.y=mouseY-50;
			
			if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),slider1.value,bmpd2,new Point(bmp2.x,bmp2.y),slider2.value)) {
				bmp1.filters = [new GlowFilter()];
				bmp2.filters = [new GlowFilter()];
			} else {
				bmp1.filters=[];
				bmp2.filters=[];
			}
		}
	}
}

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

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

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

package {
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	public class BitmapCollision3 extends Sprite {
		private var bmpd1:BitmapData;
		private var bmpd2:BitmapData;
		private var star1:Star;
		private var star2:Star;
		public function BitmapCollision3() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;			
			star1=new Star(50);
			addChild(star1);
			star2=new Star(50);
			star2.x=200;
			star2.y=200;
			addChild(star2);			
			bmpd1=new BitmapData(stage.stageWidth,stage.stageHeight,true,0);
			bmpd2=bmpd1.clone();
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving);
			//注:这里bmpd1,bmpd2都没被转成bitmap,更没有加入到舞台中.
		}
		private function onMouseMoving(event:MouseEvent):void {
			
			star1.x=mouseX;
			star1.y=mouseY;
			//先清空bitmapData中的数据,准备一个完全透明的黑"底板"。
			bmpd1.fillRect(bmpd1.rect, 0);
			bmpd2.fillRect(bmpd2.rect, 0);
			//再把要检测的(movieclip或sprite)对象,画到里面.
			bmpd1.draw(star1, new Matrix(1, 0, 0, 1, star1.x, star1.y));
			bmpd2.draw(star2, new Matrix(1, 0, 0, 1, star2.x, star2.y));
			//碰撞检测
			//注意:因为bmpd1,bmpd2都没被加入到舞台上,所以默认都在同样的0坐标位置,因此下面的坐标,直接用默认的Point对象实例即可.
			if (bmpd1.hitTest(new Point(), 255, bmpd2, new Point(), 255)) {
				star1.filters = [new GlowFilter()];
				star2.filters = [new GlowFilter()];
			} else {
				star1.filters=[];
				star2.filters=[];
			}
		}
	}
}

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

 

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

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

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

 

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

网格碰撞检测

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

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

 

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

 

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

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

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

 

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

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

 

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

 

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

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

package {
	import flash.display.Sprite;
	public class Ball extends Sprite {
		private var _color:uint;
		private var _radius:Number;
		private var _vx:Number=0;
		private var _vy:Number=0;
		public function Ball(radius:Number, color:uint = 0xffffff) {
			_radius=radius;
			_color=color;
			draw();
		}
		private function draw():void {

			graphics.clear();
			graphics.lineStyle(0);
			graphics.beginFill(_color, 1);
			graphics.drawCircle(0, 0, _radius);
			graphics.endFill();
			graphics.drawCircle(0, 0, 1);//在中心画一个点
		}
		public function update():void {
			x+=_vx;
			y+=_vy;
		}
		public function set color(value:uint):void {
			_color=value;
			draw();
		}
		public function get color():uint {
			return _color;
		}
		public function set radius(value:Number):void {
			_radius=value;
			draw();
		}
		public function get radius():Number {
			return _radius;
		}
		public function set vx(value:Number):void {
			_vx=value;
		}
		public function get vx():Number {
			return _vx;
		}
		public function set vy(value:Number):void {
			_vy=value;
		}
		public function get vy():Number {
			return _vy;
		}
	}
}

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

package {
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	
	public class GridCollision extends Sprite {
		
		private const GRID_SIZE:Number=30;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
		private const RADIUS:Number=15;//小球的半径
		private var _balls:Array;
		private var _grid:Array;
		private var _numBalls:int=100;//小球数量
		private var _numChecks:int=0;//检测次数
		private var _txt:TextField = new TextField();

		public function GridCollision() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			makeBalls();//创建一堆小球
			makeGrid();//
			drawGrid();
			assignBallsToGrid();
			checkGrid();
			
			//显示计数器
			trace(_numChecks);
			addChild(_txt);
			_txt.background = true;
			_txt.backgroundColor=0xffff99;
			_txt.height = 20;
			_txt.width = 30;
			_txt.alpha = 0.7;
			
			stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownClick);
		}

		private function mouseDownClick(e:MouseEvent):void{
			for (var i:int=0; i<_numBalls; i++) {
				var ball:Ball=_balls[i];
				ball.x=Math.random()*stage.stageWidth;
				ball.y=Math.random()*stage.stageHeight;	
				ball.color = 0xffffff;
			}
			_numChecks=0;
			makeGrid();//
			drawGrid();
			assignBallsToGrid();
			checkGrid();
			
		}

		//创建_numBalls个小球实例,并随机摆放到舞台上
		private function makeBalls():void {
			_balls=new Array  ;
			for (var i:int=0; i<_numBalls; i++) {
				var ball:Ball=new Ball(RADIUS);
				ball.x=Math.random()*stage.stageWidth;
				ball.y=Math.random()*stage.stageHeight;
				addChild(ball);
				_balls.push(ball);
			}
		}

		private function makeGrid():void {
			_grid=new Array ;

			for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
				_grid[i]=new Array  ;

				for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
					_grid[i][j]=new Array ;//每个单元格对应一个数组(用来存放该单元格中的小球)
				}
			}
		}

		private function drawGrid():void {
			// 画出行列线
			graphics.lineStyle(0,.5);
			for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
				graphics.moveTo(i,0);
				graphics.lineTo(i,stage.stageHeight);
			}
			for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
				graphics.moveTo(0,i);
				graphics.lineTo(stage.stageWidth,i);
			}
		}


		private function assignBallsToGrid():void {
			for (var i:int=0; i<_numBalls; i++) {
				// 球的位置除以格子大小,得到该球所在网格的行列数
				var ball:Ball=_balls[i] as Ball;
				var xpos:int=Math.floor(ball.x/GRID_SIZE);
				var ypos:int=Math.floor(ball.y/GRID_SIZE);
				_grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
			}
		}

		private function checkGrid():void {
			for (var i:int=0; i<_grid.length; i++) {
				for (var j:int=0; j<_grid[i].length; j++) {
					
					checkOneCell(i,j);//单元格cell_self自身的碰撞检测
					checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
					checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
					checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
					checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
				}
			}
		}
	
		//cellSelf与自身的检测
		private function checkOneCell(x:int,y:int):void {
			// 检测当前格子内所有的对象
			var cell:Array=_grid[x][y] as Array;
			for (var i:int=0; i<cell.length-1; i++) {
				var ballA:Ball=cell[i] as Ball;
				for (var j:int=i+1; j<cell.length; j++) {
					var ballB:Ball=cell[j] as Ball;
					checkCollision(ballA,ballB);
				}
			}
		}

		//cellSelf与其它单元格的检测
		private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
			//确保要检测的格子存在
			if (x2<0) {
				return;
			}
			if (x2>=_grid.length) {
				return;
			}
			if (y2>=_grid[x2].length) {
				return;
			}
			var cell0:Array=_grid[x1][y1] as Array;
			var cell1:Array=_grid[x2][y2] as Array;
			
			// 检测当前格子和邻接格子内所有的对象
			for (var i:int=0; i<cell0.length; i++) {
				var ballA:Ball=cell0[i] as Ball;
				for (var j:int=0; j<cell1.length; j++) {
					var ballB:Ball=cell1[j] as Ball;
					checkCollision(ballA,ballB);
				}
			}
		}

		private function checkCollision(ballA:Ball,ballB:Ball):void {
			// 判断距离的碰撞检测
			_numChecks++;//计数器累加
			_txt.text = _numChecks.toString();
			var dx:Number=ballB.x-ballA.x;
			var dy:Number=ballB.y-ballA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<ballA.radius+ballB.radius) {
				//碰撞的小球变红色
				ballA.color=0xff0000;
				ballB.color=0xff0000;
			}

		}
	}
}

5542838620100707155153016_640.jpg
在线演示

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

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

 

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

package {
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import fl.controls.Slider;
	import flash.utils.getTimer;
	import fl.events.SliderEvent;

	public class GridCollision extends Sprite {

		private const GRID_SIZE:Number=20;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
		private const RADIUS:Number=10;//小球的半径
		private var _balls:Array;
		private var _grid:Array;
		private var _numBalls:int=50;//小球数量
		private var _txtGrid:TextField = new TextField();
		private var _txtBasic:TextField = new TextField();
		private var _slider:Slider = new Slider();

		public function GridCollision() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;

			makeGrid();
			drawGrid();

			addChild(_slider);
			addChild(_txtGrid);
			addChild(_txtBasic);

			test();

			_slider.addEventListener(SliderEvent.THUMB_DRAG,sliderGrag);
			stage.addEventListener(MouseEvent.CLICK,stageClick);

		}

		private function test(isClear:Boolean=false):void {
			var i:int=0;
			if (isClear) {
				for (i=numChildren-1; i>=0; i--) {
					removeChild(getChildAt(i));
				}
				_balls.length=0;
			}
			_txtGrid.background=_txtBasic.background=true;
			_txtGrid.backgroundColor=_txtBasic.backgroundColor=0xffff99;
			_txtBasic.height=_txtGrid.height=20;
			_txtBasic.width=_txtGrid.width=135;
			_txtBasic.alpha=_txtGrid.alpha=0.9;
			_txtBasic.x=stage.stageWidth-_txtBasic.width;
			_slider.maximum=300;
			_slider.minimum=30;
			_slider.snapInterval=10;
			_slider.y=10;
			_slider.value=_numBalls;
			_slider.width=200;
			_slider.x=stage.stageWidth/2-_slider.width/2;

			makeBalls();//创建一堆小球

			var startTime:int;
			var elapsed:int;

			startTime=getTimer();
			for (i=0; i<10; i++) {
				makeGrid();
				assignBallsToGrid();
				checkGrid();
			}
			elapsed=getTimer()-startTime;
			trace("网格检测:",elapsed);
			_txtGrid.text=_numBalls+"个球网络检测:"+elapsed.toString();

			startTime=getTimer();
			for (i=0; i<10; i++) {
				basicCheck();
			}
			elapsed=getTimer()-startTime;
			trace("两两检测:",elapsed);
			_txtBasic.text=_numBalls+"个球两两检测:"+elapsed.toString();

			if (isClear) {
				addChild(_txtBasic);
				addChild(_txtGrid);
				addChild(_slider);
			}
		}

		private function sliderGrag(e:SliderEvent):void {
			_numBalls=e.value;
			trace("sliderGrag");
		}

		private function stageClick(e:MouseEvent):void {
			trace("stageClick");
			test(true);
		}

		//创建_numBalls个小球实例,并随机摆放到舞台上
		private function makeBalls():void {
			_balls=new Array  ;
			for (var i:int=0; i<_numBalls; i++) {
				var ball:Ball=new Ball(RADIUS);
				ball.x=Math.random()*stage.stageWidth;
				ball.y=Math.random()*stage.stageHeight;
				addChild(ball);
				ball.alpha=0.5;
				_balls.push(ball);
			}
		}

		private function makeGrid():void {
			_grid=new Array  ;
			for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
				_grid[i]=new Array  ;
				for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
					_grid[i][j]=new Array  ;//每个单元格对应一个数组(用来存放该单元格中的小球)
				}
			}
		}

		private function drawGrid():void {
			// 画出行列线
			graphics.lineStyle(0,0x999999);
			for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
				graphics.moveTo(i,0);
				graphics.lineTo(i,stage.stageHeight);
			}
			for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
				graphics.moveTo(0,i);
				graphics.lineTo(stage.stageWidth,i);
			}
		}


		private function assignBallsToGrid():void {
			for (var i:int=0; i<_numBalls; i++) {
				// 球的位置除以格子大小,得到该球所在网格的行列数
				var ball:Ball=_balls[i] as Ball;
				var xpos:int=Math.floor(ball.x/GRID_SIZE);
				var ypos:int=Math.floor(ball.y/GRID_SIZE);
				_grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
			}
		}

		private function checkGrid():void {
			for (var i:int=0; i<_grid.length; i++) {
				for (var j:int=0; j<_grid[i].length; j++) {
					checkOneCell(i,j);//单元格cell_self自身的碰撞检测
					checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
					checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
					checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
					checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
				}
			}
		}

		//cellSelf与自身的检测
		private function checkOneCell(x:int,y:int):void {
			// 检测当前格子内所有的对象
			var cell:Array=_grid[x][y] as Array;
			for (var i:int=0; i<cell.length-1; i++) {
				var ballA:Ball=cell[i] as Ball;
				for (var j:int=i+1; j<cell.length; j++) {
					var ballB:Ball=cell[j] as Ball;
					checkCollision(ballA,ballB);
				}
			}
		}

		//cellSelf与其它单元格的检测
		private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
			//确保要检测的格子存在
			if (x2<0) {
				return;
			}
			if (x2>=_grid.length) {
				return;
			}
			if (y2>=_grid[x2].length) {
				return;
			}
			var cell0:Array=_grid[x1][y1] as Array;
			var cell1:Array=_grid[x2][y2] as Array;

			// 检测当前格子和邻接格子内所有的对象
			for (var i:int=0; i<cell0.length; i++) {
				var ballA:Ball=cell0[i] as Ball;
				for (var j:int=0; j<cell1.length; j++) {
					var ballB:Ball=cell1[j] as Ball;
					checkCollision(ballA,ballB);
				}
			}
		}

		private function checkCollision(ballA:Ball,ballB:Ball):void {
			// 判断距离的碰撞检测
			var dx:Number=ballB.x-ballA.x;
			var dy:Number=ballB.y-ballA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<ballA.radius+ballB.radius) {
				//碰撞的小球变红色
				ballA.color=0xff0000;
				ballB.color=0xff0000;
			}
		}

		//(最原始的)两两检测
		private function basicCheck():void {
			for (var i: int=0; i < _balls.length - 1; i++) {
				var ballA:Ball=_balls[i] as Ball;
				for (var j: int=i+1; j < _balls.length; j++) {
					var ballB:Ball=_balls[j] as Ball;
					checkCollision(ballA, ballB);
				}
			}
		}
	}
}

5542838620100707151054045_640.jpg
在线演示

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

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

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

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

package {
	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.events.EventDispatcher;
	
	public class CollisionGrid extends EventDispatcher {
		
		private var _checks:Vector.<DisplayObject>;//用于保存需要碰撞检测的对象(注:Vector.<T>相当于c#中的泛型数组)
		private var _grid:Vector.<Vector.<DisplayObject>>;//网格(注:这里用“一维数组套一维数组”的方法替代了原来的二维数组)
		private var _gridSize:Number;
		private var _height:Number;
		private var _numCells:int;
		private var _numCols:int;
		private var _numRows:int;
		private var _width:Number;
		
		public function CollisionGrid(width:Number, height:Number, gridSize:Number) {
			_width=width;
			_height=height;
			_gridSize=gridSize;
			
			_numCols=Math.ceil(_width/_gridSize);//计算总列数			
			_numRows=Math.ceil(_height/_gridSize);//计算总行数
			_numCells=_numCols*_numRows;//单元格总数
		}
		
		//画格子
		public function drawGrid(graphics:Graphics):void {
			graphics.lineStyle(0, .5);
			for (var i:int = 0; i <= _width; i += _gridSize) {
				graphics.moveTo(i, 0);
				graphics.lineTo(i, _height);
			}
			for (i = 0; i <= _height; i += _gridSize) {
				graphics.moveTo(0, i);
				graphics.lineTo(_width, i);
			}
		}
		
		//将需要检测的对象(泛型)数组objects分配到网络
		public function assign(objects:Vector.<DisplayObject>):void {
			var numObjects:int=objects.length;
			_grid=new Vector.<Vector.<DisplayObject>>(_numCells);
			_checks = new Vector.<DisplayObject>();
			for (var i:int = 0; i < numObjects; i++) {
				var obj:DisplayObject=objects[i];
				//注意:这里用“Grid.[索引]”(定位)的方式,替换了原来的“Grid.[列][行]”(单元格的定位)方式--回想一下bitmap位图中的像素索引就更容易理解了
				var index:int=Math.floor(obj.y/_gridSize)*_numCols+Math.floor(obj.x/_gridSize);
				//“单元格”--延时实例化"
				if (_grid[index]==null) {
					_grid[index]=new Vector.<DisplayObject>  ;
				}
				//将对象推入"单元格"
				_grid[index].push(obj);
			}
			
			//检测需要碰撞的对象,并保存到_checks数组
			checkGrid();
		}
		
		//"单元格"检测
		private function checkGrid():void {
			for (var i:int = 0; i < _numCols; i++) {
				for (var j:int = 0; j < _numRows; j++) {
					checkOneCell(i, j);
					checkTwoCells(i, j, i + 1, j);
					checkTwoCells(i, j, i - 1, j + 1);
					checkTwoCells(i, j, i, j + 1);
					checkTwoCells(i, j, i + 1, j + 1);
				}
			}
		}
		
		//(自身)单个单元格的检测
		private function checkOneCell(x:int, y:int):void {
			var cell:Vector.<DisplayObject>=_grid[y*_numCols+x];
			if (cell==null) {
				return;
			}
			var cellLength:int=cell.length;
			
			for (var i:int = 0; i < cellLength - 1; i++) {
				var objA:DisplayObject=cell[i];
				for (var j:int = i + 1; j < cellLength; j++) {
					var objB:DisplayObject=cell[j];
					_checks.push(objA, objB);
				}
			}
		}
		
		//单元格(x1,y1)与单元格(x2,y2)的检测
		private function checkTwoCells(x1:int, y1:int, x2:int, y2:int):void {
			if (x2>=_numCols||x2<0||y2>=_numRows) {
				return;
			}
			var cellA:Vector.<DisplayObject>=_grid[y1*_numCols+x1];
			var cellB:Vector.<DisplayObject>=_grid[y2*_numCols+x2];
			if (cellA==null||cellB==null) {
				return;
			}
			var cellALength:int=cellA.length;
			var cellBLength:int=cellB.length;
			for (var i:int = 0; i < cellALength; i++) {
				var objA:DisplayObject=cellA[i];
				for (var j:int = 0; j < cellBLength; j++) {
					var objB:DisplayObject=cellB[j];
					_checks.push(objA, objB);
				}
			}
		}
		
		public function get checks():Vector.<DisplayObject> {
			return _checks;
		}
	}
}

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

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

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

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

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

package {
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.utils.getTimer;
	import flash.display.DisplayObject;
	import flash.events.MouseEvent;
	import flash.text.TextField;

	public class GridCollision2 extends Sprite {
		private const GRID_SIZE:Number=20;
		private const RADIUS:Number=10;
		private var _balls:Vector.<DisplayObject>;//这里用Vector代替了Array
		private var _grid:CollisionGrid;
		private var _numBalls:int=50;
		private var _text:TextField;

		public function GridCollision2() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			_text = new TextField();
			_text.background = true;
			_text.backgroundColor = 0xffff99;
			_text.width = 135;
			_text.height = 20;
			_text.alpha = 0.9;
			
			
			_grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
			_grid.drawGrid(graphics);

			makeBalls();
			addChild(_text);
			test();

			stage.addEventListener(MouseEvent.CLICK,stageClick);
		}

		private function stageClick(e:MouseEvent):void {
			test(true);
		}

		private function test(isRestart:Boolean=false):void {
			if (isRestart) {
				for (var i:int=0; i<_numBalls; i++) {
					var ball:Ball=_balls[i] as Ball;
					ball.x=Math.random()*stage.stageWidth;
					ball.y=Math.random()*stage.stageHeight;
					ball.color = 0xffffff;
				}
			}

			var startTime:int;
			var elapsed:int;
			startTime=getTimer();
			for (i=0; i<10; i++) {
				_grid.assign(_balls);//将所有需要检测的ball放入_grid.checks
				var numChecks:int=_grid.checks.length;
				for (var j:int=0; j<numChecks; j+=2) {
					checkCollision(_grid.checks[j] as Ball,_grid.checks[j+1] as Ball);
				}
			}
			elapsed=getTimer()-startTime;
			trace("Elapsed:",elapsed);
			_text.text = _numBalls + "个小球碰撞检测:" + elapsed.toString();
		}
		
		//初始化小球实例
		private function makeBalls():void {
			_balls=new Vector.<DisplayObject>(_numBalls);
			for (var i:int=0; i<_numBalls; i++) {
				var ball:Ball=new Ball(RADIUS);
				ball.x=Math.random()*stage.stageWidth;
				ball.y=Math.random()*stage.stageHeight;
				ball.alpha = 0.8;
				addChild(ball);
				_balls[i]=ball;
			}
		}

		//检测碰撞
		private function checkCollision(ballA:Ball,ballB:Ball):void {
			var dx:Number=ballB.x-ballA.x;
			var dy:Number=ballB.y-ballA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<ballA.radius+ballB.radius) {
				//(碰撞后的小球变红色)
				ballA.color=0xff0000;
				ballB.color=0xff0000;
			}
		}
	}
}

5542838620100707151054045_640.jpg
在线演示

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

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

package {
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display.DisplayObject;
	import flash.events.Event;
	
	public class GridCollision3 extends Sprite {
		private const GRID_SIZE:Number=20;
		private const RADIUS:Number=10;
		private var _balls:Vector.<DisplayObject>;
		private var _grid:CollisionGrid;
		private var _numBalls:int=100;
		public function GridCollision3() {
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			_grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
			_grid.drawGrid(graphics);
			makeBalls();
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		function onEnterFrame(event:Event):void {
			updateBalls();
			_grid.assign(_balls);
			var numChecks:int=_grid.checks.length;
			for (var j:int = 0; j < numChecks; j += 2) {
				checkCollision(_grid.checks[j] as Ball, _grid.checks[j + 1] as Ball);
			}
		}
		private function makeBalls():void {
			_balls=new Vector.<DisplayObject>(_numBalls);
			for (var i:int = 0; i < _numBalls; i++) {
				var ball:Ball=new Ball(RADIUS);
				ball.x=Math.random()*stage.stageWidth;
				ball.y=Math.random()*stage.stageHeight;
				ball.vx=Math.random()*4-2;
				ball.vy=Math.random()*4-2;
				addChild(ball);
				_balls[i]=ball;
			}
		}
		private function updateBalls():void {
			for (var i:int = 0; i < _numBalls; i++) {
				
				var ball:Ball=_balls[i] as Ball;
				ball.update();
				if (ball.x<RADIUS) {
					ball.x=RADIUS;
					ball.vx*=-1;
				} else if (ball.x > stage.stageWidth - RADIUS) {
					ball.x=stage.stageWidth-RADIUS;
					ball.vx*=-1;
				}
				if (ball.y<RADIUS) {
					ball.y=RADIUS;
					ball.vy*=-1;
				} else if (ball.y > stage.stageHeight - RADIUS) {
					ball.y=stage.stageHeight-RADIUS;
					ball.vy*=-1;
				}
				ball.color=0xffffff;
			}
		}
		private function checkCollision(ballA:Ball, ballB:Ball):void {
			var dx:Number=ballB.x-ballA.x;
			var dy:Number=ballB.y-ballA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<ballA.radius+ballB.radius) {
				ballA.color=0xff0000;
				ballB.color=0xff0000;
			}
		}
	}
}

5542838620100707151054045_640.jpg
在线演示

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

回顾一下以前做过的节点花园示例,当时因为粒子数量比较少,还看不出有什么性能问题,让我们把粒子数量弄得多一点,比如500,再来测试下:

package {
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;
	import flash.events.Event;
	import flash.geom.Point;
	
	[SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
	public class NodeGardenLines extends Sprite {
		private var particles:Array;
		private var numParticles:uint=300;
		private var minDist:Number=50;
		private var springAmount:Number=.001;
		public function NodeGardenLines() {
			init();
		}
		private function init():void {
			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;
			particles = new Array();
			for (var i:uint = 0; i < numParticles; i++) {
				var particle:Ball=new Ball(2,0x00ff00,false);
				particle.x=Math.random()*stage.stageWidth;
				particle.y=Math.random()*stage.stageHeight;
				particle.vx=Math.random()*6-3;
				particle.vy=Math.random()*6-3;
				addChild(particle);
				particles.push(particle);
			}
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			
			var fps:FPSshow = new FPSshow();
			addChild(fps);
		}
		private function onEnterFrame(event:Event):void {
			graphics.clear();
			for (var i:uint = 0; i < numParticles; i++) {
				var particle:Ball=particles[i];
				particle.x+=particle.vx;
				particle.y+=particle.vy;
				if (particle.x>stage.stageWidth) {

					particle.x=0;
				} else if (particle.x < 0) {
					particle.x=stage.stageWidth;
				}
				if (particle.y>stage.stageHeight) {
					particle.y=0;
				} else if (particle.y < 0) {
					particle.y=stage.stageHeight;
				}
			}
			for (i=0; i < numParticles - 1; i++) {
				var partA:Ball=particles[i];
				for (var j:uint = i + 1; j < numParticles; j++) {
					var partB:Ball=particles[j];
					spring(partA, partB);
				}
			}
		}
		private function spring(partA:Ball, partB:Ball):void {
			var dx:Number=partB.x-partA.x;
			var dy:Number=partB.y-partA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<minDist) {
				graphics.lineStyle(1, 0xffffff, 1 - dist / minDist);
				graphics.moveTo(partA.x, partA.y);
				graphics.lineTo(partB.x, partB.y);
				var ax:Number=dx*springAmount;
				var ay:Number=dy*springAmount;
				partA.vx+=ax;
				partA.vy+=ay;
				partB.vx-=ax;
				partB.vy-=ay;
			}
		}
	}
}

5542838620100707145502095_640.jpg
在线演示

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

package {
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;
	import flash.events.Event;
	import flash.geom.Point;
	
	[SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
	public class NodeGardenGrid extends Sprite {
		private var particles:Vector.<DisplayObject>;
		private var numParticles:uint=300;
		private var minDist:Number=50;
		private var springAmount:Number=.001;
		private var grid:CollisionGrid;
		
		public function NodeGardenGrid() {
			init();
		}
		
		private function init():void {
			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;
			grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,52);
			particles = new Vector.<DisplayObject>();
			for (var i:uint = 0; i < numParticles; i++) {
				var particle:Ball=new Ball(2,0x00ff00,false);
				particle.x=Math.random()*stage.stageWidth;
				particle.y=Math.random()*stage.stageHeight;
				particle.vx=Math.random()*6-3;
				particle.vy=Math.random()*6-3;
				addChild(particle);
				particles.push(particle);
			}
			addEventListener(Event.ENTER_FRAME, onEnterFrame);

			var fps:FPSshow = new FPSshow();
			addChild(fps);
		}
		
		private function onEnterFrame(event:Event):void {
			graphics.clear();
			for (var i:uint = 0; i < numParticles; i++) {
				var particle:Ball=particles[i] as Ball;
				particle.x+=particle.vx;
				particle.y+=particle.vy;
				if (particle.x>stage.stageWidth) {
					particle.x=0;
				} else if (particle.x < 0) {
					particle.x=stage.stageWidth;
				}
				if (particle.y>stage.stageHeight) {
					particle.y=0;
				} else if (particle.y < 0) {
					particle.y=stage.stageHeight;
				}
			}
			grid.assign(particles);
			var checks:Vector.<DisplayObject>=grid.checks;
			trace(checks.length);
			var numChecks:int=checks.length;
			for (i=0; i < numChecks; i += 2) {
				var partA:Ball=checks[i] as Ball;
				var partB:Ball=checks[i+1] as Ball;
				spring(partA, partB);
			}
		}
		
		private function spring(partA:Ball, partB:Ball):void {
			var dx:Number=partB.x-partA.x;
			var dy:Number=partB.y-partA.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<minDist) {
				graphics.lineStyle(1, 0x00ff00, 1 - dist / minDist);
				graphics.moveTo(partA.x, partA.y);
				graphics.lineTo(partB.x, partB.y);
				var ax:Number=dx*springAmount;
				var ay:Number=dy*springAmount;
				partA.vx+=ax;
				partA.vy+=ay;
				partB.vx-=ax;
				partB.vy-=ay;
			}
		}
	}
}

5542838620100707145611063_640.jpg
在线演示

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

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

as3.0 动画编程 第一部分AtionSript动画基础 第1章基本动画概念 1.1 什么是动画 1.2帧和运动 1.2.1帧就是记录 1.2.2程序帧 1.3动态与静态 1.4小结 第2章AtionSript3.0动画基础 2.1动画基础 2.2关于AtionSript版本 2.3类和OOP 2.3.1基类 2.3.2包 2.3.3导入 2.3.4构造函数 2.3.5继承 2.3.6Movielip/Sprite子类 2.3.7创建文档类 2.4设置AtionSript3.0应用程序 2.4.1使用FlashS3IDE 2.4.2使用FlexBuilder 2.4.3使用免费的命令行编译器 2.4.4关于跟踪 2.4.5缩放影片 2.5使用代码动画 2.5.1循环 2.5.2帧循环 2.5.3剪辑事件 2.5.4事件和事件处理器 2.5.5侦听器和处理器 2.5.6动画事件 2.6显示列表 2.7用户交互 2.7.1鼠标事件 2.7.2鼠标位置 2.7.3键盘事件 2.7.4键盘代码 2.8小结 第3动画中的三角学 3.1什么是三角学 3.2角 3.2.1弧度和度 3.2.2Flash坐标系统 3.2.3三角形的边 3.3三角函数 3.3.1正弦 3.3.2余弦 3 3.3.3正切 3.3.4反正弦和反余弦 3.3.5反正切 3.4旋转 3.5波 3.5.1光滑的上下运动 3.5.2线性垂直运动 3.5.3脉冲动画 3.5.4两个角的波 3.5.5用drawingAPI绘制波 3.6圆和椭圆 3.6.1圆形运动 3.6.2椭圆形运动 3.7毕达哥拉斯定理 3.8两点之间的距离 3.9本章重点公式 3.10小结 第4章渲染技术 4.1Flash中的颜色 4.1.1使用16进制颜色值 4.1.2透明度和32位色 4.1.3新的数值类型:int和uint 4.1.4组合颜色 4.1.5提取组成色 4.2drawingAPI 4.2.1图形对象 4.2.2使用lear移除绘画 4.2.3使用lineStyle设定线条样式 4.2.4使用lineTo和moveTo绘制线条 4.2.5使用urveTo绘制曲线 4.2.6使用beginFill和endFill创建图形 4.2.7使用beginGradientFill创建渐变填充 4.3颜色变换 4.4滤镜 4.4.1创建滤镜 4.4.2动画滤镜4 4.5位图 4.6载入或嵌入内容 4.6.1载入内容 4.6.2嵌入内容 4.7本章重点公式 4.8小结 第二部分 基本运动 第5章速度和加速度 5.1速度 5.1.1向量和速度 5.1.2一个轴上的速度 5.1.3两个轴上的速度 5.1.4角速度 4 5.1.5速度扩展 5.2加速度 5.2.1一个轴上的加速度 5.2.2两个轴上的加速度 5.2.3重力加速度 5.2.4角加速度 5.2.5太空船 5.3本章重点公式 5.4小结 第6章边界和摩擦力 6.1环境边界 6.1.1设置边界 6.1.2移除物体 6.1.3重新生成物体 6.1.4屏幕折回 6.1.5回弹 6.2摩擦力 6.2.1摩擦力,115正确方法 6.2.2摩擦力,116容易方法 6.2.3摩擦力的应用 6.3本章重点公式 6.4小结 第7章用户交互:移动物体 7.1按下和放开精灵 7.2拖动精灵 7.2.1使用mouseMove拖动 7.2.2使用startDrag/stopDrag拖动 7.2.3拖动与运动代码结合 7.3抛 7.4小结 第三部分 高级运动 第8章缓动和弹性 8.1比例运动 8.2缓动 8.2.1简单的缓动 8.2.2何时停止缓动 8.2.3移动的目标 8.2.4缓动不只是应用于运动 8.2.5高级缓动 8.3弹性 8.3.1一维弹性 8.3.2二维弹性 8.3.3移动目标点的弹性 8.3.4弹性在哪儿 8.3.5弹性链 8.3.6多目标点弹性 8.3.7目标偏移 8.3.8使用弹性贴加多个物体 8.4本章重点公式 5 8.5小结 第9章碰撞检测 9.1碰撞检测方法 9.2hitTestObjet和hitTestPoint1 529.2.1碰撞测试两个精灵 9.2.2碰撞测试一个精灵和一个点 9.2.3使用shapeFlag的碰撞测试 9.2.4hitTest总结 9.3基于距离的碰撞检测 9.3.1简单的基于距离的碰撞检测 9.3.2基于碰撞的弹性 9.4多物体碰撞检测策略 9.4.1基本的多物体碰撞检测 9.4.2多物体弹性 9.5其他的碰撞检测方法 9.6本章重点公式 9.7小结 第10章坐标旋转和角度回弹 10.1简单的坐标旋转 10.2高级坐标旋转 10.2.1旋转一个物体 10.2.2旋转多个物体 10.3沿角度回弹 10.3.1执行旋转 10.3.2优化代码 10.3.3动态化 10.3.4修复“脱离边界”问题 10.3.5多角度回弹 10.4本章重点公式 10.5小结 。。。。。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值