2D物理引擎 Box2D for javascript Games 第二章 向世界添加刚体

2D物理引擎 Box2D for javascript Games 第二章

向世界添加刚体

有了刚体(Bodies)才能创建 Box2D 游戏。任何你可以移动的或交互 的对象都是刚体(Bodies)。

愤怒的小鸟(Angry Birds)中创建的小鸟和小猪是刚 体,同样在图腾破坏者(Totem Destroyer)中的黄金神像和图腾砖块也是刚体。

本章将带你学习创建各种类型的Box2D刚体,此外还有一些其它重要的特性,如下表所列

  • 创建圆形刚体

  • 创建矩形刚体

  • 创建任意多边形刚体

  • 使用 DebugDraw() 方法测试模拟

  • 定义刚体的类型: static, dynamic 或 kinimatic

  • 设置材质属性: 密度(density), 摩擦系数(friction) 和 恢复系数(resitution)

  • 度量单位

  • 创建合成对象

通过本章的学习,你将会创建一个你的第一个图腾破坏者类型的游戏。

本章有较多的知识点,那么我们废话少说,直接开始本章的学习吧!

你的第一个模拟—一个球落地

我们先从简单的任务开始,最简单的物理模拟: 一个球落到地面。

虽然这是一个简单小球落地的模拟但是它将是你的第一个模拟,并且易于很快实现它。

让我们看看在这次模拟中我们要做些什么:

  • 世界的重力(gravity)

  • 一个受到作用力(例如:重力(gravity))的球

  • 一个不受任何作用力的地面

  • 某种材质,正如我们希望小球在地面弹起的材质

在之前的学习中, 你已经能够配置世界的重力了, 所以我们直接从创建小球开始

  1. 无论我们是创建球形还是多边形,第一步都是创建一个刚体:
var bodyDef = new b2BodyDef();

// b2BodyDef 类是一个刚体的定义类,它将持有创建我们刚体所需要的所有数据。
  1. 现在可以将刚体添加到世界中。因为我们采用的舞台尺寸是640 x 480,我们将 把球放置在舞台的顶部的中心位置,该位置为(320,30),如下所示:

    bodyDef.position.Set(10.66,1); 
    

    通过 position 属性显示的设置了刚体在世界中的位置, 可能你会困惑为什么明明说是 (320,30) 却变成了 (10.66,1)。

    这原因要归咎到度量单位上。虽然网页中的 Canvas 是以像素 (pixels) 为度量单位,但是在 Box2D 中尝试 模拟真实的世界并采用米 (meters) 作为度量单位。

    对于米 (meters) 和像素 (pixels) 之间的转换没有通用的标准, 我们采用下面的转换标准效果还不错:

    1米 = 30像素

    定义一个变量来帮助我们将米(meters) 转换成像素 (pixels), 我们便可以在 Box2D 世界(world) 中进行操作时使用像素而不用使用米来作为度量单位。

    这样将使我们在制作 Canvas 游戏时使用像素来思考, 从而变得更加直观。

  2. 打开你在第一章中创建的 demo1-1.html,并像下面那样修改它:

<script>
  function init(){
    var b2Vec2 = Box2D.Common.Math.b2Vec2
    ,b2AABB = Box2D.Collision.b2AABB
    ,b2BodyDef = Box2D.Dynamics.b2BodyDef
    ,b2Body = Box2D.Dynamics.b2Body
    ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef
    ,b2Fixture = Box2D.Dynamics.b2Fixture
    ,b2World = Box2D.Dynamics.b2World
    ,b2MassData = Box2D.Collision.Shapes.b2MassData
    ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
    ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
    ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw
    ,b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
    ;

    
    var world;
    var worldScale = 30;
    function main(){
        world = new b2World(new b2Vec2(0, 9.81), true);
        var bodyDef = new b2BodyDef(); 
        bodyDef.position.Set(320/worldScale,30/worldScale);

        setInterval(updateWorld, 1000 / 60);
    }
    function updateWorld() {
        world.Step(1/30,10,10);
        world.ClearForces(); // 清除作用力
    }

    main();
  }
  init();
</script>

注意我是怎样创建世界和调用 step 方法的。相比之前少用了几行代码。

一旦你创建了刚体定义,那么是时候给它一个形状了。

创建一个圆形形状

形状(shape)是一个2D几何对象,例如一个圆形或者多边形在这里必须是凸多边形(每一个内角小于180度)。

记住 Box2D 只能处理凸多边形

此处,我们从小球开始, 所以我们创建一个圆形:

var circleShape =new b2CircleShape(25/worldScale);

b2CircleShape是用来创建圆形形状,并且它的构造函数需要一个半径(radius)作为 参数。

在之前的代码中,我们创建了一个圆形,它的半径为25像素(pixels),由于设置了 worldScale 变量。

从现在起,每次你想要使用像素进行操作时,你只要将它们除以 worldScale 即可。你也可以定义一个方法名为 pixelsToMeters 的方法,在每次你需要将像 素(pixels)转换成米(meters)时调用。

当我们有了刚体定义和形状时,我们将使用夹具(fixture)来将它们粘起来。

创建夹具 fixture

夹具 fixture 用于将形状绑定到刚体上,然后定义它的材质,设置密度 (density),摩擦系数(friction)以及恢复系数(restitution)。

此刻我们无需去关心材质, 让我们把注意力集中到 fixture 上:

  1. 首先,我们创建 fixture:
var fixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;

一旦我们通过构造函数创建了夹具(fixture),我们将分配之前创建 的形状给它的shape属性。

  1. 最后,我们准备将球添加到世界中:
var theBall = world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);

b2Body是刚体的实体:是物质,是通过使用 bodyDef 属性创建的具 体刚体。

  1. 再次说明一下,使用以下步骤将刚体添加到世界中:
    I 创建一个刚体定义,它将持有刚体信息,例如刚体的位置信息。

II 创建一个形状,它将决定刚体的显示形状

III. 创建一个夹具, 通过它将形状附加到刚体定义上。

IV. 使用夹具创建刚体在世界中的实体

一旦你知道了每一步的作用, 添加刚体到你的 Box2D 世界中将会很简单

回到我们的项目。现在的main函数内应该看起来和下面一样:

