首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

运用Box2D制作AS3游戏——2.1a版本——Hello World Box2D

2012-10-31 
使用Box2D制作AS3游戏——2.1a版本——Hello World Box2D想要制作一个像纸上怪物一样酷的基于物理学的flash游

使用Box2D制作AS3游戏——2.1a版本——Hello World Box2D
想要制作一个像纸上怪物一样酷的基于物理学的flash游戏吗? 最好的方式就是使用一个叫做Box2D的很好的flash开源类库。现在有很多关于flash的物理引擎,但是Box2D就属于这些引擎中的战斗机。很多开发人员选择使用Box2D,并且现在Box2D有许多个语言版本(C++,java,xna,iphone,android),这使得Box2D成为了一款炙手可热的开发引擎。

       但是这也意味着,大部分的教程使用的不是AS3.0的编程语言,或者使用的是过时的版本。

      本教程使用AS3.0,以及最新的Box2D——2.1a版本。

      在本教程中,将会运用Box2D创建一个由能够进行真实碰撞的对象组成的世界,让你在实践中学习取得经验。

     如果你是第一次接触Box2D,你可能会对其以直观的方式将创建一个物体分为几个步骤的方式感到不解。比如,如果你想创建一个方块状和一个球状图形,并让他们能够发生碰撞。你可能希望只需要一个Block类和Ball类,通过new就能创建方块状和一个球状图形,并将他们添加到你的Box2dWord.

      但是你错了,在Box2D中,如果你想创建一个方块,你需要进行以下几个步骤,现在不用去担忧它是否是没意义的,后面我们将给出案例源码。

      1.创建一个Shape(形状)——这是一个对象的几何部分。
      2.创建一个Body(刚体)定义——这是Box2D中的一个特殊对象类型,作为创建刚体的参数。
      3.告诉World(世界)创建一个body(刚体)——将你创建的body定义传给world(世界),让世界以它作为参数创建一个刚体。一个刚体在世界中是由多个形状构成的不可分割的对象,这些形状共享同一个质心,即刚体的质心。
      4.创建一个fixture(固定附着物,即形状与刚体的绑定物)——fixture是box2d中的一个新的对象类型,用于定义添加到刚体上的形状的属性

      Box2D有一个特殊的方式让你创建物体。而且你必须在正确的时间,按正确的顺序进行。它不需要你在了解其内部组织结构上浪费时间。原因有两方面:其一,它最初是用不同的语言编写的,它遵循这些语言的许多公约。其二,这些步骤使得Box2D的运行很有效率。

      要明白,Box2D和其它的Actionscript APIS是不同的。不要去反对它,去用它就行了。因为它对自己的规则非常自信,并且异常强大。

      好了,让我们来写一些代码。我们将要创建一个box2d世界,在里面建两个矩形刚体,并用box2d内置的调试渲染器将其画在屏幕上。下面是我们将要创建的整个类。现在只需要大致的看一下,后面我们将详细的对其进行讲解。
01.package 

