最近Actionscript3.0出來之後,我決定搞一個物理引擎。其實早在2006年低,我就專門花了一些時間系統地學習了經典力學和相關的數學知識, 但那時我對如何編寫一個遊戲的物理引擎沒有一點思路。
Alec Cove實現了這個想法。 他爲Flash CS3和Flex寫出了一個物理引擎,而且非常易於使用。通過這個引擎你可以創建非常cool的東西。在我們開始這個教程之前,先準備下載這個引擎的代碼. 可以直接使用subversion下載源碼和api文檔, 具體的方法是: svn checkout http://ape.googlecode.com/svn/trunk/ ape-read-only。
你還可以到http://groups.google.com/group/ape-general/topics上參與APE引擎的一些討論。
我將使用FlashDevelop來創建一個APE引擎的基本場景。(譯者: 原文作者使用Flex 2)
我們將創建一個CarDemo的as文件, 其中代碼如下:
package {
import Flash.display.*;
public class CarDemo extends Sprite {
public function CarDemo() {
}
}
}
在編寫as文檔類時,我喜歡做的第一件事就是設置場景的背景顏色和幀頻。
package {
import Flash.display.*;
[SWF(backgroundColor="#115A70", frameRate="60")]
public class CarDemo extends Sprite {
public function CarDemo() {
}
}
}
下一步就是導入APE引擎包。並且設置一下舞臺的屬性。我們禁止了Flash player的自動縮放功能,並設置場景爲左上對齊。
APE引擎是一個靜態類,所以我們不需要實例化它。在下面的步驟裏,我們將初始化APE引擎,並設置它的模擬速度。如果設置的模擬速度太高,我相信它會產生精度上的損失。
下面我們將把一個容器傳遞給APEngine。一個容器是引擎的可視化部分。在以上的代碼裏,CarDemo繼承了Sprite類。然後我們把this傳遞給ape engine。如果我們的CarDemo繼承的是MovieClip,這樣做也是沒有問題的。(譯者: 因爲MovieClip本身也是繼承Sprite的)
然後我們創建一個力(massless force),並應用到加入引擎的所有不固定的物體上。這個力就是重力(gravity)。一個矢量(vector)簡單地表示一個物體對象,並通過x, y值來表示相對與舞臺的位置。 當我們增加一個力到一個矢量物體(0, 1)上,這即表示我們不希望這個矢量物體水平移動,而僅僅垂直運動1個像素。
package {
import Flash.display.*;
import org.cove.ape.*;
[SWF(backgroundColor="#115A70", frameRate="60")]
public class CarDemo extends Sprite {
public function CarDemo() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
APEngine.init(1/4);
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,1));
}
}
}
我們最後的工作是需要啓動引擎,即通過ENTER_FRAME事件調用run方法。關於這一點有很多爭論,你也可以使用基於timer來調用run方法。run方法的目的是通知APE開始計算下一幀,並渲染結果到容器上(還記得我們前面把this傳給了APE引擎了嗎?)。APE運行後,會盡可能地接近我們設置的幀頻(60fps)。
(譯者: 1. 這裏的run函數調用可以理解爲遊戲循環, 即Game Loop, 主要是flash已經內部封裝了windows消息機制。2. 這裏的ENTER_FRAME事件肯定是不準確的,它依賴與我們每一幀的處理速度。3. 這裏容器Container是as3的概念,容易混淆, as3中把它作爲一個顯示的平臺,我們可以理解爲一個frame, plane, 或者surface。當然container還實現了一些對象佈局,渲染順序等功能)
我們向APE引擎加入了很多對象,這會導致每1/60秒渲染一幀變慢。請記住: 就拿Quake 3來說,也不能在大部分機器上達到每秒60幀的速度。
package {
import Flash.display.*;
import org.cove.ape.*;
[SWF(backgroundColor="#115A70", frameRate="60")]
public class CarDemo extends Sprite {
public function CarDemo() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
APEngine.init(1/4);
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,1));
stage.addEventListener(Event.ENTER_FRAME, run);
}
private function run(e:Event): void {
APEngine.step();
APEngine.paint();
}
}
}
現在,我們編譯程序,並運行。如果你沒有得到任何錯誤,那你就已經創建了自己的物理世界了,看,這是多麼的簡單!
接下來,我們將創建3個表面,更多的牆和門。記住我們爲所有不是牆的對象都設置重力。
現在我們創建一個新的類,叫Surface.as。 這個類將作爲APE引擎擴展的一部分。APE Group類的目的是幫助我們更容易地管理這些對象。如果我們繼承了Group類,那我們就可以在一個類文件中加入所有的表面,從而使得所有的對象都在一起維護。
package outsider{
import Flash.display.*;
import org.cove.ape.*;
public class Surface extends Group {
public function Surface(collideInternal:Boolean=false) {
super(collideInternal);
}
}
}
我們下一個目標是創建一個矩形來作爲表面。矩形和圓是APE引擎的核心對象。所有加入到APE中的對象都是粒子(particle)。編碼非常簡單,你只需要設置粒子的x, y座標, 長度, 高度,旋轉弧度即可。 如果引擎返回true, 則表示已經固定了粒子。如果一個對象固定了,則它總是保持靜態的。重力(gravity)和碰撞(collision)隻影響移動對象。因爲我們繼承了Group類,這意味Surface類也可以增加粒子。
package outsider{
import Flash.display.*;
import org.cove.ape.*;
public class Surface extends Group {
public function Surface(collideInternal:Boolean=false) {
super(collideInternal);
var lineThickness:int = 1;
var lineColor:uint = 0xffffff;
var fillColor:uint = 0xffffff;
var leftWall:RectangleParticle = new RectangleParticle(10, 200, 20, 400, 0, true);
leftWall.setStyle(lineThickness, lineColor, 1, fillColor, 1);
addParticle(leftWall);
var floor:RectangleParticle = new RectangleParticle(405, 400, 800, 20, 0, true);
floor.setStyle(lineThickness, lineColor, 1, fillColor, 1);
addParticle(floor);
var rightWall:RectangleParticle = new RectangleParticle(800, 200, 20, 400, 0, true);
rightWall.setStyle(lineThickness, lineColor, 1, fillColor, 1);
addParticle(rightWall);
}
}
}
現在,我們再回到CarDemo類,並增加Surface類到這個Demo中。這樣我們的Demo將會顯示這小表面。代碼如下:
package {
import Flash.display.*;
import org.cove.ape.*;
import outsider.*;
[SWF(backgroundColor="#115A70", frameRate="60")]
public class CarDemo extends Sprite {
public function CarDemo() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
APEngine.init(1/4);
APEngine.container = this;
APEngine.addMasslessForce(new Vector(0,1));
var surface:Surface = new Surface();
APEngine.addGroup(surface);
stage.addEventListener(Event.ENTER_FRAME, run);
}
private function run(e:Event): void {
APEngine.step();
APEngine.paint();
}
}
}