http://blog.sina.com.cn/s/blog_441c62380101i1uv.html
2,在世界里添加刚体(A)
在Box2D世界里任何你想移动或者相互作用的东西都是刚体,疯狂小鸟游戏中的小鸟,小猪和小箱子都是刚体,破坏者游戏里的那些积木也都是刚体,在这一章中你会学到各种不同的刚体,以及一些重要的属性:
1,创建圆形刚体
2,创建方形刚体
3,创建多边形刚体
4,使用 debug draw 测试你的模拟
5,定义刚体类型:static, dynamic, 或者kinematic
6,设置材质属性:密度(density),摩擦力(friction)和弹力(restitution)
7,测量单位
8,创建组合物体
学完了这一章,你就可以写破坏者游戏的一关了,这一章很长奥~,下面我们一起来看看吧。
你的第一个仿真模拟,小球直落到地板上
你的第一个仿真程序,非常简单。
让我们看看都需要添加些什么:
1,带有重力的世界。
2,一个可以和力(重力)相互作用的小球。
3,一个和力不发生关系的地板。
4,一些材质属性,我们希望小球在地板上弹起来。
你已经完成了重力部分,下面我们来开始小球的部分,
1,我们创建的是球形的还是多边形的都可以,
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef 是这个刚体的定义,他存储了你所要建刚体的所有信息。
2,现在把小球扔到世界里去,我们的 flash 大小是 640 x 480,我们把小球放在舞台的中上方(320,30)
bodyDef.position.Set(10.66,1);
position属性显然及时处理位置的啦,你肯定好奇位置为什么会是(10.66,1),而不是之前告诉你们的(320,30),这是测量单位的关系,Flash 中使用像素来计算的,但是Box2D中却是用米来计算的。把像素转化成米没有一个通用的规则,我们可以用下列代码来进行转化
meters = pixels * 30;
现在定义一个变量来方便像素和米之间的转化,我们也可以在 Box2D中使用像素做单位,这样在flash 游戏里面看起来更直观。
3,修改第一章中我们写的代码,
package {
import flash.display.Sprite;
import flash.events.Event;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
public class Main extends Sprite {
private var world:b2World;
private var worldScale:Number=30;
public function Main() {
world=new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
private function updateWorld(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces();
}
}
}
注意,以上代码已经创建 world 并且调用了 Step 函数
创建一个圆形
shape 是个二维几何图形,比如说圆或者多边形,这些图形都是凸面的(每一个内角都必须小于180度),记住,Box2D 只解决凸多边形。
好,开始写圆的代码:
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
使用 b2CircleShape 创建一个圆形,你要把半径作为参数传给它,我们创建了一个半径为25像素的圆,因为变量 worldScale 的存在,整个代码看起来很清晰,以后每次你想使用米的时候都要使用 worldScale 进行转化,使用 pixel/worldScale ,也可以定义一个叫 pixelsToMeters 的函数,每次进行转化的时候都调用它。
现在我们有了刚体定义和形状,我们使用 fixture 把他们联系在一起。
创建一个 fixture
fixture 可以把形状捆绑在一个刚体上,然后定义它的材料属性,设置它的密度,摩擦力跟弹力,先不用担心材料问题,我们先着重看一下 fixture,
1,第一步是创建 fixture,
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;
2,当我们用构造函数创建 fixture,我们把之前创建好的形状属性传给他,最后我们把小球加在世界里,
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
b2Body就是刚体,根据 bodyDef 的内容,就可以创建一个实实在在的刚体了。
3,回顾一下,我们是如何创建一个刚体的,
i, 创建一个刚体定义,定义中包含了刚体的信息,比如说位置之类的,
ii ,创建一个形状,他就是刚体应该的形状。
iii, 创建一个 fixture 连接形状(shape)和刚体定义(bodyDef)。
iv, 使用 fixture 在世界中创建刚体。
你理解了每一步的含义,创建刚体也不是难事了。
4,目前为止的代码如下:
package {
import flash.display.Sprite;
import flash.events.Event;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
public class Main extends Sprite {
private var world:b2World;
private var worldScale:Number=30;
public function Main() {
world=new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
private function updateWorld(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces;
}
}
}
准备测试,看看你的第一个Box2D程序。
好吧,你什么都没有看到,在你生气的丢掉这本书之前,最好知道Box2D 只运行物理世界的那部分,并不管如何显示。这说明你的刚体即使正在Box2D里面正常运行,你也看不见他。
使用 Debug Draw 去测试你的仿真环境
巧的是,Box2D有一个特别好的功能,debug draw,看看他能帮我们做些什么,
1,debug draw 可以帮助我们看见 Box2D 的运行情况。我们可以在 step 方法后调用 world 的 DrawDebugData 方法,把它添加到 update 方法中:
world.DrawDebugData();
2,每一次迭代后,告诉world来显示一下当前世界,我们要用debug draw 来进行显示设置,在Main 类中加入下列代码
package {
import flash.display.Sprite;
import flash.events.Event;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
public class Main extends Sprite {
private var world:b2World;
private var worldScale:Number=30;
public function Main() {
world =new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape=circleShape;
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
var debugDraw:b2DebugDraw = new b2DebugDraw();
var debugSprite:Sprite = new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
private function updateWorld(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces();
world.DrawDebugData();
}
}
}
3,看起来不少东西啊,下面逐个解释,你已经了解了 DrawDebugData,来看一下其他的。
var debugDraw:b2DebugDraw = new b2DebugDraw();
b2DebugDraw 是一个类,它可以提供绘制那些物理实体的方法,供你调试使用。
var debugSprite:Sprite = new Sprite();
debugSprite 是显示列表上的一个 sprite,是要在舞台上显示。
debugDraw.SetSprite(debugSprite);
SetSprite方法确定在一个指定的 sprite 进行显示绘制。
debugDraw.SetDrawScale(worldScale);
因为我们已经把米转换为像素,我们需要通知 debug draw 根据我们的缩放参数进行绘制,
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
SetFlags方法允许我们决定对一种物理实体进行显示绘制,现在我们就可以绘制图形了。
debugDraw.SetFillAlpha(0.5);
SetFillAlpha 方法就是为了视觉效果,这样的设置图形的边线是不透明的,但是图形内部的填充色是半透明的。这样会让你绘制的显示图形更加清晰。
4,测试后的图片如下:
可是球现球是一动不动的,别急,我们现在就来解决这个问题。
现在我们创建一个类似地面的东西,比如说在舞台的底部放置一个大的正方形,剩下的东西都不难了,因为此后新添加的刚体在被创建的时候就会自动的被添加到显示列表上。
创建一个 box Shape
我们分一下步骤来做:
1,首先 body 和 fixture 的定义可以再次分配给去定义新的刚体定义,这样我们就不需要再声明一个 bodyDef,但是我们可以继续使用之前创建的那个球体,只需要改变它的位置。
bodyDef.position.Set(320/worldScale,470/worldScale);
现在刚体被定义在舞台的水平中心,贴近底部。
2,要创建一个多边形,就要使用 b2PolygonShape
var polygonShape:b2PolygonShape=new b2PolygonShape();
创建多边形跟创建圆的一样。
3,多边形会有一些限制,暂时呢只考虑用一个对称的盒子,需要使用 SetAsBox 方法
polygonShape.SetAsBox(320/worldScale,10/worldScale);
这个方法需要两个参数,盒子宽的一半和长的一半,新多边形的中心在 (320,470),长为640,高为20,这就是我们创建的地板。
4,我们修改 fixture 的shape属性,把它连接到新的多边形:
fixtureDef.shape=polygonShape;
5,最后,我们可以创建世界里的刚体并且放置在里面,跟做球体是一样滴。
var theFloor:b2Body=world.CreateBody(bodyDef);
theFloor.CreateFixture(fixtureDef);
6,现在你的代码应该是这样的:
public function Main() {
world=new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
var fixtureDef:b2FixtureDef=new b2FixtureDef();
fixtureDef.shape=circleShape;
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
bodyDef.position.Set(320/worldScale,470/worldScale);
var polygonShape:b2PolygonShape=new b2PolygonShape();
polygonShape.SetAsBox(320/worldScale,10/worldScale);
fixtureDef.shape=polygonShape;
var theFloor:b2Body=world.CreateBody(bodyDef);
theFloor.CreateFixture(fixtureDef);
var debugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite=new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
7,测试影片:
我们用一章多的内容去讲第一个刚体,在添几行代码就可以在添加更多的刚体了,
现在来关心一下重力问题吧,小球是时候掉落了。
不同的刚体类型——静态的(static) ,动态的(dynamic) ,运动学的(kinematic)
Box2D共用三种类型,静态的(static) ,动态的(dynamic) ,运动学的(kinematic)
静态刚体不响应任何力的作用,冲量和碰撞都不会使它移动,只能靠人为的移动,这就是上面小球不动的原因,默认情况下,所有Box2D都是静态刚体,也不会和其他的静态刚体还有运动学上的刚体发生碰撞。
动态刚体受力,冲量,碰撞,其他任何事件(物理世界里的)的影响,动态刚体也可以手动控制,我建议让它们根据世界里的事件进行移动,比如说力,还有和其他物体的相撞。
运动学刚体是动态刚体和静态刚体的结合体,运动学刚体不受力的影响,但是可以手动,也可以给他们设置速度,运动学刚体不和其他静态和动态刚体发生碰撞。
现在回到仿真模拟,小球和地板应该使用哪种类型的刚体呢。
地板不需要移动,所以应该是静态刚体,而小球是受到重力运动的。
告诉Box2D每个刚体的类型,在刚体定义中的 type 属性中进行设置,类型有b2Body.b2_staticBody, b2Body.b2_dynamicBody, 和b2Body.b2_kinematicBody ,分别代表了静态,动态和运动的刚体。
Main 函数如下显示:
Your new Main function is shown as follows:
public function Main() {
world=new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
bodyDef.type=b2Body.b2_dynamicBody;
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
var fixtureDef:b2FixtureDef=new b2FixtureDef();
fixtureDef.shape=circleShape;
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
bodyDef.position.Set(320/worldScale,470/worldScale);
bodyDef.type=b2Body.b2_staticBody;
var polygonShape:b2PolygonShape=new b2PolygonShape();
polygonShape.SetAsBox(320/worldScale,10/worldScale);
fixtureDef.shape=polygonShape;
var theFloor:b2Body=world.CreateBody(bodyDef);
theFloor.CreateFixture(fixtureDef);
var debugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite=new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
测试结果:
关于debug draw 的不同颜色,静态刚体显示为绿色,动态刚体休眠的时候是灰色,没有休眠的时候是红色,运动学刚体,跟上边的截图都不一样,是蓝色的。
现在你很清楚让刚体休眠这个概念了吧,看出它是如何节省CPU的,当球碰到地面时,没有其他的力作用于它,所以它可以一直休眠。
现在有个新的问题,小球并没有弹跳,我们要给这个刚体设置些新的属性了。
密度(Density), 摩擦系数(friction),和弹力系数(restitution)
现在来给你介绍刚体的三个属性,密度,摩擦系数,弹力系数,他们是可以左右到刚体的”行为“。
密度是用来设置刚体的质量的,每米以千克计算,密度越高刚体越沉,还有密度的值不可以是负值。
摩擦系数使用在两个刚体相互滑动时,它被定义成一个系数,值从 0(没有摩擦系数)到 1(相当大的系数),它也不可以是负值。
弹力系数决定了一个刚体在发生碰撞时会产生多大的弹力,跟密度和摩察系数一样,也不能是负值,它的取值范围也是从0到1的系数,这里小球的弹力系数是 0,因此它无法弹起(非弹性碰撞(inelastic collision))。碰撞后,刚体的反弹速度和发生碰撞时的速度是一样的(完全弹性碰撞(perfectly elastic collision)),更详细具体的解释建议自行查找。
密度,摩擦系数,弹力系数是必须要设置的值,继续修改下 Main 函数
public function Main() {
world=new b2World(new b2Vec2(0,9.81),true);
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(320/worldScale,30/worldScale);
bodyDef.type=b2Body.b2_dynamicBody;
var circleShape:b2CircleShape;
circleShape=new b2CircleShape(25/worldScale);
var fixtureDef:b2FixtureDef=new b2FixtureDef();
fixtureDef.shape=circleShape;
fixtureDef.density=1;
fixtureDef.restitution=0.6;
fixtureDef.friction=0.1;
var theBall:b2Body=world.CreateBody(bodyDef);
theBall.CreateFixture(fixtureDef);
bodyDef.position.Set(320/worldScale,470/worldScale);
bodyDef.type=b2Body.b2_staticBody;
var polygonShape:b2PolygonShape=new b2PolygonShape();
polygonShape.SetAsBox(320/worldScale,10/worldScale);
fixtureDef.shape=polygonShape;
var theFloor:b2Body=world.CreateBody(bodyDef);
theFloor.CreateFixture(fixtureDef);
var debugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite=new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
这些属性只分配一次,所以有些刚体有着相同的属性,这本书里我们会学习好多 fixture 属性。
测试影片:
祝贺你,你已经完成了真正意义上的Box2D项目,现在已经创建基本的图形并且设置他们的属性了。
下面该做点游戏相关的了
写一关 Totem Destroyer游戏
我们到底要做什么呢,模仿一个跟Totem Destroyer差不多游戏,看下面的截图:
Totem Destroyer: Gabriel Ochsenhofer ( www.gabs.tv)
这是Totem Destroyer游戏第一关的截图,如果你用心看的话,砖块的大小都是30的倍数,你知道原因吗?哈,很简单,那是因为米和像素之间的换算。
作者在创建游戏的时候可能是直接用米,但我们要直接用像素。首先想要复制这些砖块。
在写代码之前,为了简单起见,写一些函数帮助我们代码重用。
显示
1,debugDraw 函数会处理调试绘图的相关代码:
private function debugDraw():void {
var debugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite=new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
}
2,因为需要很多砖块,也需要个方法专门创建砖块,一个需要位置和大小的砖块,因此 brick 方法需要两个参数:砖块的x,y坐标和宽高。
private function brick(pX:int,pY:int,w:Number,h:Number):void {
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(pX/worldScale,pY/worldScale);
// bodyDef.type=b2Body.b2_dynamicBody;
var polygonShape:b2PolygonShape=new b2PolygonShape();
polygonShape.SetAsBox(w/2/worldScale,h/2/worldScale);
var fixtureDef:b2FixtureDef=new b2FixtureDef();
fixtureDef.shape=polygonShape;
fixtureDef.density=2;
fixtureDef.restitution=0.4;
fixtureDef.friction=0.5;
var theBrick:b2Body=world.CreateBody(bodyDef);
theBrick.CreateFixture(fixtureDef);
}
当然,你也可以在方法中传入密度,摩擦系数,弹力系数作为参数。
上面的代码没有什么新的知识了,但是还是要注意一下突出的部分:
。首先,设置type 属性的那一行已经有了注释,这里面的是静态类型,当你设计一关游戏的时候,先从静态的刚体开始,等没有问题了,在转换成动态的刚体,使用静态刚体可以让你清楚地看见每个刚体的初始位置,避免出现刚体之间位置重叠的情况。
。第二,SetAsBox属性,木条的宽和高先要除以2然后在除以 worldScale,我想去调用 brick 方法使用以像素为单位的宽和高,虽然SetAsBox方法所需的参数半宽和半长,也没什么关系了。
3,Main方法如下:
public function Main() {
world=new b2World(new b2Vec2(0,5),true);
debugDraw();
// level design goes here
addEventListener(Event.ENTER_FRAME,updateWorld);
}
private function brick(pX:int,pY:int,w:Number,h:Number):void {
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.position.Set(pX/worldScale,pY/worldScale);
//bodyDef.type=b2Body.b2_dynamicBody;
var polygonShape:b2PolygonShape=new b2PolygonShape();
polygonShape.SetAsBox(w/2/worldScale,h/2/worldScale);
var fixtureDef:b2FixtureDef=new b2FixtureDef();
fixtureDef.shape=polygonShape;
fixtureDef.density=2;
fixtureDef.restitution=0.4;
fixtureDef.friction=0.5;
var theBrick:b2Body=world.CreateBody(bodyDef);
theBrick.CreateFixture(fixtureDef);
}
private function debugDraw():void {
var debugDraw:b2DebugDraw=new b2DebugDraw();
var debugSprite:Sprite=new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
}
private function updateWorld(e:Event):void {
world.Step(1/30,10,10);
world.ClearForces();
world.DrawDebugData();
}
Main函数很简单清晰了,我们只需调用六次 brick 方法,每次生成一个障碍物。
注意 我这里把重力设置为(0,5)而不是现实世界的重力,小一点的重力会让刚体下落的慢一些,产生戏剧的效果,怎么说,这都是一个游戏设计上的选择,你可以设置自己喜欢的重力值。
4,更新后的Main 函数
public function Main() {
world=new b2World(new b2Vec2(0,5),true);
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);
addEventListener(Event.ENTER_FRAME,updateWorld);
}
从第一个 brick 函数到最后一个,我们分别创建可左右两边的地基,剩下的分别是从下到上摞着。
5,测试影片
Totem Destroyer: Gabriel Ochsenhpfer(www.gab.tv )
0 0