最近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();
}
}
}