function main(){
  world = new b2World(new b2Vec2(0, 9.81), true);
  var bodyDef = new b2BodyDef(); 
  bodyDef.position.Set(320/worldScale,30/worldScale);
  var circleShape = new b2CircleShape(25/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = circleShape;
  fixtureDef.density = 1;
  fixtureDef.restitution = .6;
  fixtureDef.friction = .1;
  var theBall = world.CreateBody(bodyDef);
  theBall.CreateFixture(fixtureDef);

  setInterval(updateWorld, 1000 / 60);
}

定时保存项目并测试它。准备好看看你的第一个 Box2D刚体的表现? 运行它!

额…,然而你现在运行时还是看不到任何东西。。

让我告诉你原因,Box2D只负责模拟物理世界,而不负责显示任何东西。

这意味着,你的刚体正活跃在你的Box2D世界中,只是你看不到而已。

使用调试绘图测试你的模拟

幸运的是,Box2D 有一个特性,调试绘图(debug draw),它将帮助你显示出模拟的情况:

在网页中首先要添加一个canvas如

<canvas id="canvas" width="640" height="480" style="" ></canvas>
  1. 调试绘图(debug draw)将Box2D世界中发生的事情显示出来,在

updateWorld方法中,我们可以在Step()方法之后调用世界(world)的 DrawDebugData()方法:

world.DrawDebugData();
  1. 一旦我们告知世界在每次遍历之后显示调试绘图(debug draw),我们需要通 过调试绘图(debug draw)定义视觉设置。如下添加代码到你的main函数内:
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
  1. 这里有很多代码,所以让我们来解释一下发生了什么。你已经知道 DrawDebugData()方法代表什么,所以我们将解释其它行代码代表的意思:
var debugDraw = new b2DebugDraw(); 

b2DebugDraw是一个类,它支持调试绘制(debug draw)出你在游戏中的物理实体。

var debugSprite:Sprite = new Sprite();

debugSprite 被添加到显示列表(Display List), 准备显示在 canvas 上。

debugDraw.SetSprite(debugSprite);

SetSprite()方法告知 debugSprite 将要被用来显示调试的绘图 (debug draw)。

debugDraw.SetDrawScale(worldScale);

因为我们要将米(meters)转变为像素(pixels),我们需要通知调试绘图(debug draw)我们使用的换算比例。

debugDraw.SetFlags(b2DebugDraw.e_shapeBit);

SetFlag() 方法允许我们决定我们将在调试绘图 (debug draw) 中描绘的物理实体的类型。当前我们只需要绘制形状。

补充说明:

setFlag() 方法用于选择绘制 Box2D 对象的内容类型。这样可以节省 CPU 开支。setFlag() 方法有一个 16 进制的参数,这参数的取值只能是 b2DebugDraw 中定义的下面几个常量

另外,我们还可以用”或”运算符,同时使用多个Flag

debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);

debugDraw.SetFillAlpha(0.5);

SetFillAlpha()方法是为了便于美观而设置的。形状的轮廓是不透明的, 填充的颜色是半透明的。这将使得调试绘图输出更加易于理解。

world.SetDebugDraw(debugDraw);

最后,我们将指派调试绘图(Debug draw)到我们刚刚创建的世界(world)

4.现在是时候来测试一下了, 然后你应该会看到下图所示的样子:

image

就这样! 你设法看到了你放置在 Box2D 世界中的刚体。

目前,球体还无法在重力的作用下下落,但是不要担心,我们将在稍后修改它。

现在,让我们来创建一些可以作为地面的东西,例如一个放置在舞台底部边缘的大矩形。

从现在开始一切将更加简单, 作为新的刚体将会很快的自动显示在它所添加的世界中。

完整源码在 demo2-1.html 中查看

创建矩形形状

让我执行下面的步骤:

  1. 首先,刚体和夹具的定义可以重指定到我们定义的新的刚体上。这样,我们无需再去定义 bodyDef 变量,但是我们要改变原先在创建球时使用的坐 标:

    bodyDef.position.Set(320/worldScale,470/worldScale); 
    
  2. 我们将用 b2PolygonShape 类创建一个多边形:

    var polygonShape = new b2PolygonShape(); 
    

    这样,我们以之前创建圆形形状时,相同的方法创建了一个多边形形状。

  3. 多边形形状必须遵守一些限制,但是目前,因为我们只需要一个轴对称的矩 形,SetAsBox()方法便能满足我们的需要:

    polygonShape.SetAsBox(320/worldScale,10/worldScale);
    

    这个方法需要两个参数:矩形的半宽长和半高长。最后,我们的新多边形形状的中心在像素 (320,470), 它的宽度为 640 像素和高度为 20 像素——这是我们刚刚创建的地面的尺寸

  4. 现在,我们改变定义的夹具的shape属性,附加新的多边形形状: 

    fixtureDef.shape = polygonShape; 
    
  5. 最后,我们可以创建刚体并将夹具附加上去,就像我们在球形上做的那样。

    var theFloor = world.CreateBody(bodyDef); 
    theFloor.CreateFixture(fixtureDef); 
    
  6. 你的 main 方法应该向下面这样:

    function main(){
      world = new b2World(new b2Vec2(0, 9.81), true);
      var bodyDef = new b2BodyDef(); 
      bodyDef.position.Set(320/worldScale,30/worldScale);
      var circleShape = new b2CircleShape(25/worldScale);
      var fixtureDef = new b2FixtureDef();
      fixtureDef.shape = circleShape;
      fixtureDef.density = 1;
      fixtureDef.restitution = .6;
      fixtureDef.friction = .1;
      var theBall = world.CreateBody(bodyDef);
      theBall.CreateFixture(fixtureDef);
    
      // 定义矩形地面
      bodyDef.position.Set(320/worldScale, 470/worldScale); 
      bodyDef.type = b2Body.b2_staticBody;
      var polygonShape = new b2PolygonShape();
      polygonShape.SetAsBox(320/worldScale, 10/worldScale);
      fixtureDef.shape = polygonShape; // 复用夹具
    
      var theFloor = world.CreateBody(bodyDef);
      theFloor.CreateFixture(fixtureDef);
    
      //setup debug draw
      var debugDraw = new b2DebugDraw();
      debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
      debugDraw.SetDrawScale(worldScale);
      debugDraw.SetFillAlpha(0.5);
      debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
      world.SetDebugDraw(debugDraw);
    
      setInterval(updateWorld, 1000 / 60);
    }
    
  7. 测试影片,你将会看到地面:

image

完整源码在 demo2-2.html 中查看

你看是不是很简单? 我们花了将近一章半去绘制我们的第一个刚体

然后只花了很少的几行代码添加另一个刚体。

不同的刚体类型——static, dynamic 和 kinematic

有三种 Box2D 刚体的类型: staitc, dynamic 和 kinematic。

一个 static 类型的刚体不受任何力,冲量或撞击的影响并且不会移动。它只能通过 用户手动移动。默认情况下,所有的Box2D刚体都是static类型的刚体,这就是为什 么球不移动的原因。一个static类型的刚体不会和别的static或kinematic类型的刚体发 生碰撞。

一个 dynamic 类型的刚体受力,冲量,撞击以及任何世界事件的影响。它可以通过 手动移动,虽然我建议让它们通过世界的重力,和任何类型刚体的碰撞来移动。