02.{ 

03.    import flash.display.Sprite; 

04.    import flash.events.Event; 

05.    // Bring In Box2D  

06.    import Box2D.Dynamics.* 

07.    import Box2D.Collision.* 

08.    import Box2D.Collision.Shapes.* 

09.    import Box2D.Dynamics.Joints.* 

10.    import Box2D.Dynamics.Contacts.* 

11.    import Box2D.Common.Math.* 

12.  

13.    /**

14.     * ...

15.     * @author Zach

16.     */ 

17.    public class Main extends Sprite 

18.    { 

19.        private var world:b2World; 

20.        private var timestep:Number; 

21.        private var iterations:uint; 

22.        private var pixelsPerMeter:Number = 30; 

23.  

24.        public function Main():void 

25.        { 

26.  

27.            makeWorld(); 

28.            makeWalls(); 

29.            makeDebugDraw(); 

30.  

31.            if (stage) init(); 

32.            else addEventListener(Event.ADDED_TO_STAGE, init); 

33.  

34.        } 

35.  

36.        private function makeWorld():void 

37.        { 

38.            // Define the gravity vector  

39.            var gravity:b2Vec2 = new b2Vec2(0.0, 10.0); 

40.            // Allow bodies to sleep  

41.            var doSleep:Boolean = true; 

42.            // Construct a world object  

43.            world = new b2World(gravity, doSleep); 

44.            world.SetWarmStarting(true); 

45.            timestep = 1.0 / 30.0; 

46.            iterations = 10; 

47.  

48.        } 

49.  

50.        private function makeWalls():void 

51.        { 

52.            // Note: I am assuming a stage size of 640x400;  

53.            // These placements will put a row of boxes around an area that size.  

54.  

55.            // We reuse the shape and Body Definitions.  

56.            // Box2D creates a different body each time we call world.CreateBody(wallBd);  

57.            var wall:b2PolygonShape= new b2PolygonShape(); 

58.            var wallBd:b2BodyDef = new b2BodyDef(); 

59.            var wallB:b2Body; 

60.  

61.            // Left  

62.            wallBd.position.Set( -95 / pixelsPerMeter, 400 / pixelsPerMeter / 2); 

63.            wall.SetAsBox(100/pixelsPerMeter, 400/pixelsPerMeter/2); 

64.            wallB = world.CreateBody(wallBd); // Box2D handles the creation of a new b2Body for us.  

65.            wallB.CreateFixture2(wall); 

66.            // Right  

67.            wallBd.position.Set((640 + 95) / pixelsPerMeter, 400 / pixelsPerMeter / 2); 

68.            wallB = world.CreateBody(wallBd); 

69.            wallB.CreateFixture2(wall); 

70.            // Top  

71.            wallBd.position.Set(640 / pixelsPerMeter / 2, -95 / pixelsPerMeter); 

72.            wall.SetAsBox(680/pixelsPerMeter/2, 100/pixelsPerMeter); 

73.            wallB = world.CreateBody(wallBd); 

74.            wallB.CreateFixture2(wall); 

75.            // Bottom  

76.            wallBd.position.Set(640 / pixelsPerMeter / 2, (400 + 95) / pixelsPerMeter); 

77.            wallB = world.CreateBody(wallBd); 

78.            wallB.CreateFixture2(wall); 

79.  

80.        } 

81.  

82.        private function makeDebugDraw():void 

83.        { 

84.            // set debug draw  

85.            var debugDraw:b2DebugDraw = new b2DebugDraw(); 

86.            var debugSprite:Sprite = new Sprite(); 

87.            addChild(debugSprite); 

88.            debugDraw.SetSprite(debugSprite); 

89.            debugDraw.SetDrawScale(30.0); 

90.            debugDraw.SetFillAlpha(0.3); 

91.            debugDraw.SetLineThickness(1.0); 

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

93.            world.SetDebugDraw(debugDraw); 

94.        } 

95.  

96.        private function init(e:Event = null):void 

97.        { 

98.            removeEventListener(Event.ADDED_TO_STAGE, init); 

99.            // entry point  

100.            update() 

101.        } 

102.  

103.        private function update(e:Event = null):void 

104.        { 

105.            world.Step(timestep, iterations, iterations); 

106.            world.ClearForces(); 

107.            // Render  

108.            world.DrawDebugData(); 

109.        } 

110.  

111.    } 

112.  

113.} 
复制代码
通览整个类,如果你看到6-11行,我们使用通配符*导入Box2d来引用所有类。对于本教程来说这种做法很好,但是在实际操作中,我通常只导入需要用到的类。

        教程中,我已经在构造函数中调用了三个函数,这三个函数的名称代表我们将要进行的三个步骤(27-29行).让我们详细的来看看每一个步骤:首先,创建世界。

       第一步:创建world(世界)
      在Box2D 2.1a版本中,创建世界要比以前的版本简单得多。如果你看过旧版本的例子,你应该知道,创建世界需要进行定义世界的大小等许多零零碎碎的操作。但是现在不用了。

     现在创建世界非常的直接了当。
01.private function makeWorld():void 

