AS3 ---- 万有引力(Gravity in Action)

来源:互联网 发布:军中妖姬汤灿 知乎 编辑:程序博客网 时间:2024/05/17 00:29
原文翻译自:http://www.flashperfection.com/tutorials/Gravity-in-Action-75225.html
本文转载自天地会:http://bbs.9ria.com/thread-120250-1-1.html

研究物理动力学中的力学,是很有趣且蛮重要的,这节课主要研究力学中的运动以及变化。万有引力就是个例子:它能使卫星围绕着地球运动,并且能让我们站立在地球上。

在这节教程中,我们将要创造一个模拟自然现象,并且让其遵守自然现象的规律,试着在屏幕上玩粒子游戏。
在所有生成出来的粒子中间,有一个主要的大粒子来吸引其他的小粒子。由于这些小粒子朝着大粒子移动,用户可以单击大粒子并来回拖动它,这会使这些小粒子不断改变他们的运动轨迹。

当这些小粒子撞到大粒子的边缘线时,它们就停止运动,并且它们不会和其他粒子重叠在一起。
这篇教材的所写的构架内容,介绍了如何执行模拟,都是利用简单的物理原理来编写实现的

步骤1:万有引力公式

首先,看一看物理公式。两个物体之间相互吸引产生的吸引力,由下面的公式表述:

Formula of attractive force

F:物体(p2)作用给任意一个粒子(p1)的吸引力
G:引力常量
m1: p1的质量;
m2: p2的质量;
r: p1到p2间的距离

特别在下面做些笔记:
  1. 引力与间距之间的关系: F 是和分开粒子之间距离的平方成反比的。这意味着A和B的距离越近,那么吸引力就越大,反之亦然。如果你把距离的值乘以一倍,那么力的值就会降为它本来的值的四分之一。
  2. 引力常数的值——G,科学数值是6.67259 x 10-11 N * m2 / kg2。然而,在Flash环境下,它的值会被500所替换。
  3. 我们能把粒子的宽度和它们的质量扯上联系。在这个例子中,我已经定义了粒子的质量是它半径的一半。

步骤2:牛顿第二定律公式

为了把力转化为动力,我们需要计算出粒子的加速度。牛顿的著名公式是这么写的:
formula f=ma

F: 作用于相互物体间的引力(p2)
m: 运动对象的质量(p1);
a: 受力(F)影响的运动对象(p2)的加速度;
从这看出,一个更大的力作用在粒子A上会导致一个更快的加速度(假设质量保持不变)。这个加速度会改变粒子的速率。

步骤3: 开始工程

在FlashDevelop IDE来执行工程会更好。构建你的工程文件。
1.新建工程项目 > 新工程
2.选择窗口上方的,AS3工程
3.命名工程文件,按照我的,给它命名为Attrator
4.选择你的工程文件位置。

步骤4:你需要的类

有四个类需要创建在\src\文件夹中:Main.as,Vector2D.as,Ball.as还有Math2.as.你也可以从资源包里下载所有这些类,并且试着一步一步去了解这些类的原理,为了以后好修改这些类。
它们的作用如下: 

Class NamePurpose of OrganisationMain.asClass to create the balls visually and to attach animation to events.Vector2DClass that holds all vector manipulation functions.BallClass that contains functions to visually generate a ball, implements dynamics and kinematics of a ball.Math2Static class that holds a function to facilitate randomizing the initial location of balls.
步骤5:随机设值

让我们先说说Math2类吧。下面这个函数会生成一个随机数,在指定的范围之内。接受两个输入值,minimun和maximun来限制范围。
public static function randomiseBetween(range_min:int, range_max:int):int{ var range:int = range_max - range_min; var randomised:int = Math.random() * range + range_min; return randomised; }

步骤6:Vector2D,获取与设定

大多数要用的数学都位于Vector2D中。对读者而言,这篇写向量分析的文章会比较熟悉。下面的函数是用来设定和给予向量的分量值的,增加了一个使所有分量值重新为0的方法。
无论如何,如果你对Vector感到别扭,请看看一个很好的帖子:Daniel Sidhion写的欧几里德几何学。

public function Vector2D(valueX:Number, valueY:Number) { this._x = valueX; this._y = valueY; } public function set vecX(valueX:Number):void{ this._x = valueX; } public function get vecX():Number{ return this._x } public function set vecY(valueY:Number):void{ this._y = valueY; } public function get vecY():Number{ return this._y } public function setVector(valueX:Number, valueY:Number):void{ this._x = valueX; this._y = valueY; } public function reset():void{ this._x = 0; this._y = 0; }

步骤7: Vector2D的操作

Vector2D的主要用法,在于以下功能:
* 获取向量的大小值
* 获取向量原点位置的角度
* 获取向量的向量方向
* 执行向量的加法运算,减法运算,以及乘法运算。