一个 kinematic 类型的刚体是一个介于static和dynamic刚体之间的混合刚体。它不 受理的影响,但是可以通过手动和设置它们的速率来移动。它不能和static和 kinematic类型的刚体碰撞。

回到我们需要的模拟中来。哪种类型是我们要指派给球和地面的呢?

地面是static类型的刚体,它无需移动,然而球要移动因为要模拟重力, 所以是 dynamic 类型的刚体。

你只需要设置刚体定义的 type 属性就能告知 Box2D 每一个刚体的类型,属性值可以是

b2Body.b2_staticBody, b2Body.b2_dynamicBody 或 b2Body.b2_kinematicBody 分别对应 static,dynamic 或 kinematic 刚体。

为球添加上 bodyDef.type=b2Body.b2_dynamicBody;

为地面添加上 bodyDef.type=b2Body.b2_staticBody;

你的新 main 方法像下面这样:

function main(){
  world = new b2World(new b2Vec2(0, 9.81), true);
  var bodyDef = new b2BodyDef(); 
  bodyDef.position.Set(320/worldScale,30/worldScale);
  bodyDef.type = b2Body.b2_dynamicBody;
  var circleShape = new b2CircleShape(25/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = circleShape;
  var theBall = world.CreateBody(bodyDef);
  theBall.CreateFixture(fixtureDef);

  // 定义矩形地面
  bodyDef.position.Set(320/worldScale, 470/worldScale); // 复用定义刚体
  bodyDef.type = b2Body.b2_staticBody;
  var polygonShape = new b2PolygonShape();
  polygonShape.SetAsBox(320/worldScale, 10/worldScale);
  fixtureDef.shape = polygonShape; // 复用夹具

  var theFloor = world.CreateBody(bodyDef);
  theFloor.CreateFixture(fixtureDef);

  //setup debug draw
  var debugDraw = new b2DebugDraw();
  debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
  debugDraw.SetDrawScale(worldScale);
  debugDraw.SetFillAlpha(0.5);
  debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
  world.SetDebugDraw(debugDraw);

  setInterval(updateWorld, 1000 / 60);
}

image

在恭喜你运行成功你的第一个模拟之前,让我们花点时间来讲一下关于在调试绘图 (debug draw) 运行的中不同颜色的意义。

static 类型的刚体将会绘制成绿色。

dynamic 类型的刚体, 当它们没有在睡眠状态时将会绘制成红色,在睡眠状态时将会绘制成灰色。

kinematic 类型的刚体, 在之前的屏幕截图中没有显示,它将会被显示为蓝色。

现在,我们知道当刚体进入睡眠状态并节约CPU资源这个概念。

正如你所见, 当球撞击地面,没有别的作用力影响它时,球可以进入睡眠状态,直到有新的作用力发生为止。

现在,有一个新的问题。球在落地后没有弹起。如果我们想要运行一个完美的模拟, 我们需要给我们的刚体增加更多的属性。

密度, 摩擦 和 恢复

现成你已经知道怎样向世界添加刚体

我想向你介绍三种会改变刚体行为的属性: 密度,摩擦和恢复。

密度 (density) 用来设置刚体的质量,按照公斤每平方米。越高的密度意味着越重的刚体, 并且该值不能为负。

摩擦 (friction) 在两个刚体在彼此的表面上移动时产生, 它是通过一个系数来定义, 通常它的范围在 0(没有摩擦)-1(最大摩擦)之间。它不能为负数。

恢复 (restitution) 决定刚体在发生碰撞时反弹的程度。与密度 (density) 和摩擦 (friction) 一样, 它不能为负数并且它是一个介于 0-1 的系数。

一个小球在 restitution 为 0 时落向地面,不发生反弹(无弹性碰撞),反之 restitution 为 1 时小球将会以此刻撞击时相同的速率弹起(完全弹性碰撞)。

密度(density),摩擦(friction)和恢复(restitution)必须添加到夹具上,所以在main方法中添加以下几行代码:

fixtureDef.density=1;

fixtureDef.restitution=0.6;

fixtureDef.friction=0.1;

现在你的main函数内看起来应该是这样的

function main(){
    world = new b2World(new b2Vec2(0, 9.81), true);
    var bodyDef = new b2BodyDef(); 
    bodyDef.position.Set(320/worldScale,30/worldScale);
    bodyDef.type = b2Body.b2_dynamicBody;
    var circleShape = new b2CircleShape(25/worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = circleShape;
    fixtureDef.density = 1;
    fixtureDef.restitution = .6;
    fixtureDef.friction = .1;
    var theBall = world.CreateBody(bodyDef);
    theBall.CreateFixture(fixtureDef);

    // 定义矩形地面
    bodyDef.position.Set(320/worldScale, 470/worldScale); // 复用定义刚体
    bodyDef.type = b2Body.b2_staticBody;
    var polygonShape = new b2PolygonShape();
    polygonShape.SetAsBox(320/worldScale, 10/worldScale);
    fixtureDef.shape = polygonShape; // 复用夹具

    var theFloor = world.CreateBody(bodyDef);
    theFloor.CreateFixture(fixtureDef);

    //setup debug draw
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
    debugDraw.SetDrawScale(worldScale);
    debugDraw.SetFillAlpha(0.5);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    world.SetDebugDraw(debugDraw);

    setInterval(updateWorld, 1000 / 60);
}

我向夹具指派一次属性,而所有的刚体都将使用这个相同的夹具。在本书的整个

讲解过程中,我们将要处理很多夹具的属性,但是目前让我们只需要设置小球弹跳即可。

测试 demo2-3.html,你就会发现小球在弹跳

image

祝贺你!你刚刚完成了你的第一个真实的Box2D项目,那么现在你有能力去创建基础的形状和为它们分配特性和属性。

接下去让我开始来创建一个准游戏吧…

源码 demo 存放在 https://github.com/willian12345/Box2D-for-Javascript-Games

向世界添加刚体

有了刚体(Bodies)才能创建 Box2D 游戏。任何你可以移动的或交互 的对象都是刚体(Bodies)。

愤怒的小鸟(Angry Birds)中创建的小鸟和小猪是刚 体,同样在图腾破坏者(Totem Destroyer)中的黄金神像和图腾砖块也是刚体。

本章将带你学习创建各种类型的Box2D刚体,此外还有一些其它重要的特性,如下表所列

  • 创建圆形刚体

  • 创建矩形刚体

  • 创建任意多边形刚体

  • 使用 DebugDraw() 方法测试模拟

  • 定义刚体的类型: static, dynamic 或 kinimatic

  • 设置材质属性: 密度(density), 摩擦系数(friction) 和 恢复系数(resitution)

  • 度量单位

  • 创建合成对象

通过本章的学习,你将会创建一个你的第一个图腾破坏者类型的游戏。

本章有较多的知识点,那么我们废话少说,直接开始本章的学习吧!

你的第一个模拟—一个球落地

我们先从简单的任务开始,最简单的物理模拟: 一个球落到地面。

虽然这是一个简单小球落地的模拟但是它将是你的第一个模拟,并且易于很快实现它。

让我们看看在这次模拟中我们要做些什么:

  • 世界的重力(gravity)

  • 一个受到作用力(例如:重力(gravity))的球

  • 一个不受任何作用力的地面

  • 某种材质,正如我们希望小球在地面弹起的材质

在之前的学习中, 你已经能够配置世界的重力了, 所以我们直接从创建小球开始

  1. 无论我们是创建球形还是多边形,第一步都是创建一个刚体:
var bodyDef = new b2BodyDef();

// b2BodyDef 类是一个刚体的定义类,它将持有创建我们刚体所需要的所有数据。
  1. 现在可以将刚体添加到世界中。因为我们采用的舞台尺寸是640 x 480,我们将 把球放置在舞台的顶部的中心位置,该位置为(320,30),如下所示:

    bodyDef.position.Set(10.66,1); 
    

    通过 position 属性显示的设置了刚体在世界中的位置, 可能你会困惑为什么明明说是 (320,30) 却变成了 (10.66,1)。

    这原因要归咎到度量单位上。虽然网页中的 Canvas 是以像素 (pixels) 为度量单位,但是在 Box2D 中尝试 模拟真实的世界并采用米 (meters) 作为度量单位。

    对于米 (meters) 和像素 (pixels) 之间的转换没有通用的标准, 我们采用下面的转换标准效果还不错:

    1米 = 30像素

    定义一个变量来帮助我们将米(meters) 转换成像素 (pixels), 我们便可以在 Box2D 世界(world) 中进行操作时使用像素而不用使用米来作为度量单位。

    这样将使我们在制作 Canvas 游戏时使用像素来思考, 从而变得更加直观。

  2. 打开你在第一章中创建的 demo1-1.html,并像下面那样修改它:

<script>
  function init(){
    var b2Vec2 = Box2D.Common.Math.b2Vec2
    ,b2AABB = Box2D.Collision.b2AABB
    ,b2BodyDef = Box2D.Dynamics.b2BodyDef
    ,b2Body = Box2D.Dynamics.b2Body
    ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef
    ,b2Fixture = Box2D.Dynamics.b2Fixture
    ,b2World = Box2D.Dynamics.b2World
    ,b2MassData = Box2D.Collision.Shapes.b2MassData
    ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
    ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
    ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw
    ,b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
    ;

    
    var world;
    var worldScale = 30;
    function main(){
        world = new b2World(new b2Vec2(0, 9.81), true);
        var bodyDef = new b2BodyDef(); 
        bodyDef.position.Set(320/worldScale,30/worldScale);

        setInterval(updateWorld, 1000 / 60);
    }
    function updateWorld() {
        world.Step(1/30,10,10);
        world.ClearForces(); // 清除作用力
    }

    main();
  }
  init();
</script>

注意我是怎样创建世界和调用 step 方法的。相比之前少用了几行代码。

一旦你创建了刚体定义,那么是时候给它一个形状了。

创建一个圆形形状

形状(shape)是一个2D几何对象,例如一个圆形或者多边形在这里必须是凸多边形(每一个内角小于180度)。

记住 Box2D 只能处理凸多边形

此处,我们从小球开始, 所以我们创建一个圆形:

var circleShape =new b2CircleShape(25/worldScale);

b2CircleShape是用来创建圆形形状,并且它的构造函数需要一个半径(radius)作为 参数。

在之前的代码中,我们创建了一个圆形,它的半径为25像素(pixels),由于设置了 worldScale 变量。

从现在起,每次你想要使用像素进行操作时,你只要将它们除以 worldScale 即可。你也可以定义一个方法名为 pixelsToMeters 的方法,在每次你需要将像 素(pixels)转换成米(meters)时调用。

当我们有了刚体定义和形状时,我们将使用夹具(fixture)来将它们粘起来。

创建夹具 fixture

夹具 fixture 用于将形状绑定到刚体上,然后定义它的材质,设置密度 (density),摩擦系数(friction)以及恢复系数(restitution)。

此刻我们无需去关心材质, 让我们把注意力集中到 fixture 上:

  1. 首先,我们创建 fixture:
var fixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;

一旦我们通过构造函数创建了夹具(fixture),我们将分配之前创建 的形状给它的shape属性。

  1. 最后,我们准备将球添加到世界中:
var theBall = world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);

b2Body是刚体的实体:是物质,是通过使用 bodyDef 属性创建的具 体刚体。

  1. 再次说明一下,使用以下步骤将刚体添加到世界中:
    I 创建一个刚体定义,它将持有刚体信息,例如刚体的位置信息。

II 创建一个形状,它将决定刚体的显示形状

III. 创建一个夹具, 通过它将形状附加到刚体定义上。

IV. 使用夹具创建刚体在世界中的实体

一旦你知道了每一步的作用, 添加刚体到你的 Box2D 世界中将会很简单

回到我们的项目。现在的main函数内应该看起来和下面一样:

function main(){
  world = new b2World(new b2Vec2(0, 9.81), true);
  var bodyDef = new b2BodyDef(); 
  bodyDef.position.Set(320/worldScale,30/worldScale);
  var circleShape = new b2CircleShape(25/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = circleShape;
  fixtureDef.density = 1;
  fixtureDef.restitution = .6;
  fixtureDef.friction = .1;
  var theBall = world.CreateBody(bodyDef);
  theBall.CreateFixture(fixtureDef);

  setInterval(updateWorld, 1000 / 60);
}

定时保存项目并测试它。准备好看看你的第一个 Box2D刚体的表现? 运行它!

额…,然而你现在运行时还是看不到任何东西。。

让我告诉你原因,Box2D只负责模拟物理世界,而不负责显示任何东西。

这意味着,你的刚体正活跃在你的Box2D世界中,只是你看不到而已。

使用调试绘图测试你的模拟

幸运的是,Box2D 有一个特性,调试绘图(debug draw),它将帮助你显示出模拟的情况:

在网页中首先要添加一个canvas如

<canvas id="canvas" width="640" height="480" style="" ></canvas>
  1. 调试绘图(debug draw)将Box2D世界中发生的事情显示出来,在

updateWorld方法中,我们可以在Step()方法之后调用世界(world)的 DrawDebugData()方法:

world.DrawDebugData();
  1. 一旦我们告知世界在每次遍历之后显示调试绘图(debug draw),我们需要通 过调试绘图(debug draw)定义视觉设置。如下添加代码到你的main函数内:
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
  1. 这里有很多代码,所以让我们来解释一下发生了什么。你已经知道 DrawDebugData()方法代表什么,所以我们将解释其它行代码代表的意思:
var debugDraw = new b2DebugDraw(); 

b2DebugDraw是一个类,它支持调试绘制(debug draw)出你在游戏中的物理实体。

var debugSprite:Sprite = new Sprite();

debugSprite 被添加到显示列表(Display List), 准备显示在 canvas 上。

debugDraw.SetSprite(debugSprite);

SetSprite()方法告知 debugSprite 将要被用来显示调试的绘图 (debug draw)。

debugDraw.SetDrawScale(worldScale);

因为我们要将米(meters)转变为像素(pixels),我们需要通知调试绘图(debug draw)我们使用的换算比例。

debugDraw.SetFlags(b2DebugDraw.e_shapeBit);

SetFlag() 方法允许我们决定我们将在调试绘图 (debug draw) 中描绘的物理实体的类型。当前我们只需要绘制形状。

补充说明:

setFlag() 方法用于选择绘制 Box2D 对象的内容类型。这样可以节省 CPU 开支。setFlag() 方法有一个 16 进制的参数,这参数的取值只能是 b2DebugDraw 中定义的下面几个常量

另外,我们还可以用”或”运算符,同时使用多个Flag

debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);

debugDraw.SetFillAlpha(0.5);

SetFillAlpha()方法是为了便于美观而设置的。形状的轮廓是不透明的, 填充的颜色是半透明的。这将使得调试绘图输出更加易于理解。

world.SetDebugDraw(debugDraw);

最后,我们将指派调试绘图(Debug draw)到我们刚刚创建的世界(world)

4.现在是时候来测试一下了, 然后你应该会看到下图所示的样子:

image

就这样! 你设法看到了你放置在 Box2D 世界中的刚体。

目前,球体还无法在重力的作用下下落,但是不要担心,我们将在稍后修改它。

现在,让我们来创建一些可以作为地面的东西,例如一个放置在舞台底部边缘的大矩形。

从现在开始一切将更加简单, 作为新的刚体将会很快的自动显示在它所添加的世界中。

完整源码在 demo2-1.html 中查看

创建矩形形状

让我执行下面的步骤:

  1. 首先,刚体和夹具的定义可以重指定到我们定义的新的刚体上。这样,我们无需再去定义 bodyDef 变量,但是我们要改变原先在创建球时使用的坐 标:

    bodyDef.position.Set(320/worldScale,470/worldScale); 
    
  2. 我们将用 b2PolygonShape 类创建一个多边形:

    var polygonShape = new b2PolygonShape(); 
    

    这样,我们以之前创建圆形形状时,相同的方法创建了一个多边形形状。

  3. 多边形形状必须遵守一些限制,但是目前,因为我们只需要一个轴对称的矩 形,SetAsBox()方法便能满足我们的需要:

    polygonShape.SetAsBox(320/worldScale,10/worldScale);
    

    这个方法需要两个参数:矩形的半宽长和半高长。最后,我们的新多边形形状的中心在像素 (320,470), 它的宽度为 640 像素和高度为 20 像素——这是我们刚刚创建的地面的尺寸

  4. 现在,我们改变定义的夹具的shape属性,附加新的多边形形状: 

    fixtureDef.shape = polygonShape; 
    
  5. 最后,我们可以创建刚体并将夹具附加上去,就像我们在球形上做的那样。

    var theFloor = world.CreateBody(bodyDef); 
    theFloor.CreateFixture(fixtureDef); 
    
  6. 你的 main 方法应该向下面这样:

    function main(){
      world = new b2World(new b2Vec2(0, 9.81), true);
      var bodyDef = new b2BodyDef(); 
      bodyDef.position.Set(320/worldScale,30/worldScale);
      var circleShape = new b2CircleShape(25/worldScale);
      var fixtureDef = new b2FixtureDef();
      fixtureDef.shape = circleShape;
      fixtureDef.density = 1;
      fixtureDef.restitution = .6;
      fixtureDef.friction = .1;
      var theBall = world.CreateBody(bodyDef);
      theBall.CreateFixture(fixtureDef);
    
      // 定义矩形地面
      bodyDef.position.Set(320/worldScale, 470/worldScale); 
      bodyDef.type = b2Body.b2_staticBody;
      var polygonShape = new b2PolygonShape();
      polygonShape.SetAsBox(320/worldScale, 10/worldScale);
      fixtureDef.shape = polygonShape; // 复用夹具
    
      var theFloor = world.CreateBody(bodyDef);
      theFloor.CreateFixture(fixtureDef);
    
      //setup debug draw
      var debugDraw = new b2DebugDraw();
      debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
      debugDraw.SetDrawScale(worldScale);
      debugDraw.SetFillAlpha(0.5);
      debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
      world.SetDebugDraw(debugDraw);
    
      setInterval(updateWorld, 1000 / 60);
    }
    
  7. 测试影片,你将会看到地面:

image

完整源码在 demo2-2.html 中查看

你看是不是很简单? 我们花了将近一章半去绘制我们的第一个刚体

然后只花了很少的几行代码添加另一个刚体。

不同的刚体类型——static, dynamic 和 kinematic

有三种 Box2D 刚体的类型: staitc, dynamic 和 kinematic。

一个 static 类型的刚体不受任何力,冲量或撞击的影响并且不会移动。它只能通过 用户手动移动。默认情况下,所有的Box2D刚体都是static类型的刚体,这就是为什 么球不移动的原因。一个static类型的刚体不会和别的static或kinematic类型的刚体发 生碰撞。

一个 dynamic 类型的刚体受力,冲量,撞击以及任何世界事件的影响。它可以通过 手动移动,虽然我建议让它们通过世界的重力,和任何类型刚体的碰撞来移动。

一个 kinematic 类型的刚体是一个介于static和dynamic刚体之间的混合刚体。它不 受理的影响,但是可以通过手动和设置它们的速率来移动。它不能和static和 kinematic类型的刚体碰撞。

回到我们需要的模拟中来。哪种类型是我们要指派给球和地面的呢?

地面是static类型的刚体,它无需移动,然而球要移动因为要模拟重力, 所以是 dynamic 类型的刚体。

你只需要设置刚体定义的 type 属性就能告知 Box2D 每一个刚体的类型,属性值可以是

b2Body.b2_staticBody, b2Body.b2_dynamicBody 或 b2Body.b2_kinematicBody 分别对应 static,dynamic 或 kinematic 刚体。

为球添加上 bodyDef.type=b2Body.b2_dynamicBody;

为地面添加上 bodyDef.type=b2Body.b2_staticBody;

你的新 main 方法像下面这样:

function main(){
  world = new b2World(new b2Vec2(0, 9.81), true);
  var bodyDef = new b2BodyDef(); 
  bodyDef.position.Set(320/worldScale,30/worldScale);
  bodyDef.type = b2Body.b2_dynamicBody;
  var circleShape = new b2CircleShape(25/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = circleShape;
  var theBall = world.CreateBody(bodyDef);
  theBall.CreateFixture(fixtureDef);

  // 定义矩形地面
  bodyDef.position.Set(320/worldScale, 470/worldScale); // 复用定义刚体
  bodyDef.type = b2Body.b2_staticBody;
  var polygonShape = new b2PolygonShape();
  polygonShape.SetAsBox(320/worldScale, 10/worldScale);
  fixtureDef.shape = polygonShape; // 复用夹具

  var theFloor = world.CreateBody(bodyDef);
  theFloor.CreateFixture(fixtureDef);

  //setup debug draw
  var debugDraw = new b2DebugDraw();
  debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
  debugDraw.SetDrawScale(worldScale);
  debugDraw.SetFillAlpha(0.5);
  debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
  world.SetDebugDraw(debugDraw);

  setInterval(updateWorld, 1000 / 60);
}

image

在恭喜你运行成功你的第一个模拟之前,让我们花点时间来讲一下关于在调试绘图 (debug draw) 运行的中不同颜色的意义。

static 类型的刚体将会绘制成绿色。

dynamic 类型的刚体, 当它们没有在睡眠状态时将会绘制成红色,在睡眠状态时将会绘制成灰色。

kinematic 类型的刚体, 在之前的屏幕截图中没有显示,它将会被显示为蓝色。

现在,我们知道当刚体进入睡眠状态并节约CPU资源这个概念。

正如你所见, 当球撞击地面,没有别的作用力影响它时,球可以进入睡眠状态,直到有新的作用力发生为止。

现在,有一个新的问题。球在落地后没有弹起。如果我们想要运行一个完美的模拟, 我们需要给我们的刚体增加更多的属性。

密度, 摩擦 和 恢复

现成你已经知道怎样向世界添加刚体

我想向你介绍三种会改变刚体行为的属性: 密度,摩擦和恢复。

密度 (density) 用来设置刚体的质量,按照公斤每平方米。越高的密度意味着越重的刚体, 并且该值不能为负。

摩擦 (friction) 在两个刚体在彼此的表面上移动时产生, 它是通过一个系数来定义, 通常它的范围在 0(没有摩擦)-1(最大摩擦)之间。它不能为负数。

恢复 (restitution) 决定刚体在发生碰撞时反弹的程度。与密度 (density) 和摩擦 (friction) 一样, 它不能为负数并且它是一个介于 0-1 的系数。

一个小球在 restitution 为 0 时落向地面,不发生反弹(无弹性碰撞),反之 restitution 为 1 时小球将会以此刻撞击时相同的速率弹起(完全弹性碰撞)。

密度(density),摩擦(friction)和恢复(restitution)必须添加到夹具上,所以在main方法中添加以下几行代码:

fixtureDef.density=1;

fixtureDef.restitution=0.6;

fixtureDef.friction=0.1;

现在你的main函数内看起来应该是这样的

function main(){
world = new b2World(new b2Vec2(0, 9.81), true);
var bodyDef = new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
bodyDef.type = b2Body.b2_dynamicBody;
var circleShape = new b2CircleShape(25/worldScale);
var fixtureDef = new b2FixtureDef();
fixtureDef.shape = circleShape;
fixtureDef.density = 1;
fixtureDef.restitution = .6;
fixtureDef.friction = .1;
var theBall = world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);

// 定义矩形地面
bodyDef.position.Set(320/worldScale, 470/worldScale); // 复用定义刚体
bodyDef.type = b2Body.b2_staticBody;
var polygonShape = new b2PolygonShape();
polygonShape.SetAsBox(320/worldScale, 10/worldScale);
fixtureDef.shape = polygonShape; // 复用夹具

var theFloor = world.CreateBody(bodyDef);
theFloor.CreateFixture(fixtureDef);

//setup debug draw
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);

setInterval(updateWorld, 1000 / 60);

}

