在Egret中使用Box2D

昨天我们一起认识了Egret白鹭引擎,一个源自于ActionScript的HTML5游戏引擎,可以让有ActionScript经验的开发者轻松制作出HTML5游戏。作为Box2D的忠实粉丝,拉登大叔想到的第一件事,就是将Egret和Box 2D结合起来,开发物理游戏,好啦,闲话少说我们马上开始。

egretandBox2d

Typescript版的Box2D

因为Egret是基于Typescript语言的,所以为了实现与Egret的完美结合,我们需要使用Typescript版本的Box2D,这一点并不难,在github上搜索Box2D可以很轻松的找到TS版的Box2Dweb,地址如下:

https://github.com/borisyankov/DefinitelyTyped/tree/master/box2d

它是由ActionScript版Box2D 2.1转译过来的,因此在使用起来可以完全参考本站中的Box2D教程。接下来是将TS版本的Box2D导入到你的Egret项目中啦。(先不要下载,后面我会进行说明)

导入Box2D

我们知道,在ActionScript中通过设置项目属性中,可以添加外部类库的引用。在Egret中也是类似的,但是因为Egret项目目前没有自己的IDE,无法通过可视化窗口设置对类库的引用,只能通过项目文件夹下的egretProperties.json文件来设置。具体可以参考官方文档:Egret 与第三方 TypeScript / JavaScript 库集成

http://docs.egret-labs.org/post/manual/threelibs/uselibs.html

不过说实话,官方的文档有不少坑的,还是看我的图文教程吧。

大体过程是这样的:

  • 将类库文件粘贴到项目中,Egret官方文档中推荐放置在src文件夹下,但我个人总不习惯将类库放在src源代码目录下,所有自己进件了一个myLibs的文件夹(不要放在libs文件夹下,有可能会导致编译错误)。这里的类库文件包括两个,一个是原生的js文件,另一个是以d.ts结尾的,对原生js文件API进行声明的.ts文件。另外还有一个.json的类库配置文件。如下图所示:

3rhParty-01

  • 为了Egret项目可以正确的引用第3放类库,项目配置文件B和类库配置文件A中的内容,需要遵循一定的规则,具体如下图所示:

3rhParty

  • 类库配置文件A.json文件中,各个属性说明如下:
    • name:编译后类库文件将保存到libs文件夹的以该属性值命名的Box2dFolder子文件夹下。
    • dependence:需要引用的类库。这里保持不变。
    • source:源文件存放的目录,“.”表示当前目录。
    • file_list:此模块包括的所有代码,需要包括全部 js 文件、ts 文件以及js文件所对应的.d.ts文件
  • 按照上图编辑好项目配置文件egretProperties.json文件和类库配置文件A.json,在命令提示符中输入 egret build -e,编译项目类库引用。

编译完成后,我们就可以基于Egret编写Box2D物理应用啦。

需要特别说明的是,JS版Box2DWeb中的SetSprite调试试图是一个CanvasRenderingContext2D对象,这是canvas对象的一个属性,如以下代码所示:

debugDraw.SetSprite(document.GetElementsByTagName("canvas")[0].getContext("2d"));

而Egret本身也是基于canvas进行渲染的,这样会导致Box2D调试试图会覆盖Egret的渲染内容。拉登大叔基于Egret的Sprite对象,修改了JS版Box2DWeb中的调试试图的渲染方法,我们可以像ActionScript版本Box2Dweb一样,直接用Sprite对象来渲染调试试图,修改后代码如下:

        var s:egret.Sprite = new egret.Sprite();
        this.addChild(s);
        this.debug.SetSprite(s);

修改后的Box2D对应的js和ts文件,请点击下面的链接进行下载。

Box2Dweb for egret

示例说明

好了,闲话少说,我们来看一看示例。下面的示例中,舞台上会随机创建20个尺寸不一的矩形,自由下落,当他们落到地面时,会很自然的进行反弹和碰撞。点击图片查看动态效果:

egretBox2DDemo

对应的源代码如下,点击下载源文件

