Chinaunix首页 | 论坛 | 博客
  • 博客访问: 768187
  • 博文数量: 372
  • 博客积分: 10063
  • 博客等级: 中将
  • 技术积分: 4220
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-24 11:36
文章分类

全部博文(372)

文章存档

2012年(372)

分类: 虚拟化

2012-04-28 14:53:15

最近在研究Box2D,能快速上手的中文文档很少,所以翻译了一个经典案例。第一次翻译技术文档,翻译的不好~!欢迎指教
 
 
 
----------------------------------------------------------------------------华丽丽的分割线---------------------------------------------

制作你的第一个HTML 5游戏

最后你将创建的产品:

 

 

HTML 5以超乎任何人想象的速度发展。强大而专业的解决方案已经被开发出来了……甚至是在游戏的世界。今天,你可以利用 和HTML5 的canvas 标签来创建你的第一个游戏。

什么是Box2D?

    Box2D是用来开发游戏和应用的一个流行的开源2D物理引擎。它主要是用C++来写的,已经被很多社区志愿者翻译成为各种语言。

用同样的方法和对象,你可以用各种语言来写你的游戏,比如C(iPhone/iPad),Actionscript 3.0(Web),Html 5(Web) ,等等。

 

第一步-创建项目

 

要开始开发你的例子,首先要下载Html 5 的Box2D引擎。接下来,按照下面所示的结构创建一个新的HTML文件(从box2d-js项目复制js和lib目录到你的游戏目录中)。

 

现在,你还应该在HTML文件中包含一些必须的文件来使用Box2D:

1.    

2.     

3.    

4.     

5.     

6.     

7.     

8.     

9.     

10.      

11.      

12.      

13.      

14.      

15.      

16.      

17.      

18.      

19.      

20.      

21.      

22.      

23.      

24.      

25.      

26.      

27.      

28.      

29.      

30.      

31.      

32.      

33.      

34.      

35.      

36.      

37.      

38.      

39.      

40.      

41.      

42.      

43.      

44.      

45.      

46.      

47.      

48.      

49.      

50.      

51.      

52.      

53.      

54.      

55.      

56.      

57.      

58.      

59.      

60.      

61.      

62.      

63.      

64.      

65.      

66.      

67.      

是的,这是一个数量巨大的HTTP请求!

  请注意,开发的时候,强烈建议你把这些源文件都集中到一个文件中。

接下来,创建两个新的script文件在/js/目录下,分别命名为"box2dutils.js"和"game.js"。

box2dutils.js—这是用一些例子的box2dlib中复制粘贴过来的,它对于绘制的函数很重要。

game.js—这是游戏自身的js。我们在这里创建平台,播放器,应用键盘交互等。

复制粘贴以下代码到box2dutils.js中。不要着急!我一点一点解释!

1.   function drawWorld(world, context) {  

2.       for (var j = world.m_jointList; j; j = j.m_next) {  

3.           drawJoint(j, context);  

4.       }  

5.       for (var b = world.m_bodyList; b; b = b.m_next) {  

6.           for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {  

7.               drawShape(s, context);  

8.           }  

9.       }  

10.     }  

11.     function drawJoint(joint, context) {  

12.         var b1 = joint.m_body1;  

13.         var b2 = joint.m_body2;  

14.         var x1 = b1.m_position;  

15.         var x2 = b2.m_position;  

16.         var p1 = joint.GetAnchor1();  

17.         var p2 = joint.GetAnchor2();  

18.         context.strokeStyle = '#00eeee';  

19.         context.beginPath();  

20.         switch (joint.m_type) {  

21.         case b2Joint.e_distanceJoint:  

22.             context.moveTo(p1.x, p1.y);  

23.             context.lineTo(p2.x, p2.y);  

24.             break;  

25.       

26.         case b2Joint.e_pulleyJoint:  

27.             // TODO  

28.             break;  

29.       

30.         default:  

31.             if (b1 == world.m_groundBody) {  

32.                 context.moveTo(p1.x, p1.y);  

33.                 context.lineTo(x2.x, x2.y);  

34.             }  

35.             else if (b2 == world.m_groundBody) {  

36.                 context.moveTo(p1.x, p1.y);  

37.                 context.lineTo(x1.x, x1.y);  

38.             }  

39.             else {  

40.                 context.moveTo(x1.x, x1.y);  

41.                 context.lineTo(p1.x, p1.y);  

42.                 context.lineTo(x2.x, x2.y);  

43.                 context.lineTo(p2.x, p2.y);  

44.             }  

45.             break;  

46.         }  

47.         context.stroke();  

48.     }  

