Cocos2D-HTML5 游戏中的 Box2DWeb
PUBLISHED
简介
这篇文章的目的是使开发人员熟悉 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 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 : 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 游戏对所讨论的话题进行了演示。