我向夹具指派一次属性,而所有的刚体都将使用这个相同的夹具。在本书的整个

讲解过程中,我们将要处理很多夹具的属性,但是目前让我们只需要设置小球弹跳即可。

测试 demo2-3.html,你就会发现小球在弹跳

image

祝贺你!你刚刚完成了你的第一个真实的Box2D项目,那么现在你有能力去创建基础的形状和为它们分配特性和属性。

接下去让我开始来创建一个准游戏吧…

源码 demo 存放在 https://github.com/willian12345/Box2D-for-Javascript-Games

创建图腾破坏者的关卡

现在你有能力创建你的第一个游戏原型,我们将从创建图腾破坏者的级别开始。

为了展示我们所做事情的真实性,我们将流行的 Flash 游戏图腾破坏者的一关作为 我们模仿的对象。请看下面的截图:

image

这是图腾破坏者游戏原型的第一关,如果你仔细观察你会发现砖块的尺寸是 30 的倍数。

你知道这是什么原因吗?如果在你认真学习了前面的章节,你就会知道这是将米和像素的转换所致。

作者创建游戏可能是直接使用米作为度量单位的,但是我们将坚持自己的选择使用像素作为度量单位。

目前,我们无需去担心怎样设置褐色和黑色的图腾颜色,我们只要想着再现图腾即可。

在我们开始编码之前,我将建议你创建几个方法,这样可以帮我们重用部分代码。

别担心,没什么新的东西,我们只是稍微组织一下我们的脚本。

  1. 因此,我们将创建一个debugDraw()方法,它将处理所有与调试绘图
function debugDraw(){
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
    debugDraw.SetDrawScale(worldScale);
    debugDraw.SetFillAlpha(0.5);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    world.SetDebugDraw(debugDraw);
}

  1. 然后,由于我们要创建很多的砖块,所以我强烈推荐用一个方法传递砖块的位置和尺寸来创建砖块,所以下面的 brick() 方法的参数为: 砖块原点的水平和垂直坐标,宽度和高度。
function brick(px, py, w, h){
  var bodyDef = new b2BodyDef();
  bodyDef.position.Set(px/worldScale, py/worldScale);
  // bodyDef.type = b2Body.b2_dynamicBody;
  var polygonShape = new b2PolygonShape();
  polygonShape.SetAsBox(w/2/worldScale, h/2/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = polygonShape;
  fixtureDef.density = 2;
  fixtureDef.restitution = .4;
  fixtureDef.friction = .5;
  var theBrick = world.CreateBody(bodyDef);
  theBrick.CreateFixture(fixtureDef);
}

如果你看看代码就会发现没有什么新东西,但是我希望你能看看第4行和第6行的那几行代码:

  • 首先,被注释了的那行设置类型属性的代码, 因此目前我们创建的都是static类型的刚体。当你在设置关卡的时候,使用static类型的刚体比 较好,一旦你满意你所设计的关卡时,可以将它们转变为dynamic类型 的刚体。static类型的刚体将帮助你查看每一个刚体的精确位置,避免 重叠或其它的设计错误。

  • 其次,在 SetAsBox() 方法中, 我们将砖块的宽高分别除以 2 再除以之前 的 worldScale。无论它要的是砖块的半宽和半高,我这么做是想要用真实的宽高像素来调用 brick() 方法,

  1. 你的Main类的方法应该看起来向下面这样:
function main(){
  world = new b2World(gravity, sleep);
  debugDraw();
  // level design goes here 
  setInterval(updateWorld, 1000 / 60);
}

现在Main()方法看起来简单清晰,那么我们可以通过调用brick()方法,一次一块的来搭建我们的关卡。

并且,请注意我将重力(gravity)设置为(0,5)而不是作为小球下落时的真实世界的重力。较弱的重力将会使图腾破坏和下落变得慢一些,从而产生更 好的效果。总之,这只是个与游戏设计的建议,而且你可以自由设置你自己的重力。

  1. 现在,让我们回到Main()方法中:
function main(){
  world = new b2World(gravity, sleep);
  debugDraw();
  brick(275,435,30,30);
  brick(365,435,30,30);
  brick(320,405,120,30);
  brick(320,375,60,30);
  brick(305,345,90,30);
  brick(320,300,120,60);
  setInterval(updateWorld, 1000 / 60);
}

我们先搭建左边的基石砖块和右边的基石砖块,然后剩下的砖块从下向上一次堆放。

  1. 测试
    image

完整源码在 demo2-4.html 中查看

我想你会为你的测试成功而感到非常的开心。正如你学习搭建你的第一个图腾那 样,我们准备将黄金神像放到图腾的顶部。

在我们添加地面和改变图腾刚体为dynamic类型之前,我希望你思考一下黄金神像。

假使我们想要给黄金神像一个形状,而这个形状是不同于矩形和圆形的形状,这将会怎样呢?

创建复合刚体

黄金神像在图腾破坏者中是主要角色,我们不能用一个矩形来代表它或者否则诅咒的图腾将会永远困扰着我们。

我想象出了下面的图形:

image

这就是我说的黄金神像,就是我们将要在 Box2D 中创建的物体。

上图的左边是神像刚体的轮廓,然后右边是完成的神像轮廓。

首先你看到的神像是由不止一个刚体组合而成的一个复杂刚体。记住 Box2D只能接受凸多边形。和图腾砖块只是单个的堆叠不同,我们需要通过某种方法合并所有的神像组成对象。

首先,我们将从创建一个垂直的矩形开始,这个我们已经知道怎样创建了,我们将把 它的代码放在 Main() 方法中最后一块图腾砖块之后。

function main(){
  world = new b2World(gravity, sleep);
  debugDraw();
  brick(275,435,30,30);
  brick(365,435,30,30);
  brick(320,405,120,30);
  brick(320,375,60,30);
  brick(305,345,90,30);
  brick(320,300,120,60);
  idol(320, 242);
  setInterval(updateWorld, 1000 / 60);
}

下面的 idol() 方法是我们创建神像的方法:

function idol(px, py){
  var bodyDef = new b2BodyDef();
  bodyDef.position.Set(px/worldScale, py/worldScale);
  //bodyDef.type = b2Body.b2_dynamicBody;
  var polygonShape = new b2PolygonShape();
  polygonShape.SetAsBox(5/worldScale, 20/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = polygonShape;
  fixtureDef.density = 1;
  fixtureDef.restitution = .4;
  fixtureDef.friction = .5;
  var theIdol = world.CreateBody(bodyDef);
  theIdol.CreateFixture(fixtureDef);
}

目前,我们只添加了一个矩形形状,就和砖块一样,所以这些代码无需说明。

运行后,你将会看到神像的刚体。

image

完整源码在demo2-5.html中查看

第二部分,我们将创建一个交叉的形状。这个交叉的形状是也是由两个矩形组成的,但是这次他们分别要顺时针和逆时针旋转45度。

你将在下面学到创建定向的矩形形状。

创建定向的矩形

创建定向矩形形状,我将使用 b2PolygonShape 类中和 SetAsBox() 方法相似的 SetAsOrientedBox() 增强方法。 参数是矩形的宽和高,矩形的中心定义是一个b2Vec2对象和旋转的弧度。

  1. 应用上面的方法,在idol()方法中按照下面的步骤操作:

    var bW = 5/worldScale;
    var bH = 20/worldScale;
    var boxPos = new b2Vec2(0, 10/worldScale);// 可以使用矩形内的相对位置
    var boxAngle = -Math.PI / 4;
    

    前两行代码,我们定义了矩形的尺寸,和我们刚刚创建的矩形一样。

    让我们来看看第三行,在这里我们定义了位置,你可能会有疑惑。

    第一个神像的组件矩形的中心为(320,242)像素,所以为什么我要将第二个神像矩形 的位置设置为(0,10)?

    不是应该放置在第一块神像组件矩形的附近吗? 这是你需要学习复合对象神奇的地方。

    现在,我们不需要定义绝对位置,而是定义一个相对第一个神像组件矩形的位置。所以,这将意味着,矩形将放置在刚体中心偏下的位置。最后一行就是指定旋转的弧度,逆时针45度。

  2. 你可以将这四个变量作为参数传入SetAsOrientedBox()方法:

    polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
    
  3. 然后,照例我们要更新夹具的shape属性:

    fixtureDef.shape = polygonShape;
    
  4. 那么,神奇的神奇的事情要发生了,我不用去创建一个新 b2Body 对象,我们将夹具附加到已经存在的 theIdol 刚体上:

    theIdol.CreateFixture(fixtureDef);
    
  5. 如果我们为交叉形状的另一个矩形应用相同的参数,我们需要改变一下boxAngle变量:

    boxAngle=Math.PI/4;
    
  6. 然后我们可以创建定向矩形,更新夹具的 shape 属性,然后添加它到 theIdol 刚体上:

    polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
    fixtureDef.shape = polygonShape;
    theIdol.CreateFixture(fixtureDef);
    
  7. 最后,idol() 方法将看起来和下面的代码片段一样:

    function idol(px, py){
        var bodyDef = new b2BodyDef();
        bodyDef.position.Set(px/worldScale, py/worldScale);
        //bodyDef.type = b2Body.b2_dynamicBody;
        var polygonShape = new b2PolygonShape();
        polygonShape.SetAsBox(5/worldScale, 20/worldScale);
        var fixtureDef = new b2FixtureDef();
        fixtureDef.shape = polygonShape;
        fixtureDef.density = 1;
        fixtureDef.restitution = .4;
        fixtureDef.friction = .5;
        var theIdol = world.CreateBody(bodyDef);
        theIdol.CreateFixture(fixtureDef);
    
        // 创建定向矩形
        var bW = 5/worldScale;
        var bH = 20/worldScale;
        var boxPos = new b2Vec2(0, 10/worldScale);// 可以使用矩形内的相对位置
        var boxAngle = -Math.PI / 4;
        // 左倾矩形
        polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
        fixtureDef.shape = polygonShape;
        theIdol.CreateFixture(fixtureDef);
    
        // 右倾矩形
        boxAngle = Math.PI / 4;
        polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
        fixtureDef.shape = polygonShape;
        theIdol.CreateFixture(fixtureDef);
    }
    
  8. 运行测试,我们将看到神像与之前的图案比较相似了:

image

完整代码在 demo2-6.html

现在,我们仍需要创建它的头部。

这次,你应该可以自己去创建它了吧。总而言之,它只是另一个复合刚体的定向矩形部件。

我将向你展示另外一种方式,使用更加强大的方法创建一个多边形形状。

创建各种类型的凸多边形

Box2D允许你创建任何种类的多边形形状,只要多边形是凸多边形,这将意味着它拥 有的所有内角要小于180度,所以所有的顶点要远离中心,而且你要按顺时针顺序排 列它们。

  1. 首先,让我们创建一个向量(Vector)来储存所有的顶点:

    // 用多边形方式创建凸多边形
    var vertices = new b2Vec2();
    
  2. 然后,我们将所有顶点作为b2Vec2对象并顺时针顺序推入向量(vertices)中, 并为b2Vec2对象指派顶点相对神像刚体的中心的坐标位置。

    var vertices = new Box2D.NVector();
    vertices.push(new b2Vec2(-15/worldScale,-25/worldScale));
    vertices.push(new b2Vec2(0,-90/worldScale));
    vertices.push(new b2Vec2(15/worldScale, -25/worldScale));
    vertices.push(new b2Vec2(0,-10/worldScale));
    
  3. 之前的几行代码表示的是神像的头部的四个顶点。现在让我们将向(vector) 变成多边形形状

    polygonShape.SetAsVector(vertices, 4);
    

    SetAsVector方法将任何顺时针方向的顶点向量(vector)变成一个多边形形 状。第二个参数只是代表需要的顶点数。

  4. 最后,和通常一样,你需要更新夹具的shape属性并将它添加到theIdol刚 体上

    fixtureDef.shape = polygonShape;
    theIdol.CreateFixture(fixtureDef);
    
  5. 下面就是idol()方法看起来的样子:

    function idol(px, py){
      var bodyDef = new b2BodyDef();
      bodyDef.position.Set(px/worldScale, py/worldScale);
      bodyDef.type = b2Body.b2_dynamicBody;
      var polygonShape = new b2PolygonShape();
      polygonShape.SetAsBox(5/worldScale, 20/worldScale);
      var fixtureDef = new b2FixtureDef();
      fixtureDef.shape = polygonShape;
      fixtureDef.density = 1;
      fixtureDef.restitution = .4;
      fixtureDef.friction = .5;
      var theIdol = world.CreateBody(bodyDef);
      theIdol.CreateFixture(fixtureDef);
    
      // 创建定向矩形
      var bW = 5/worldScale;
      var bH = 20/worldScale;
      var boxPos = new b2Vec2(0, 10/worldScale);// 可以使用矩形内的相对位置
      var boxAngle = -Math.PI / 4;
      // 左倾矩形
      polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
      fixtureDef.shape = polygonShape;
      theIdol.CreateFixture(fixtureDef);
    
      // 右倾矩形
      boxAngle = Math.PI / 4;
      polygonShape.SetAsOrientedBox(bW, bH, boxPos, boxAngle);
      fixtureDef.shape = polygonShape;
      theIdol.CreateFixture(fixtureDef);
    
      // 用多边形方式创建凸多边形
      var vertices = new Box2D.NVector();
      vertices.push(new b2Vec2(-15/worldScale,-25/worldScale));
      vertices.push(new b2Vec2(0,-90/worldScale));
      vertices.push(new b2Vec2(15/worldScale, -25/worldScale));
      vertices.push(new b2Vec2(0,-10/worldScale));
      polygonShape.SetAsVector(vertices, 4);
      fixtureDef.shape = polygonShape;
      theIdol.CreateFixture(fixtureDef);
    }
    
  6. 运行测试后你将在最后看到你完成的在图腾顶部的神像

    image

  7. 这时,你只需要创建地面,它是一个 static 类型的矩形刚体

    function main(){
      world = new b2World(gravity, sleep);
      debugDraw();
      brick(275,435,30,30);
      brick(365,435,30,30);
      brick(320,405,120,30);
      brick(320,375,60,30);
      brick(305,345,90,30);
      brick(320,300,120,60);
      idol(320, 242);
      floor();
      setInterval(updateWorld, 1000 / 60);
    }
    
  8. 下面是 floor() 方法的定义:

    function floor(){
      var bodyDef = new b2BodyDef();
      bodyDef.position.Set(320/worldScale, 465/worldScale);
      var polygonShape = new b2PolygonShape();
      polygonShape.SetAsBox(320/worldScale, 15/worldScale);
      var fixtureDef = new b2FixtureDef();
      fixtureDef.shape = polygonShape;
      fixtureDef.restitution = .4;
      fixtureDef.friction = .5;
      var theFloor = world.CreateBody(bodyDef);
      theFloor.CreateFixture(fixtureDef);
    }
    
  9. 然后删除 brick() 方法内的注释行,将砖块设置为dynamic类型:

    bodyDef.type = b2Body.b2_dynamicBody;
    
  10. 最后,我们将神像的也设置为 dynamic 类型 idol() 方法内

    bodyDef.type = b2Body.b2_dynamicBody;
    

正如本章开始时所说的,你在Box2D中创建一个真实的图腾破坏者关卡已经完成

image

完整代码请查看 demo2-7

小结

你刚刚学习的是本书中最重要的章节之一,在这里你学习了怎样创建刚体然后

使用它们去设计成功游戏的关卡,例如图腾破坏者。

为了习惯使用 Box2D 的刚体,我建议你创建更多的图腾破坏者(Totem Destroyer)的关卡或者一些红砖移除(Red Remover)或者愤怒的小鸟(Angry Birds)的关卡。总之,它只是一个简单的形状。

本文相关代码请在

https://github.com/willian12345/Box2D-for-Javascript-Games

注:转载请注明出处博客园:王二狗Sheldon池中物 (willian12345@126.com)

https://github.com/willian12345

posted @ 2016-05-16 11:38  池中物王二狗  阅读(3793)  评论(16编辑  收藏  举报
转载入注明博客园 王二狗Sheldon Email: willian12345@126.com https://github.com/willian12345