最后你将创建的产品:
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
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
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
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,说明他们碰撞后是怎样交互的。
许多引擎中设置的位置是中心,而不是左上角。
回顾这段代码:
在这里,我们创建了一个形状定义,它可以是一个正方形或者长方形,还设置了它的密度(它被强制移动或旋转的频率)。
我们设置了userDate,通常我们会设置图形对象,但是在这个例子中,我只设置了字符串作为碰撞对象的类型标识符。这个参数不影响物理算法。
设置我的box尺寸的一半(从位置点或者中心点到角落的一条线)。
我们创建了物体的定义并且为它添加了形状定义。
设置位置。
在世界中创建物体并且返回它的值。
创建球体玩家
我已经直接在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);
所以,我们怎样一步一步创建一个物体?
创建形状,fixtures或者sensor。
创建物体定义。
将形状,fixtures或者sensor添加到物体中。(这篇文章中不解释)。
在世界中创建物体。
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. }
这里我们实现了:
指示box2dWorld完成物理模拟。
清空canvas重新绘制。
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
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
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.οnkeydοwn=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
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写的游戏,恭喜你!