02.        { 

03.            // Define the gravity vector  

04.            var gravity:b2Vec2 = new b2Vec2(0.0, 10.0); 

05.            // Allow bodies to sleep  

06.            var doSleep:Boolean = true; 

07.            // Construct a world object  

08.            world = new b2World(gravity, doSleep); 

09.            world.SetWarmStarting(true); 

10.            timestep = 1.0 / 30.0; 

11.            iterations = 10; 

12.  

13.        } 
复制代码
第4行  我们定义了一个重力变量,为向量类型,世界中的所有对象都将被赋予这个重力向量。你可以通过设置向量为(0,-10)来创建反重力,或者设置向量为(0,0)来创建零重力,甚至是将X参数设为一个正数,Y参数设为O来创建不可思议的侧身重力。

第6行  决定是否让Box2D停止检测已经停止移动的物体的碰撞。设为True会降低CPU的消耗,但是你需要注意在某些情况下使其为flase.就个人而言,我还没遇到这样的情况。
第8行 创建世界!我们设置它的重力参数,是否休眠属性。
第9行 告诉世界,所有的刚体开始的时候都没有休眠。如果你设为flase,新建的刚体将不会立刻受到重力的影响直到你明确的将他们唤醒,或受到外力的作用。对于用Box2d制作像台球类型的游戏,因为其游戏开始的时候所有的球都是静止的,就可以将其设为flase.
第10行    Box2D在指定的时间内处理模拟对象的碰撞检测。timestep(步长)告诉Box2d两次碰撞检测应该间隔多久时间。保持这个数值较小是个好主意。

第11行 告诉Box2D在移动前要解决多少次复杂的碰撞。考虑这样一个情形,一次碰撞让一个刚体移进第三个刚体。现在他们发生了碰撞,并将一个刚体反弹回第一个刚体。由于物理模拟的实质,这些碰撞将没有止境。你使用iterations(迭代次数)告诉box2D“好了,已经够近了——向前移动",如果你将iterations设得小,你的碰撞模拟将会包含更多的错误和奇怪的行为,但是会更快。如果你将其值设得较大,将会更加精确,但是会降低整个程序的性能。除非你遇到问题,不然就将其值都设为10.

现在进行下一步——添加对象到我们的世界。

    第二步:创建墙(边界)
      现在我们有了一个空的Box2D世界。现在我们将添加一些能发生碰撞的刚体到里面。值得注意的是,Box2D中所有的类都是以”b2“为前缀开始的。这是一个非常有用的方式,通过这个方式,Box2D确保你不会创建和其同名的类。于是你创建一个英雄,英雄有一个身体——你依然可以为他创建一个b2Body,而不用担心覆盖类名。

      下面是创建边界墙的代码:
01.private function makeWalls():void 