49.     function drawShape(shape, context) {  

50.         context.strokeStyle = '#000000';  

51.         context.beginPath();  

52.         switch (shape.m_type) {  

53.         case b2Shape.e_circleShape:  

54.             {  

55.                 var circle = shape;  

56.                 var pos = circle.m_position;  

57.                 var r = circle.m_radius;  

58.                 var segments = 16.0;  

59.                 var theta = 0.0;  

60.                 var dtheta = 2.0 * Math.PI / segments;  

61.                 // draw circle  

62.                 context.moveTo(pos.x + r, pos.y);  

63.                 for (var i = 0; i < segments; i++) {  

64.                     var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));  

65.                     var v = b2Math.AddVV(pos, d);  

66.                     context.lineTo(v.x, v.y);  

67.                     theta += dtheta;  

68.                 }  

69.                 context.lineTo(pos.x + r, pos.y);  

70.       

71.                 // draw radius  

72.                 context.moveTo(pos.x, pos.y);  

73.                 var ax = circle.m_R.col1;  

74.                 var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);  

75.                 context.lineTo(pos2.x, pos2.y);  

76.             }  

77.             break;  

78.         case b2Shape.e_polyShape:  

79.             {  

80.                 var poly = shape;  

81.                var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));  

82.                 context.moveTo(tV.x, tV.y);  

83.                 for (var i = 0; i < poly.m_vertexCount; i++) {  

84.                    var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));  

85.                     context.lineTo(v.x, v.y);  

86.                 }  

87.                 context.lineTo(tV.x, tV.y);  

88.             }  

89.             break;  

90.         }  

91.         context.stroke();  

92.     }  

93.       

94.     function createWorld() {  

95.         var worldAABB = new b2AABB();  

96.         worldAABB.minVertex.Set(-1000, -1000);  

97.         worldAABB.maxVertex.Set(1000, 1000);  

98.         var gravity = new b2Vec2(0, 300);  

99.         var doSleep = true;  

100.      var world = new b2World(worldAABB, gravity, doSleep);  

101.      return world;  

102.  }  

103.    

104.  function createGround(world) {  

105.      var groundSd = new b2BoxDef();  

106.      groundSd.extents.Set(1000, 50);  

107.      groundSd.restitution = 0.2;  

108.      var groundBd = new b2BodyDef();  

109.      groundBd.AddShape(groundSd);  

110.      groundBd.position.Set(-500, 340);  

111.      return world.CreateBody(groundBd)  

112.  }  

113.    

114.  function createBall(world, x, y) {  

115.      var ballSd = new b2CircleDef();  

116.      ballSd.density = 1.0;  

117.      ballSd.radius = 20;  

118.      ballSd.restitution = 1.0;  

119.      ballSd.friction = 0;  

120.      var ballBd = new b2BodyDef();  

121.      ballBd.AddShape(ballSd);  

122.      ballBd.position.Set(x,y);  

123.      return world.CreateBody(ballBd);  

124.  }  

125.    

126.  function createBox(world, x, y, width, height, fixed, userData) {  

127.      if (typeof(fixed) == 'undefined') fixed = true;  

128.      var boxSd = new b2BoxDef();  

129.      if (!fixed) boxSd.density = 1.0;  

130.    

131.      boxSd.userData = userData;  

132.    

133.      boxSd.extents.Set(width, height);  

134.      var boxBd = new b2BodyDef();  

135.      boxBd.AddShape(boxSd);  

136.      boxBd.position.Set(x,y);  

137.      return world.CreateBody(boxBd)  

138.    }  

第二步-开发游戏

打开你之前创建的index.html文件,在body元素里添加一个canvas元素(600*400)。这就是我们应用HTML 5绘图的API的地方。

  

当然,到了这里,我们应该插入game.js和box2dutils.js。

1.     

2.     

HTML就是这样!现在我们的乐趣在于Javascript.

打开game.js,插入一下代码:

1.   // some variables that we gonna use in this demo  

2.   var initId = 0;  

3.   var player = function(){  

4.       this.object = null;  

5.       this.canJump = false;  

6.   };  

7.   var world;  

8.   var ctx;  

9.   var canvasWidth;  

10.     var canvasHeight;  

11.     var keys = [];  

12.       

13.     // HTML5 onLoad event  

14.     Event.observe(window, 'load', function() {  

15.         world = createWorld(); // box2DWorld  

16.         ctx = $('game').getContext('2d'); // 2  

17.         var canvasElm = $('game');  

18.         canvasWidth = parseInt(canvasElm.width);  

19.         canvasHeight = parseInt(canvasElm.height);  

20.         initGame(); // 3 

21.         step(); // 4 

22.       

23.     // 5 

24.         window.addEventListener('keydown',handleKeyDown,true);  

25.         window.addEventListener('keyup',handleKeyUp,true);  

26.     });  