class Main extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private world:Box2D.Dynamics.b2World;
    private debug:Box2D.Dynamics.b2DebugDraw;
    private p2m:number = 30;
    private onAddToStage(event:egret.Event) {
        var sWidth:number = this.stage.stageWidth;
        var sHeight:number = this.stage.stageHeight;

        this.createWorld();
        this.createDebug();
        for(var i:number = 0;i<20;i++){
            this.createBox(Math.random()*300+50,Math.random()*200+50,Math.random()*30+10,Math.random()*30+10);
        }
        this.createBox(sWidth/2,sHeight,sWidth,10,true);
        this.createBox(sWidth/2,0,sWidth,10,true);
        this.createBox(0,sHeight/2,10,sHeight,true);
        this.createBox(sWidth,sHeight/2,10,sHeight,true);

        this.addEventListener(egret.Event.ENTER_FRAME,this.loop,this);
    }
    private createBox(posX:number,posY:number,w:number,h:number,isStatic:boolean=false){
        var bodyDef:Box2D.Dynamics.b2BodyDef = new Box2D.Dynamics.b2BodyDef();
        bodyDef.position = new Box2D.Common.Math.b2Vec2(posX/this.p2m,posY/this.p2m);
        bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
        if(isStatic) {
            bodyDef.type = Box2D.Dynamics.b2Body.b2_staticBody;
        }
        var body:Box2D.Dynamics.b2Body = this.world.CreateBody(bodyDef);

        var poly:Box2D.Collision.Shapes.b2PolygonShape;
        poly = Box2D.Collision.Shapes.b2PolygonShape.AsBox(w/this.p2m,h/this.p2m);
        var fixtureDef:Box2D.Dynamics.b2FixtureDef = new Box2D.Dynamics.b2FixtureDef();
        fixtureDef.density = 3;
        fixtureDef.restitution = 0.2;
        fixtureDef.shape = poly;

        body.CreateFixture(fixtureDef);
    }
    private createWorld(){
        var gravity:Box2D.Common.Math.b2Vec2 = new Box2D.Common.Math.b2Vec2(0,10);
        this.world = new Box2D.Dynamics.b2World(gravity,true);
    }
    private createDebug(){
        var s:egret.Sprite = new egret.Sprite();
        this.addChild(s);

        this.debug = new Box2D.Dynamics.b2DebugDraw();
        this.debug.SetSprite(s);
        this.debug.SetDrawScale(30);
        this.debug.SetLineThickness(1);
        this.debug.SetAlpha(0.8);
        this.debug.SetFillAlpha(0.5);
        this.debug.SetFlags(Box2D.Dynamics.b2DebugDraw.e_shapeBit);
        this.world.SetDebugDraw(this.debug);
    }
    private loop(e:egret.Event){
        this.world.Step(1/60,10,10);
        this.world.DrawDebugData();
    }
}

代码与ActionScript中类似,我就不进行过多的讲解了,如果你对Box2D还不太熟悉,请参考我们之前的教程。

http://www.ladeng6666.com/blog/category/box2d/page/5/

只是需要注意的是,在声明变量指定变量类型时,必须使用完整的包名称,如:

private world:Box2D.Dynamics.b2World;

我至今还是很不习惯。

不过,我试过在微信和手机浏览器中打开上面的示例,二维码如下:

egretBox2DRCCode

运行效果不是很流畅,所以本文也只是抛砖引玉,向大家介绍Egret与Box2D的搭配使用。同时也期待Egret官方团队能够找到更高效的物理引擎解决方案。

联系作者

公众号:拉小登 | 微博:拉登Dony | B站:拉小登Excel

8 Replies to “在Egret中使用Box2D”

  1. 拉登大神,有没有box2dweb的教程,你的书籍如果再有一本box2dweb的就好了,因为我想用H5开发游戏!

  2. 我用demo 在egret 时
    var gravity:Box2D.Common.Math.b2Vec2 = new Box2D.Common.Math.b2Vec2(0,10);
    报 Box2DBox2D is not defined 是什么原因拉登大叔

回复

您的电子邮箱地址不会被公开。 必填项已用*标注