DFdou's Blog Life is short,Be yourself.

7kankan11bookekzwС˵Ҫ · uawx鶼Ķ ðƴӢ ޴½
209/092

AS3-多物体碰撞万有引力+连线+拖拽版

Demo如下,本文内容部分来自《Foundation Actionscript 3.0 Animation: Making Things Move!》:



基本上这次的内容就是前边几次的综合,不过加上了连线的部分。
直接上代码:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	public class NodesMass extends Sprite {
		private var particles:Array;
		//粒子数量
		private var numParticles:uint = 15;
		//两球间的画线距离设置,这里是整个Flash的宽度都画线
		private var minDist:Number = stage.stageWidth;
		//碰到墙壁后的反弹参数,负数是表示反向,正常情况下应该是小于0但是大于-1的值,设置成正数会有很有趣的效果出现
		private var bounce:Number = -0.7;
		//模拟中立的一个值,值越大吸引力越大
		private var springAmount:Number = 0.001;
		//画线用的容器
		private var spLine:Sprite;
		//当前拖动的Ball实例
		private var currentParticles:Ball;
		//当前拖动的Ball实例x位置
		private var oldX:Number;
		//当前拖动的Ball实例y位置
		private var oldY:Number;
		public function NodesMass () {
			init ();
		}
		private function init ():void {
			spLine=new Sprite();
			addChild(spLine);
			particles = new Array  ;
			for (var i:uint = 0; i < numParticles; i++) {
				var size:Number = Math.random() * 5 + 10;
				var color:uint= Math.random() * 0xffffff;
				var particle:Ball = new Ball(size,color);
				particle.x = Math.random() * stage.stageWidth;
				particle.y = Math.random() * stage.stageHeight;
				particle.vx = Math.random() * 2 - 1;
				particle.vy = Math.random() * 2 - 1;
				particle.mass = size;
				addChild (particle);
				particle.addEventListener (MouseEvent.MOUSE_DOWN,onMouseDownHandler);
				particles.push (particle);
			}
			addEventListener (Event.ENTER_FRAME,onEnterFrame);
		}
		private function onMouseDownHandler (_e:MouseEvent):void {
			currentParticles = _e.currentTarget as Ball;
			currentParticles.vx = 0;
			currentParticles.vy = 0;
			currentParticles.alpha = 0.5;
			stage.addEventListener (MouseEvent.MOUSE_UP,onMouseUpHandler);
			currentParticles.startDrag ();
			removeEventListener (Event.ENTER_FRAME, onEnterFrame);
			addEventListener (Event.ENTER_FRAME, trackVelocity);
		}
		//计算抛掷速度向量
		private function trackVelocity (_e:Event):void {
			currentParticles.vx=currentParticles.x-oldX;
			currentParticles.vy=currentParticles.y-oldY;
			oldX=currentParticles.x;
			oldY=currentParticles.y;
		}
		private function onMouseUpHandler (_e:MouseEvent):void {
			stage.removeEventListener (MouseEvent.MOUSE_UP,onMouseUpHandler);
			currentParticles.stopDrag ();
			currentParticles.alpha=1;
			removeEventListener (Event.ENTER_FRAME, trackVelocity);
			addEventListener (Event.ENTER_FRAME, onEnterFrame);
		}
		private function onEnterFrame (event:Event):void {
			for (var i:uint = 0; i < numParticles; i++) {
				var particle:Ball = particles[i];
				particle.x += particle.vx;
				particle.y+=particle.vy;
				checkWalls (particle);
			}
			//每次更新线条之前先clear掉原来的线条
			spLine.graphics.clear();
			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];
					checkCollision (partA, partB);
					spring (partA,partB);
				}
			}
		}
		private function checkWalls (ball:Ball):void {
			if (ball.x+ball.radius>stage.stageWidth) {
				ball.x=stage.stageWidth-ball.radius;
				ball.vx*=bounce;
			} else if (ball.x - ball.radius < 0) {
				ball.x=ball.radius;
				ball.vx*=bounce;
			}
			if (ball.y+ball.radius>stage.stageHeight) {
				ball.y=stage.stageHeight-ball.radius;
				ball.vy*=bounce;
			} else if (ball.y - ball.radius < 0) {
				ball.y=ball.radius;
				ball.vy*=bounce;
			}
		}
		private function spring (partA:Ball,partB:Ball):void {
			var dx:Number=partB.x-partA.x;
			var dy:Number=partB.y-partA.y;
			var distSQ:Number = dx*dx+dy*dy;
			var dist:Number=Math.sqrt(distSQ);
			var force:Number = partA.mass * partB.mass / distSQ;
			if (dist<minDist) {
				spLine.graphics.lineStyle (1,0xffffff,1-dist/minDist);
				spLine.graphics.moveTo (partA.x,partA.y);
				spLine.graphics.lineTo (partB.x,partB.y);
				var ax:Number=dx*springAmount;
				var ay:Number=dy*springAmount;
				partA.vx+=ax/partA.mass;
				partA.vy+=ay/partA.mass;
				partB.vx-=ax/partB.mass;
				partB.vy-=ay/partB.mass;
			}
		}
		private function checkCollision (ball0:Ball, ball1:Ball):void {
			var dx:Number=ball1.x-ball0.x;
			var dy:Number=ball1.y-ball0.y;
			var dist:Number=Math.sqrt(dx*dx+dy*dy);
			if (dist<ball0.radius+ball1.radius) {
				// 计算角度和正余弦值
				var angle:Number=Math.atan2(dy,dx);
				var sin:Number=Math.sin(angle);
				var cos:Number=Math.cos(angle);
				// 旋转 ball0 的位置
				var pos0:Point=new Point(0,0);
				// 旋转 ball1 的速度
				var pos1:Point=rotate(dx,dy,sin,cos,true);
				// 旋转 ball0 的速度
				var vel0:Point=rotate(ball0.vx,ball0.vy,sin,cos,true);
				// 旋转 ball1 的速度
				var vel1:Point=rotate(ball1.vx,ball1.vy,sin,cos,true);
				// 碰撞的作用力
				var vxTotal:Number=vel0.x-vel1.x;
				vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass);
				vel1.x=vxTotal+vel0.x;
				// 更新位置
				var absV:Number=Math.abs(vel0.x)+Math.abs(vel1.x);
				var overlap:Number = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x);
				pos0.x+=vel0.x/absV*overlap;
				pos1.x+=vel1.x/absV*overlap;
				// 将位置旋转回来
				var pos0F:Object=rotate(pos0.x,pos0.y,sin,cos,false);
				var pos1F:Object=rotate(pos1.x,pos1.y,sin,cos,false);
				// 将位置调整为屏幕的实际位置
				ball1.x=ball0.x+pos1F.x;
				ball1.y=ball0.y+pos1F.y;
				ball0.x=ball0.x+pos0F.x;
				ball0.y=ball0.y+pos0F.y;
				// 将速度旋转回来
				var vel0F:Object=rotate(vel0.x,vel0.y,sin,cos,false);
				var vel1F:Object=rotate(vel1.x,vel1.y,sin,cos,false);
				ball0.vx=vel0F.x;
				ball0.vy=vel0F.y;
				ball1.vx=vel1F.x;
				ball1.vy=vel1F.y;
			}
		}
		private function rotate (x:Number, y:Number, sin:Number, cos:Number, reverse:Boolean):Point {
			var result:Point = new Point();
			if (reverse) {
				result.x=x*cos+y*sin;
				result.y=y*cos-x*sin;
			} else {
				result.x=x*cos-y*sin;
				result.y=y*cos+x*sin;
			}
			return result;
		}
	}
}

