HTML5之2D物理引擎 Box2D for javascript Games 系列 第二部分

这是系列第二部分,之前部分在本博客中找

源码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类是一个刚体的定义类,它将持有创建我们刚体所需要的所有数 据。

2. 现在可以将刚体添加到世界中。因为我们采用的舞台尺寸是640X480,我们将 把球放置在舞台的顶部的中心位置,该位置为(320,30),如下所示: 

  bodyDef.position.Set(10.66,1); 

  通过position属性显示的设置了刚体在世界中的位置,但是我确信你会对我之前 所说的位置为(320,30)的设置而变成(10.66,1)而感到困惑。

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

  对于米(meters)和像素 (pixels)之间的转换没有通用的标准,但是我们采用下面的转换标准可以有很 好的运行效果: 

  1米 = 30像素

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

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

3. 打开你在第一章中创建的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)用于将形状绑定到刚体上,然后定义它的材质,设置密度 (density),摩擦系数(friction)以及恢复系数(restitution)。

此刻我们无需去 担心材质,让我们把注意力集中到夹具(fixture)上: 

1.首先,我们创建夹具(fixture):


  

var fixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;

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

2.最后,我们准备将球添加到世界中:


  

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

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

3.再次说明一下,使用以下步骤将刚体添加到世界中:

  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();

2. 一旦我们告知世界在每次遍历之后显示调试绘图(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);

3.这里有很多代码,所以让我们来解释一下发生了什么。你已经知道 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.现在是时候来测试一下你影片了,然后你应该会看到下图所示的样子: 

 

就这样!你设法看到了你放置在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.测试影片,你将会看到地面: 

  

完整源码在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);
}

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

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

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

现在,我们知道当刚体进入睡眠状态并节约CPU资源这个概念。正如你所见, 当球撞击地面,没有别的里影响它时,所以求可以进入睡眠状态,知道有什么 发生为止。

现在,有一个新的问题。球在落地后没有弹起。如果我们想要运行一个完美的模

拟,我们需要给我们的刚体一些更多的属性。

密度,摩擦和恢复


正如你已经知道怎样向世界添加刚体,那么我想向你介绍三种将会改变刚体行为

的属性:密度,摩擦和恢复。

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

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

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

一个小球 在恢复为0时落向地面,不发生反弹(无弹性碰撞),反之恢复为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,你就会发现小球在弹跳

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

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

 

 


注:转载请注明出处博客园:sheldon-二狗-偷饭猫(willian12345@126.com)

https://github.com/willian12345

原文地址:https://www.cnblogs.com/willian/p/5478539.html