02.        { 

03.            // Note: I am assuming a stage size of 640x400;  

04.            // These placements will put a row of boxes around an area that size.  

05.  

06.            // We reuse the shape and Body Definitions.  

07.            // Box2D creates a different body each time we call world.CreateBody(wallBd);  

08.            var wall:b2PolygonShape= new b2PolygonShape(); 

09.            var wallBd:b2BodyDef = new b2BodyDef(); 

10.            var wallB:b2Body; 

11.  

12.            // Left  

13.            wallBd.position.Set( -95 / pixelsPerMeter, 400 / pixelsPerMeter / 2); 

14.            wall.SetAsBox(100/pixelsPerMeter, 400/pixelsPerMeter/2); 

15.            wallB = world.CreateBody(wallBd); // Box2D handles the creation of a new b2Body for us.  

16.            wallB.CreateFixture2(wall); 

17.            // Right  

18.            wallBd.position.Set((640 + 95) / pixelsPerMeter, 400 / pixelsPerMeter / 2); 

19.            wallB = world.CreateBody(wallBd); 

20.            wallB.CreateFixture2(wall); 

21.            // Top  

22.            wallBd.position.Set(640 / pixelsPerMeter / 2, -95 / pixelsPerMeter); 

23.            wall.SetAsBox(680/pixelsPerMeter/2, 100/pixelsPerMeter); 

24.            wallB = world.CreateBody(wallBd); 

25.            wallB.CreateFixture2(wall); 

26.            // Bottom  

27.            wallBd.position.Set(640 / pixelsPerMeter / 2, (400 + 95) / pixelsPerMeter); 

28.            wallB = world.CreateBody(wallBd); 

29.            wallB.CreateFixture2(wall); 

30.  

31.        } 
复制代码
在本教程的前面我们描述了创建一个对象的步骤。当时你可能感觉它有点混淆不清。但是在这里,你将看到它起到的作用,它是非常清晰的。

    第8-10行   我们声明了我们需要的变量。我们将要创建4座墙,因此我们每次会重用相同的变量。由于这些对象都是不动的,我们没必要为他们创建fixture来定义密度以及其他属性。Box2D创建的默认的fixture对于不懂的刚体来说已经非常完美了。

     我们只需要使用一个BodyDefinition,一个Shape,每次让Box2D创建刚体的时候改变他们的参数就行了。把这当作一个可以重用的有顺序的表单,box2D世界通过它创建实际的刚体。但是每个刚体创建后,已获得参数的刚体和表单间就没有联系了。因此,如果你改变了表单,并将它传递给了Box2D世界,你将获得一个新的顺序表单创建的新的刚体。事实上,你可能会经常提交相同的BodyDefinition,box2D会一遍又一遍的创建一个匹配你BodyDefinition的新的刚体。

     下面让我们来看看方块的创建。
     第13行 这里我们定义了新刚体的位置。它接收X,Y的坐标参数。但是为什么会复杂的除两次?因为box2D以米和千克测量它的世界,而我们的游戏用像素测量它的世界。

            理解Box2D中的单位

             Box2D是根据以米和千克为其计量单位来设计的。这非常的方便,因为物理科学也是以米和千克为计量单位的。这能让Box2D对真实世界的现象进行真实的模拟。很明显,我们不能在任何方向都让一米和一像素等价。因此我们需要降低其比例。你可以设定你的比例,让一米等于一像素,但是这会让Box2D的运行效率降低,因为在Box2D中,1米和两米间的差异很大,很明显,但是在1像素与2像素间的差异基本上不可见。

            你必须在用户可见的计量单位与Box2D的计量单位间找到一个平衡点。按照惯例,用Box2D的程序员将30个像素设置为1米,这样对Box2D的性能和精确度来说是一个折中的办法。因此,当你想放一个对象进入Box2D世界的时候,让你的具体的像素位置除以你的比例。下面是如何将坐标转换的公式:        

                    pixels / pixelsPerMeter = box2DMeters;

                    box2DMeters * pixelsPerMeter = pixels;

           请记住最后一点。Box2D刚体的注册点在其几何中心。因此,以他们的左上角为特殊点摆放物体,你会看到那些数字都除了2.比如:如果你想创建一个100像素的正方形Box2D刚体,并将其坐标设为(0,0),你只会在屏幕上看到这个正方形的一半。要想将100x100的方形放置在屏幕的左上方,你需要将其坐标设为(100/2,100/2).

     第14行 在这里我们调用了SetAsBox()函数。这是创建不需要旋转的盒子的快捷函数。如果要创建需要旋转的盒子,就使用SetAsOrientedBox().
     第15行 我们让世界以我们的bodyDefinition为规范创建一个刚体。所有对bodyDefinition的改变都会在我们调用world.CreateBody()的时候发生作用。新建的刚体在我们对其添加东西前只是一个放置几何形状的空容器。从这一刻开始,任何对
