这是系列第二部分,之前部分在本博客中找
源码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-二狗-偷饭猫([email protected])
https://github.com/willian12345