spring函数里边加入了万有引力,恩,顺便讲下万有引力。
实际上万有引力的物理公式是:
force = G * m1 * m2 / (distance*distance)
用AS3来实现的话,可以用下边这个函数:

function gravitate (partA:Ball,partB:Ball):void {
	var dx:Number = partB.x - partA.x;
	var dy:Number = partB.y - partA.y;
	var distSQ:Number = dx * dx + dy * dy;
	var dist:Number = Math.sqrt(distSQ);
	var force:Number = partA.mass * partB.mass / distSQ;
	var ax:Number = force * dx / dist;
	var ay:Number = force * dy / dist;
	partA.vx += ax / partA.mass;
	partA.vy+=ay/partA.mass;
	partB.vx-=ax/partB.mass;
	partB.vy-=ay/partB.mass;
}

还有个简单版的:

function gravitate (partA:Ball,partB:Ball):void {
	var ax:Number = dx * springAmount;
	var ay:Number = dy * springAmount;
	partA.vx += ax / partA.mass;
	partA.vy+=ay/partA.mass;
	partB.vx-=ax/partB.mass;
	partB.vy-=ay/partB.mass;
}

去掉了烦琐的force计算,直接给定了一个值springAmount来代替引力。虽然不科学,不过还好,在实际应用中其实差别不大~

Some Related Posts

Comments (2) Trackbacks (0)
  1. 尝试过将球的数量设置成200,这样总的线条数应该是1+2+3+…+199,差不多是20000,FP会崩掉哈哈~效果不行。。

    [Reply]

  2. 说的很好 很经典我顶你

    [Reply]


Leave a comment

:zhuang :xizao :wuwu :wenhao :wc :touxiang :shuaya :shuajian :shengtian :paopao :no :meinv :maonv :loveu :leilei :kua :jrjr :dahan :chi :chaocai :byebye


CommentLuv Enabled

No trackbacks yet.