Box2DWorld-这是我们存在的地方

好吧,让我们弄清楚这个代码块做什么!

Box2DWorld是Box2D提供的一个核心类。他的功能很简单:

将一切结合到一个类中。在Box2DWorld中,有你的游戏或应用的bodies定义和碰撞管理。

打开game.js和box2dutils.js文件,在box2dutils.js中查找createWorld()函数。

1.   function createWorld() {  

2.       // here we create our world settings for collisions  

3.       var worldAABB = new b2AABB();  

4.       worldAABB.minVertex.Set(-1000, -1000);  

5.       worldAABB.maxVertex.Set(1000, 1000);  

6.       // set gravity vector  

7.       var gravity = new b2Vec2(0, 300);  

8.       var doSleep = true;  

9.       // init our world and return its value  

10.         var world = new b2World(worldAABB, gravity, doSleep);  

11.         return world;  

12.     }  

创建box2DWorld很简单。

回到game.js

参照上述两块代码中的注释数字。

在第二处,我们用选择API获取canvas元素的上下文(看起来像jQuery或者MooTools 选择器,是吗? )

在第三处,有一个新的有趣的函数:initGame(). This is where we create the scenery.

把下面的代码复制粘贴到game.js。我们再一起回头看它。

1.   function initGame(){  

2.       // create 2 big platforms  

3.       createBox(world, 3, 230, 60, 180, true, 'ground');  

4.       createBox(world, 560, 360, 50, 50, true, 'ground');  

5.     

6.       // create small platforms  

7.       for (var i = 0; i < 5; i++){  

8.           createBox(world, 150+(80*i), 360, 5, 40+(i*15), true, 'ground');  

9.       }  

10.       

11.         // create player ball  

12.         var ballSd = new b2CircleDef();  

13.         ballSd.density = 0.1;  

14.         ballSd.radius = 12;  

15.         ballSd.restitution = 0.5;  

16.         ballSd.friction = 1;  

17.         ballSd.userData = 'player';  

18.         var ballBd = new b2BodyDef();  

19.         ballBd.linearDamping = .03;  

20.         ballBd.allowSleep = false;  

21.         ballBd.AddShape(ballSd);  

22.         ballBd.position.Set(20,0);  

23.         player.object = world.CreateBody(ballBd);  

24.       

25.     }  

26.       

27.     Inside box2dutils.js, we've created a function, called createBox. This creates a static rectangle body.  

28.      

29.     function createBox(world, x, y, width, height, fixed, userData) { 

30.         if (typeof(fixed) == 'undefined') fixed = true;  

31.         //1  

32.     var boxSd = new b2BoxDef();  

33.         if (!fixed) boxSd.density = 1.0;  

34.         //2  

35.         boxSd.userData = userData;  

36.         //3  

37.         boxSd.extents.Set(width, height);  

38.       

39.         //4  

40.         var boxBd = new b2BodyDef();  

41.         boxBd.AddShape(boxSd);  

42.         //5  

43.         boxBd.position.Set(x,y);  

44.         //6  

45.         return world.CreateBody(boxBd)  

46.     }  

Box2DBody

一个Box2DBody拥有一些特性:

▪ 它可以是静态的(不受碰撞影响),运动的(但不是由于碰撞,比如你可以用胡彪移动它),或者动态的(与一切交互)。

▪必须有一个形状定义,并且说明这个对象是怎样出现的。

▪必须有至少两个fixture,说明他们碰撞后是怎样交互的。

▪许多引擎中设置的位置是中心,而不是左上角。

回顾这段代码:

  1. 在这里,我们创建了一个形状定义,它可以是一个正方形或者长方形,还设置了它的密度(它被强制移动或旋转的频率)。
  2. 我们设置了userDate,通常我们会设置图形对象,但是在这个例子中,我只设置了字符串作为碰撞对象的类型标识符。这个参数不影响物理算法。
  3. 设置我的box尺寸的一半(从位置点或者中心点到角落的一条线)。
  4. 我们创建了物体的定义并且为它添加了形状定义。
  5. 设置位置。
  6. 在世界中创建物体并且返回它的值。
 

创建球体玩家

我已经直接在game.js的编码中创建了玩家(球)。它和创建方形遵循一样的步骤,但是,这次,它是一个球。

1.   var ballSd = new b2CircleDef();  

2.       ballSd.density = 0.1;  

3.       ballSd.radius = 12;  

4.       ballSd.restitution = 0.5;  

5.       ballSd.friction = 1;  