public function getMagnitude():Number{ var lengthX:Number = this._x; var lengthY:Number = this._y; return Math.sqrt(lengthX * lengthX +lengthY * lengthY); }public function getAngle():Number{ var lengthX:Number = this._x; var lengthY:Number = this._y; return Math.atan2(lengthY, lengthX); }public function getVectorDirection():Vector2D { var vectorDirection:Vector2D = new Vector2D(this._x / this.getMagnitude(), this._y / this.getMagnitude()); return Vector2D(vectorDirection); }public function minusVector(vector2:Vector2D):void{ this._x -= vector2.vecX; this._y -= vector2.vecY; }public function addVector(vector2:Vector2D):void{ this._x += vector2.vecX; this._y += vector2.vecY; }public function multiply (scalar:Number):void{ this._x *= scalar; this._y *= scalar; }

步骤8:Ball.as 绘制

Ball类是所有的有趣的运算产生的地方。为了我们动画的开始,我们需要画一个小球并且设置几个与动力学(还有力学)有关的变量。绘制小球的方法如下:
private function draw(radius:Number, color:uint) :void{     graphics.beginFill(color, 1);     graphics.drawCircle(0, 0, radius);     graphics.endFill(); }

步骤9: Ball.as 局部变量

所提及的与动力学和力学有关的变量,可以开始定义了,如下:
private var _disp:Vector2D; //displacement vector, relative to the origin private var _velo:Vector2D; //velocity vector private var _acc:Vector2D;  //acceleration vector private var _attractive_coeff:Number = 500; private var _mass:Number

步骤10:Ball.as 初始化

因为Ball类的构造函数被调用了,因此图形也画好了。一旦画好,小球就会被随机摆放在舞台上。我们也能设置局部变量。所有向量的数量也会初始化设置为0,除了相对位移测量的起点。