bodyDefinition的改变只会影响后续使用world.CreateBody()创建的刚体。
     第16行 最后,我们添加方形给我们的刚体。现在,空的刚体包含了一个长方形。

     接着我们对其它的墙重复这个过程。在这点上,Box2D世界做得很好。不需要其它额外的工作。Box2D通过其内部数据进行模拟,不需要任何舞台上的对象工作。如果你是一个不需要GUI的通过linux命令行进行编程的人,你可以到此结束了,去管理数据库或其它的内容。

     当然,你是一个flash程序员。这意味着不看到它的运行情况就无法进行编程。好了,让我们来添加视觉元素到舞台,看看我们的世界看起来像个什么吧!

        使用DebugDraw来呈现在Box2D世界发生了什么
     使用内置的debug draw 类是呈现我们创建的Box2D世界的最快的方法。这并不奇特,只是一些矢量线和填充块,但这对于只想看看游戏引擎的效果而不担心游戏美工的我们来说已经很完美了。如何在Box2D中使用你自己的图形和影片剪辑不在本教程的讲述范围内。

    下面是添加debug draw的代码:
01.private function makeDebugDraw():void 

02.        { 

03.            // set debug draw  

04.            var debugDraw:b2DebugDraw = new b2DebugDraw(); 

05.            var debugSprite:Sprite = new Sprite(); 

06.            addChild(debugSprite); 

07.            debugDraw.SetSprite(debugSprite); 

08.            debugDraw.SetDrawScale(30.0); 

09.            debugDraw.SetFillAlpha(0.3); 

10.            debugDraw.SetLineThickness(1.0); 

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

12.            world.SetDebugDraw(debugDraw); 

13.        } 

private function makeDebugDraw():void

                {

                        // set debug draw

                        var debugDraw:b2DebugDraw = new b2DebugDraw();

                        var debugSprite:Sprite = new Sprite();

                        addChild(debugSprite);

                        debugDraw.SetSprite(debugSprite);

                        debugDraw.SetDrawScale(30.0);

                        debugDraw.SetFillAlpha(0.3);

                        debugDraw.SetLineThickness(1.0);

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

                        world.SetDebugDraw(debugDraw);

                }
复制代码
要想使用b2DebugDraw 类,我们需要:
      创建b2DebugDraw类得实例,
      赋一个空的影片剪辑给它来进行所有的绘画。
      让世界使用这个b2DebugDraw实例。

    第4行 创建b2DebugDraw 类得实例。
    第5-6行 创建一个影片剪辑并添加到舞台。
    第7-10行 为影片剪辑设一些属性以控制其显示。需要注意的是,SetDrawScale 需要匹配我们的pixelsPerMeter ,以至于我们通过debug draw画出的东西能得到我们预期的状况。
    第11行 SetFlags() 告诉debug draw 我们需要显示的数据。你想要显示出什么只取决于几何(形状)或关节(joints),或其他属性。这个函数值得一些高级项目的二次运用。
    第12行 最后,告诉世界用这个b2DebugDraw 实例来绘制。

    要想在舞台看到东西的最后步骤就是运行Box2D世界,并更新debugDraw。
    我们在update函数中做这些。
01.private function update(e:Event = null):void 

02.        { 

03.            world.Step(timestep, iterations, iterations); 

04.            world.ClearForces(); 

05.            // Render  

06.            world.DrawDebugData(); 

07.        } 

private function update(e:Event = null):void

                {

                        world.Step(timestep, iterations, iterations);

                        world.ClearForces();

                        // Render

                        world.DrawDebugData();

                }
复制代码
这个简单的函数更新b2world,当他被调用后,每隔很短的一段时间进行一次模拟。然后调用calls world.ClearForces()。现在不用担心这行了。在2.1a版本中,它已经变得可有可无了。但是现在你不得不在再次调用Step()前调用ClearForces() 。最后我们用world.DrawDebugdata()将box2D绘制在屏幕上。
    注意update(e:event) 有一个可选的事件参数。这是因为update函数将成为你的主游戏循环。它将会通过一个Timer事件每隔30毫秒左右更新一次。

    现在开始去测试你的动画吧。你会看到四个长方形从你的舞台边缘探出。恭喜,到此你已经迈过了Box2D基础的较困难的一部分。然后准备一下,我们将要添加一些东西到Box2D世界:一些实际物理碰撞。(后续翻译进行中,详见http://blog.csdn.net/cunshuifengyun/article/details/6714403)

热点排行