6.       ballSd.userData = 'player';  

7.       var ballBd = new b2BodyDef();  

8.       ballBd.linearDamping = .03;  

9.       ballBd.allowSleep = false;  

10.         ballBd.AddShape(ballSd);  

11.         ballBd.position.Set(20,0);  

12.         player.object = world.CreateBody(ballBd);  

所以,我们怎样一步一步创建一个物体?

  1. 创建形状,fixtures或者sensor。
  2. 创建物体定义。
  3. 将形状,fixtures或者sensor添加到物体中。(这篇文章中不解释)。
  4. 在世界中创建物体。
 

Box2DCircle

就像我前面提到的,这和创建一个box遵循同样的步骤,但是现在你需要设置一些新的属性。

半径--这是从圆中心到边界上任一点线的长度。

restitution--球在和其它物体碰撞的时候怎样失去或获得force

摩擦—这个球会怎么滚。

Box2DBody的更多属性

damping可以减缓物体速度—有线性阻尼和有角阻尼。

sleep  在box2D中,物体可以通过休眠来解决性能问题。比如,我们假设你现在在开发一个平台游戏,并且是一个6000*400的屏幕上。

为什么我们需要管屏幕外的物体的物理表现?不需要。这就是关键!所以正确的选择是让它们休眠,来提高你的游戏的性能。

我们已经创建了世界;你可以试验目前为止的代码,你会看到玩家落在西边的平台上。

现在,如果你试着跑这个例子,你应该好奇,为什么这个页面和白纸一样简单。

 

任何时候都要记住:Box2D不渲染,它只提供物理计算。

第三步-渲染时间

接下来,让我们来渲染Box2DWorld。

打开game.js,添加下面代码:

1.   function step() {  

2.     

3.       var stepping = false;  

4.       var timeStep = 1.0/60;  

5.       var iteration = 1;  

6.       // 1  

7.       world.Step(timeStep, iteration);  

8.       // 2  

9.       ctx.clearRect(0, 0, canvasWidth, canvasHeight);  

10.         drawWorld(world, ctx);  

11.         // 3  

12.         setTimeout('step()', 10);  

13.        }  

这里我们实现了:

  1. 指示box2dWorld完成物理模拟。
  2. 清空canvas重新绘制。
  3. 10微秒执行一次stup()函数

有了这一小段代码,我们现在可以实现物理和绘制。

你可以自己试验一下,注意看这个落下的小球,跟下面一样。

 

Box2dutils.js里的drawWorld

1.   function drawWorld(world, context) {  

2.       for (var j = world.m_jointList; j; j = j.m_next) {  

3.           drawJoint(j, context);  

4.       }  

5.       for (var b = world.m_bodyList; b; b = b.m_next) {  

6.           for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {  

7.               drawShape(s, context);  

8.           }  

9.       }  

10.     }  

我们上面写的是一个调试函数,利用html5中绘图的API将我们的世界画到canvas这块画布上。

第一层循环绘制所有的关节。我们在这里并没有用到关节,它们对于第一个例子来说有点复杂,但是,不管怎样,它们对于游戏是必不可少的。它们让你能创建非常有趣的物体。

第二层循环绘制所有的物体。

1.   function drawShape(shape, context) {  

2.       context.strokeStyle = '#000000';  

3.       context.beginPath();  

4.       switch (shape.m_type) {  

5.       case b2Shape.e_circleShape:  

6.           {  

7.               var circle = shape;  

8.               var pos = circle.m_position;  

9.               var r = circle.m_radius;  

10.                 var segments = 16.0;  

11.                 var theta = 0.0;  

12.                 var dtheta = 2.0 * Math.PI / segments;  

13.                 // draw circle  

14.                 context.moveTo(pos.x + r, pos.y);  

15.                 for (var i = 0; i < segments; i++) {  

16.                     var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));  

17.                     var v = b2Math.AddVV(pos, d);  

18.                     context.lineTo(v.x, v.y);  

19.                     theta += dtheta;  

20.                 }  

21.                 context.lineTo(pos.x + r, pos.y);  

22.       

23.                 // draw radius  

24.                 context.moveTo(pos.x, pos.y);  

25.                 var ax = circle.m_R.col1;  

26.                 var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);  

27.                 context.lineTo(pos2.x, pos2.y);  

28.             }  

29.             break;  

30.         case b2Shape.e_polyShape:  

31.             {  

32.                 var poly = shape;  

33.                var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));  

34.                 context.moveTo(tV.x, tV.y);  

35.                 for (var i = 0; i < poly.m_vertexCount; i++) {  

36.                    var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));  

