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


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

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

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

步骤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 Name Purpose of Organisation
Main.as Class to create the balls visually and to attach animation to events.
Vector2D Class that holds all vector manipulation functions.
Ball Class that contains functions to visually generate a ball, implements dynamics and kinematics of a ball.
Math2 Static 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审阅了这篇文章,也期待各位读者的评论。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章