public function Ball(radius:Number = 20, color:uint = 0x0000FF) {     this.draw(radius, color);     this._mass = radius / 2;            //assuming mass is half of radius     this.x = Math2.randomiseBetween(0, 550);     this.y = Math2.randomiseBetween(0, 400);     this._disp = new Vector2D(this.x, this.y);  //set initial displacement     this._velo = new Vector2D(0, 0);     this._acc = new Vector2D(0, 0); }

步骤11:Ball.as计算吸引力

我们需要计算出使得粒子运动的潜在的力。猜猜,为什么它是万有引力。下面的函数有助于计算力。注意那个帽子,应用了加速度的值为5.
力的水平和垂直分量,利用三角构形,得到力的大小;通过上面的动画会帮你理解这些数学知识。


public function get mass():Number{     return _mass; } private function getForceAttract (m1:Number, m2:Number, vec2Center:Vector2D):Vector2D {     /* calculate attractive force based on the following formula:     * F = K * m1 * m2 / r * r     */    var numerator:Number = this._attractive_coeff * m1 * m2;     var denominator:Number = vec2Center.getMagnitude() * vec2Center.getMagnitude();     var forceMagnitude:Number = numerator / denominator;     var forceDirection:Number = vec2Center.getAngle();       //setting a cap     if (forceMagnitude > 0) forceMagnitude = Math.min(forceMagnitude, 5);       //deriving force component, horizontal, vertical     var forceX:Number = forceMagnitude * Math.cos(forceDirection);     var forceY:Number = forceMagnitude * Math.sin(forceDirection);     var force:Vector2D = new Vector2D(forceX, forceY);     return force; }

步骤12:Ball.as 计算加速度

一旦得到向量的力,我们能计算出加速度的结果。记住,F = ma, 所以 a = F/m.

public function getAcc(vecForce:Vector2D):Vector2D{    //setting acceleration due to force    var vecAcc:Vector2D = vecForce.multiply(1 / this._mass);    return veccAcc;}

步骤13:Ball.as 计算位移

利用计算出的加速度,我们可以有效地计算出位移的结果
记住,那些计算出的力,实际上是建立在小球的各自中心点间的位移间距上的。

private function getDispTo(ball:Ball):Vector2D{    var currentVector:Vector2D = new Vector2D(ball.x, ball.y);    currentVector.minusVector(this._disp);    return currentVector;}public function attractedTo(ball:Ball) :void{    var toCenter:Vector2D = this.getDispTo(ball);    var currentForceAttract:Vector2D = this.getForceAttract(ball.mass, this._mass, toCenter);    this._acc = this.getAcc(currentForceAttract);    this._velo.addVector(this._acc);    this._disp.addVector(this._velo);}

步骤14: Ball.as 执行位移

然后,通过以下的函数,我们能把小球移动到新的位置上。注意,位移绝不会在小球当前位置上立刻执行。这样设计是为了更好处理:球之间的碰撞检测。

public function setPosition(vecDisp:Vector2D):void{    this.x = Math.round(vecDisp.vecX);    this.y = Math.round(vecDisp.vecY);}

记住,力是建立在物体各自的中心点之间的距离上的。因此,当小球间距离越来越近,引力会越来越大,它们会继续运动且渗透在一起的。
当小球边缘撞击到另一个小球边缘的时候,我们需要重新设置它们加速度和速率为0。我们需要得到一个检测两个球撞击的方法。

步骤15:Ball.as撞击检测

撞击能容易检测。任意两个单独小球之间距离不能比他们的半径的总和小。下面是撞击检测函数:
public function collisionInto (ball:Ball):Boolean{    var hit:Boolean = false;    var minDist:Number = (ball.width + this.width) / 2;     if (this.getDispTo(ball).getMagnitude() < minDist)    {        hit = true;    }     return hit;}

步骤16:Ball.as 计算位移至排斥

Calculate displacement to repel so that two overlapping balls are placed side by side without overlapping.
通常两个球之间的撞击被检测到的时候,它们的状态是重叠在一起了。我们需要确保它们坐落在边缘上并且不会相互覆盖。
怎样做呢?我们可以把球从另一个球之间距离位移一段,但是我们首先需要计算正确的位移。这里是位移的计算:

public function getRepel (ball:Ball): Vector2D{    var minDist:Number = (ball.width + this.width) / 2;    //calculate distance to repel    var toBall:Vector2D = this.getDispTo(ball);    var directToBall:Vector2D = toBall.getVectorDirection();    directToBall.multiply(minDist);    directToBall.minusVector(toBall);    directToBall.multiply( -1);    return directToBall;}

步骤17:Ball.as 执行位移到排斥

Implementing repelling displacement onto a ball
当我们计算好正确的位移之后,我们需要执行它。程序要让小球互斥,我们需要额外做两个指令。记住,我们要处理一个动力学的环境。即便我们把小球位移到小球边缘的时候,由于作用力以及碰撞造成的速率,会改变加速度,造成
不理想的里外颠簸。我们需要重新设置加速度的和速率的值为0.
public function repelledBy(ball:Ball):void{    this._acc.reset();    this._velo.reset();    var repelDisp:Vector2D = getRepel(ball);    this._disp.addVector(repelDisp);}

步骤18: Ball.as 赋予动画

最后,要想让它们互相吸引,我们要把我们的小球赋予动画(渲染)。当碰撞检测时,位移会调节到以至不会穿透边界。这会在他们用的中心点间距撞击时发生,每一个小球之间的碰撞都这样。

public function animate(center:Ball, all:Array):void{    this.attractedTo(center);    if (collisionInto(center)) this.repelledBy(center);    for (var i:int = 0; i < all.length; i++)    {        var current_ball:Ball = all[i] as Ball;        if (collisionInto(current_ball) && current_ball.name != this.name) this.repelledBy(current_ball);    }    this.setPosition(this._disp);}

步骤19:Main.as局部变量

来到我们最后一个类:Main.Main类在工程一开始就产生。局部变量包含了一个吸引其他小球的主球,以及所有球的数量,一切都在Flash展示画面中。

private var mainBall:Ball;private var totalBalls:int = 10;

步骤20:Main.as绘制小球

首先,我们应该把球初始化。这里有一个主要的大球来吸引其他的小球。把其他小球命好名称来方便于引用。

private function createBalls ():void{    mainBall = new Ball(50, 0x00FF00);    this.addChild(mainBall);    for (var i:int = 0; i < this.totalBalls; i++)    {        var currentBall:Ball = new Ball();        currentBall.name = "ball" + i;        this.addChild(currentBall);    }}

步骤21: Main.as执行小球相互作用

然后,给主要的大球分配事件侦听,使它单击鼠标的时候拖曳当释放鼠标的时候停止。

private function init(e:Event = null):void{    removeEventListener(Event.ADDED_TO_STAGE, init);    // entry point    createBalls();    mainBall.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);    stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);    animateAll();}private function onMouseUp(e:MouseEvent):void{    stopDrag();}private function onMouseDown(e:MouseEvent):void{    e.target.startDrag();}

步骤22:Main.as 小球的动画

使小球产生动画,让它们被大球吸引。把一个EnterFrame事件分配在每个小球当中。

private function animateAll():void{    for (var i:uint = 0; i < totalBalls; i++)    {        //each ball is pulled by main_ball        var current_ball:Ball = this.getChildByName("ball" + i) as Ball;        current_ball.addEventListener(Event.ENTER_FRAME, enterFrame);    }}private function enterFrame(e:Event):void{    var allObj:Array = new Array();    for (var i:int = 0; i <= totalBalls; i++)    {        var current_ball:Ball = this.getChildAt(i) as Ball;        allObj.push(current_ball);    }    e.target.animate(mainBall, allObj);}

步骤23:测试影片

最终,按下Ctrl + Enter观看动画效果吧。


结论

进一步来讲一下这个教程,读者也许通过运用线性力学来扩展了这个工程。

无论如何,模拟提供了一个传达思想的很好的手段,通过简单的图形和文本来解释这样一个复杂的物理学环境课程,特别是问题花费太多时间的时候。
我希望这小教程能给你带来一定的帮助。Terima Kasih审阅了这篇文章,也期待各位读者的评论。