Cocos2D-HTML5 游戏中的 Box2DWeb

简介

这篇文章的目的是使开发人员熟悉 Box2dWeb,并描述如何在 Tizen 应用程序中使用它。 这些主题都通过一个示例应用程序进行描述。 这篇文章是关于 Cocos2D-HTML5 游戏的一系列文章(如何通过 Cocos2d-html5 使用砖块地图编辑器)的延续。

文章以一个名为“Run Snail Run”的示例游戏演示物理和图形库的使用。 这是之前文章所用到的应用程序的新版本,添加了基于 Box2dWeb 的碰撞检测。

Box2dWeb

Box2d 是 2d 游戏的一个物理引擎。 它是根据 zlib 许可证发布的开放源代码库。 它已经被移植到许多不同的编程语言和环境中,包括 Java、Adobe Flash、C# 和 JavaScript。
在此示例应用程序中,我们使用两个连接至 JavaScript 的主要 Box2d 端口之一: Box2dWeb (http://code.google.com/p/box2dweb/)。 它比另外一个主要端口 — Box2dJS 更新 (http://box2d-js.sourceforge.net/)。 它还可以被存储在一个文件中,这是另一个优势。
虽然没有专门针对 Box2dWeb 的文件,但您可以使用 Box2dFlash 文件: http://www.box2dflash.org/docs/2.1a/reference/。 Box2dWeb 从 Box2dFlash 编译,并且 JavaScript 1.6 和 ActionScript 2.0 两者都基于相同的 ECMAScript 标准,所以这两个库的组织结构非常相似。 还有一个非常有用和全面的 Box2D 教程,位于 http://www.box2d.org/manual.html

Box2dWeb 是一个物理引擎。 在我们示例应用程序中的所有图形都使用画布创建。 该库模拟刚体运动和相互作用。 可以由多边形、圆形和边缘形组成物体。 您可以将重力、摩擦力和恢复力等力量应用于这些物体。

添加 Box2dWeb 到 Tizen 应用程序

添加 Box2dWeb 到 Tizen 应用程序非常容易。 您所要做的只是将库文件导入到您的项目并在 index.html 文件中声明它:

<script type="text/javascript" src="./js/lib/external/Box2dWeb-2.1.a.3.min.js">

您现在可以从您的 JavaScript 文件访问 Box2dWeb 函数。

创建一个 Box2dWeb 世界

第一步是创建一个 Box2dWeb 世界对象。 它负责管理内存、对象和模拟。 在我们的示例应用程序中,我们在一个独立的游戏模块中创建和管理 Box2dWeb 世界。

var world = new b2World(new b2Vec2(0, 0) // gravity
, true // allow sleep
);

构造函数采用两个参数:重力和 allowSleep。 在 RunSnailRun 游戏中,我们只使用 Box2dWeb 进行碰撞检测。 物体运动由游戏逻辑控制,因此我们将重力矢量设置为零。 第二个参数 allowSleep 决定是否让物体在休息的时候进入睡眠状态。 它可以防止过多的 CPU 开销。

创建物体

Box2dWeb 物体是库所使用的基本对象。 您可以定义它们的位置、形状和其他如摩擦力或密度等参数。

要创建一个物体,您必须遵循以下三个步骤:
1. 建立物体定义
对于蜗牛和刺猬,我们使用动态物体:

var bodyDef = new Box2D.Dynamics.b2BodyDef;
bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;

2. 定义固定装置
夹具为物体赋予形状,并添加材料属性,如密度、摩擦力和恢复力。 一个夹具具有一个单一的形状。 一个物体可以有很多夹具。

下面的代码示例显示了为刺猬创建夹具。 刺猬是圆的,所以我们可以使用一个圆形。

//create fixture object
var fixDef = new Box2D.Dynamics.b2FixtureDef;

//define basic parameters
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
fixDef.isSensor = true;

// canvas width and height in meters
var height = document.documentElement.clientHeight / game.config.box2dScale;
var width = document.documentElement.clientWidth / game.config.box2dScale;

//define shape
fixDef.shape = new Box2D.Collision.Shapes.b2CircleShape(height / 45);

形状的尺寸与画布的宽度和高度相对。 请注意夹具定义中的 isSensor 参数。 True 值意味着即使各物体不会发生碰撞和改变方向,我们仍然在它们重叠时收到通知。

3. 将夹具绑定到物体,并将物体添加到世界
最后,您必须设置物体的位置,将夹具绑定到物体,并将物体添加到世界:

bodyDef.position.x = width / 2;
bodyDef.position.y = -height / 2;
this.hedgehogBody = game.getBox2dWorld().CreateBody(bodyDef);
this.hedgehogBody.CreateFixture(fixDef);

创建自定义形状

有时默认矩形和圆形形状不能精确地定义游戏对象。 刺猬对象几乎完全是圆的,但创建蜗牛的形状就不那么容易了。 在 Box2dWeb 中,您可以定义自己的多边形形状。 它们只能是凸多边形。 这意味着:
- 每个内角均小于或等于 180 度
- 每两个顶点之间的线段都保持在多边形的内部或边界上。
要创建一个多边形,您必须提供其坐标。 要找到顶点表,您可以使用工具。 有一个流行的应用程序 VertexHelper Pro (http://itunes.apple.com/us/app/vertexhelper-pro/id411684411?mt=12),但它不是免费的并且仅可用于 Mac。 但您可以使用像 Andengine Vertex Helper (http://www.andengine.org/forums/features/vertex-helper-t1370.html) 这样的社区创建工具之一。 要使创建顶点表更容易,您可以将默认模式矢量更改为,例如:%+.5f、%+.5f。

Andengine vertex helperAndengine Vertex Helper

一旦您找到正确的坐标,您可以创建 b2Vec2 对象的一个矢量。 您可以使用 ptmRatio(像素米比)将坐标的值转换到 Box2dWorld。 然后可以创建一个 b2Polygon 形状,如下面的代码示例所示。

var ptmRatio = height / 22;// scale snail shape to graphics size on the screen

var v = [ [ -0.537508 * ptmRatio, -0.40000 * ptmRatio ], [ 0.35833 * ptmRatio, -0.36250 * ptmRatio ], [ 0.52500 * ptmRatio, 0.37083 * ptmRatio ],
        [ -0.42917 * ptmRatio, 0.37500 * ptmRatio ] ];// vector defining shape of the snail, coordinates determined using Andengine Vertex Helper tool

var vecs = [];
for ( var j = 0; j < v.length; j++) {
    var tmp = new Box2D.Common.Math.b2Vec2();
    dd.Set(v[j][0], v[j][1]);
    vecs[j] = tmp;
}

fixDef.shape = new Box2D.Collision.Shapes.b2PolygonShape;
fixDef.shape.SetAsArray(vecs, vecs.length);

存储附加数据

Box2dWeb 物体可以存储一些额外的用户数据。 在示例应用程序中,我们使用它来保存对象类型(蜗牛或刺猬)的信息。

this.hedgehogBody = game.getBox2dWorld().CreateBody(bodyDef);
this.hedgehogBody.SetUserData("hedgehog");

要获取用户数据,您可以使用 GetUserData() 函数。

this.hedgehogBody.GetUserData(); //returns "hedgehog"

设置调试绘制

Box2dWeb 不提供任何用于显示物体的功能。 它负责计算物体的位置和相互作用。 在 RunSnailRun 游戏中,对象的位置由游戏逻辑计算,Box2dWeb 仅用于碰撞检测。

调试模式调试模式

它还有一个调试模式,在该模式下,您可以看到透明的 Box2dWeb 物体。 这对实现调试目的非常有用,因为您可以检查所有对象的外观和行为是否符合预期效果。 在此示例应用程序中,调试模式可以通过 game.js 文件中的 config.box2dDebug 值进行开启/关闭切换。 我们建议在模拟器上使用调试模式,因为在那里可以更快地渲染 Box2dWeb 的调试数据。

RunSnailRun 使用 WebGL 来呈现图形。 要显示调试数据,我们使用一个单独的画布,覆盖整个游戏板。 对于 Box2dWeb,它必须在 2d 环境中使用。

<canvas id="box2d" width="1260" height="660">

Box2dWeb 调试模式不使用笛卡尔坐标系统。 x 轴的值向右表示增加,而 y 轴的值向下表示增加。 所以我们必须将 Cocos2d 笛卡尔坐标转化为 Box2dWeb 世界。 我们将在这篇文章的“模拟世界”部分对其进行详细讲解。

设置调试模式参数的所有操作都在游戏模块中进行。 可以通过 setBox2dDebug() 函数从级别模块中访问它们。
setBox2dDebug : function() {
    if (this.config.box2dDebug) {
        var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
        var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite(document.getElementById("box2d").getContext("2d"));
        debugDraw.SetDrawScale(this.config.box2dScale);
        debugDraw.SetFillAlpha(0.8);
        debugDraw.SetLineThickness(1.0);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
        world.SetDebugDraw(debugDraw);
    }
}

请注意上面示例代码中的缩放值。 它在 config.box2dScale 中被设置(在本例中,该值为 30)。 Box2dWeb 使用其自己的单位系统。 在呈现图形时,您需要将它转换为像素坐标。 缩放值 30 表示 1 米= 30 像素。
调试信息通过更新函数和 world.DrawDebugData() 函数在画布上呈现(参见这篇文章的“模拟世界”部分)。

碰撞检测

在此示例应用程序中使用 Box2dWeb 的主要目的是进行精确的碰撞检测。 RunSnailRun 的基本版本使用了相交矩形的更快解决方案(参阅“Tizen 应用程序中的 Cocos2D-HTML5 游戏框架:后续”)。 使用 b2PolygonShape 让开发人员能够定义精确的形状边界。
Box2dWeb 提供侦听器进行接触检测:Dynamics.b2ContactListener。 它有四个功能:
- 开始接触事件
- 结束接触事件
- 预先解决事件
- 事后解决事件
要设置此接触侦听器,使用 SetContactListener 函数。

// Add listeners for contact
var listener = new Box2D.Dynamics.b2ContactListener;
var that = this;

listener.BeginContact = function(contact) {

    if ((contact.GetFixtureA().GetBody().GetUserData() == 'hedgehog' && contact.GetFixtureB().GetBody().GetUserData() == 'snail')
            || (contact.GetFixtureA().GetBody().GetUserData() == 'snail' && contact.GetFixtureB().GetBody().GetUserData() == 'hedgehog')) {

        for ( var i = 0; i < that.snailsBodies.length; i++) {
            // check if it is a snail-hedgehog collision (if yes, remove the snail)
            if (that.snailsBodies[i] === contact.GetFixtureA().GetBody() || that.snailsBodies[i] === contact.GetFixtureB().GetBody()) {
                that.snailToRemove = i; // mark snail to remove
            }
        }
    }
}

// set contact listener to the world
game.getBox2dWorld().SetContactListener(listener);

在示例应用程序中,仅使用了开始接触事件。 它获取一个 b2Contact 对象作为事件处理程序函数参数。 此对象包含两个碰撞夹具:A 和 B。我们所要做的第一件事情是确定它是否是刺猬与蜗牛碰撞(不是蜗牛与蜗牛碰撞)。 然后,我们发现参与碰撞的蜗牛。 我们不能从侦听器内部删除 Box2d 物体,因为传递给它的物体已被锁定。 所以我们存储我们想要在 this.snailForRemoval 变量中删除的蜗牛索引。 通过更新函数定期对其进行检查。

update : function(dt) {
    ...
    if (this.snailToRemove != null) {
        this.removeSnail(this.snailToRemove);
        this.snailToRemove = null;
    }
},

如果它的值不是 null,则使用 removeSnail 函数删除蜗牛。

removeSnail : function(index) {
    this.removeChild(this.snails[index]);
    this.numberOfSnails--;
    this.snails.splice(index, 1);
    game.getBox2dWorld().DestroyBody(this.snailsBodies[index]);
    this.removeChild(this.snailsBodies[index]);
    this.snailsBodies.splice(index, 1);
    if (this.numberOfSnails === 0) {
         ...
        // end game level
    }
},

删除蜗牛的操作方式与没有 Box2d 的应用程序的第一个版本完全相同。 我们还必须摧毁蜗牛的身体。

模拟世界

为了适当进行碰撞检测,我们需要按固定的时间间隔更新物体位置。 应用程序通常使用 Box2dWeb 物理世界确定对象应如何移动(请参阅在 Tizen 自定义 3D 图形的文章)。 在 RunSnailRun 中,它是通过游戏逻辑来完成。 我们使用 setPosition 函数将计算出的位置设置为 Box2dWeb 物体。 它采用 b2Vec2 对象作为参数。

正如在这篇文章的“设置调试绘制"部分中所提到的,Box2dWeb 使用与 Cocos2d 不同的坐标系统,所以我们必须转换坐标。 所有像素值都被 Box2dWeb 的缩放值 game.config.box2dScale 分割。 输出值以米为单位。 此外,我们必须更改垂直位置值。 在 Box2dWeb 中,y 轴向下表示增加,而在 Cocox2d 中,y 轴向上表示增加。 计算方式如以下代码片段所示。

// set new box2dweb bodies positions
this.hedgehogBody.SetPosition(new Box2D.Common.Math.b2Vec2(this.hedgehog.getX() / game.config.box2dScale, game.config.heightInMeters
        - (this.hedgehog.getY() / game.config.box2dScale)));

for ( var i = 0; i < this.snails.length; i++) {
    this.snailsBodies[i].SetPosition(new Box2D.Common.Math.b2Vec2(this.snails[i].getX() / game.config.box2dScale, game.config.heightInMeters
            - (this.snails[i].getY() / game.config.box2dScale)));
}

Box2dWeb 以一个恒定的频率刷新世界参数。 您可以在 world.Step 函数中进行设置。

函数语法:world.Step(timeStep、velocityIterations、positionIterations);

它采用两个附加参数:位置迭代的速度和数量。

world.Step(1 / 60, 10, 10);

上述数值来自随库发布的演示。 您可以选择您自己的参数。 这些值越高,Box2dWeb 计算的结果就越精确(以性能为代价)。 要了解更多信息,请参阅 Box2d 手册或在 Tizen 自定义 3D 图形的文章。 在 RunSnailRun 游戏中,这些函数并不重要,因为我们不使用 Box2dWeb 来模拟物体运动。

如果我们处于调试模式,我们会在世界模拟的每个迭代中提取调试数据:

world.DrawDebugData();

world.Step 和 world.DrawDebugData 函数都被放置在单一游戏模块的单一函数中:updateBox2dWeb。 它以固定的时间间隔从级别模块中被调用:

window.setInterval(this.updateBox2d, 1000 / 120, game.getBox2dWorld());

清洁

每个级别完成后,您必须清洁 Box2dWeb 世界。 您可以通过在刺猬身上及每个剩余的蜗牛身上使用单个 DestroyBody 函数来完成此操作。

cleanGameObjects : function() {
    for ( var i = 0; i < this.snails.length; i++) {
         ...
        game.getBox2dWorld().DestroyBody(this.snailsBodies[i]);
        this.removeChild(this.snailsBodies[i]);
         ...
    }
    game.getBox2dWorld().DestroyBody(this.hedgehogBody);
},

总结

这是关于使用 Cocos2d-html5 开发游戏的系列文章的最后一部分。 本文展示了如何在 Tizen 应用程序中使用 Box2dWeb。 并讲述了 Box2dWeb 基础知识,及其侧重于碰撞检测的用途。 示例应用程序和 RunSnailRun 游戏对所讨论的话题进行了演示。

文件附件: