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審閱了這篇文章,也期待各位讀者的評論。

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