37.                     context.lineTo(v.x, v.y);  

38.                 }  

39.                 context.lineTo(tV.x, tV.y);  

40.             }  

41.             break;  

42.         }  

43.         context.stroke();  

44.     }  

我们遍历对象的每一个顶点,并用线绘制(Context.MoveTo和Context.LineTo)。现在,它对于一个例子很有用,但是在实践中并不是很有用。当你使用图形的时候,你只需要注意物体位置,并不需要像这个项目一样去遍历顶点。

第四步—交互

一个游戏没有交互就是电影,而如果一个电影有交互就成了一个游戏。(O(╯□╰)o,强烈不赞同!)

让我们来写方向键控制跳和移动的功能。

添加这些代码到game.js

1.   function handleKeyDown(evt){  

2.       keys[evt.keyCode] = true;  

3.   }  

4.     

5.   function handleKeyUp(evt){  

6.       keys[evt.keyCode] = false;  

7.   }  

8.     

9.   // disable vertical scrolling from arrows :)  

10.     document.onkeydown=function(){return event.keyCode!=38 && event.keyCode!=40}  

我们设置了一个数组来记录用户的每一个按键操作handleKeyDown和handleKeyUp。通过 document.onKeydown,我们禁止了浏览器本身上下键的滚屏功能。你以前玩过这样的HTML 5游戏吗?当你跳的时候,玩家,障碍和物体全部从屏幕小时?现在这不再是一个问题。

将下面这小段代码添加到stup()函数的开头。

1.   handleInteractions();  

function handleInteractions(){  

1.       // up arrow  

2.       // 1  

3.       var collision = world.m_contactList;  

4.       player.canJump = false;  

5.       if (collision != null){  

6.          if (collision.GetShape1().GetUserData() == 'player' || collision.GetShape2().GetUserData() == 'player'){  

7.              if ((collision.GetShape1().GetUserData() == 'ground' || collision.GetShape2().GetUserData() == 'ground')){  

8.                  var playerObj = (collision.GetShape1().GetUserData() == 'player' ? collision.GetShape1().GetPosition() :  collision.GetShape2().GetPosition());  

9.                  var groundObj = (collision.GetShape1().GetUserData() == 'ground' ? collision.GetShape1().GetPosition() :  collision.GetShape2().GetPosition());  

10.                     if (playerObj.y < groundObj.y){  

11.                         player.canJump = true;  

12.                     }  

13.                 }  

14.             }  

15.         }  

16.         // 2  

17.         var vel = player.object.GetLinearVelocity();  

18.         // 3  

19.         if (keys[38] && player.canJump){  

20.             vel.y = -150;  

21.         }  

22.       

23.         // 4  

24.         // left/right arrows  

25.         if (keys[37]){  

26.             vel.x = -60;  

27.         }  

28.         else if (keys[39]){  

29.             vel.x = 60;  

30.         }  

31.       

32.         // 5  

33.         player.object.SetLinearVelocity(vel);  

34.     }  

上面的代码最复杂的是第一块。我们检查碰撞,写了一个条件来判断shape1和shape2是否是玩家,如果是的话,我 们再判断shape1或者shape2是不是地面物体。如果是的话,那么玩家就在撞击地面。接下来,我们检查玩家是否在地面上,如果是的话,那么玩家可以 跳。

第二块那行代码,我们检索玩家的LinearVelocity。

第三块和第四块代码验证是否按下了方向键,并且调整速度矢量。因此在第五块代码里,我们为玩家设置了新的速度矢量。

交互就做完了!但是我们没有目标,只是跳,跳,跳……跳!

第五步—“你赢了”消息

在你的LinearVelocity的开头添加下面代码

1.   if (player.object.GetCenterPosition().y > canvasHeight){  

2.       player.object.SetCenterPosition(new b2Vec2(20,0),0)  

3.   }  

4.   else if (player.object.GetCenterPosition().x > canvasWidth-50){  

5.       showWin();  

6.       return;  

7.   }  

第一个条件判断是否落下了,是否需要返回起点(西边平台)。

第二个条件检查玩家是否到达第二个平台赢得游戏。下面是showWin()函数

1.   function showWin(){  

2.       ctx.fillStyle    = '#000';  

3.       ctx.font         = '30px verdana';  

4.       ctx.textBaseline = 'top';  

5.       ctx.fillText('Ye! you made it!', 30, 0);  

6.       ctx.fillText('thank you, andersonferminiano.com', 30, 30);  

7.       ctx.fillText('@andferminiano', 30, 60);  

8.   }  

就是这样!你刚才完成了一个简单的用HTML 5和Box2D写的游戏,恭喜你!

阅读(625) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~