这一次先来看一个经典的推箱子游戏,相信大家也都知道这款游戏,推箱子游戏最早源于日本,是一款极其锻炼逻辑思考能力的游戏,箱子只能推不能拉,玩家必须在一个有限的空间里,将所有的箱子归位,如下图所示。
这是我用最新版lufylegend.js引擎开发的,想挑战一下的朋友,可以点击下面的游戏链接试一下自己能通过几关。
http://lufylegend.com/demo/box
游戏一共6关,我在游戏里加入了排名系统,每过一关可以上传自己的成绩,跟大家比拼一下,或者您也可以将自己的过关方法心得等回复到文章下面。
制作开始
好了,废话说完了,现在来看看如何来制作这款游戏。
一,首先,你需要下载lufylegend.js引擎
下面是我在博客的lufylegend-1.6.0发布帖
http://blog.csdn.net/lufy_legend/article/details/8593968
下面一步步来进入开发正题。
二,绘制背景和箱子
我们先来准备一张图,
如果我们将上面的图平均分割成5份,那么他们的序号分别为0,1,2,3,4。
我们利用上面5个小图片就可以拼接任意的房间以及房间内箱子的摆放。
比如,我博客一开始的示例图1,它是游戏第一关的截图,要绘制这个房间,首先要知道这些图片应该摆放的位置,我们预先准备一个数组。
[javascript] view plaincopy
1. var stage01 = [
2. [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
3. [-1,-1, 1, 1, 1, 1, 1, 1,-1,-1,-1],
4. [-1,-1, 1, 0, 0, 0, 0, 1, 1,-1,-1],
5. [-1,-1, 1, 0, 0, 4, 0, 0, 1,-1,-1],
6. [-1,-1, 1, 4, 4, 0, 4, 4, 1,-1,-1],
7. [-1,-1, 1, 0, 0, 4, 0, 0, 1,-1,-1],
8. [-1,-1, 1, 1, 0, 0, 0, 0, 1,-1,-1],
9. [-1,-1,-1, 1, 1, 1, 1, 1, 1,-1,-1],
10. [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
11. ];
上面的-1表示不摆放,然后0,1,4等分别对应着图2中的序号。
按照这个数组来绘制房间就简单了,看下面的函数
[javascript] view plaincopy
1. function drawFloor(x,y){
2. if(list[y][x] < 0)return;
3. var bitmap = new LBitmap(bitmapDataList[list[y][x]]);
4. bitmap.x = x*STEP;
5. bitmap.y = y*STEP;
6. boxLayer.addChild(bitmap);
7. }
这个list数组就是上面的stage01数组,参数x,y分别是数组的列和行的序号,STEP是每个小图片的长度,绘制一个小图块,其实我就是建立了一个LBitmap对象。
关于LSprite,LBitmap等对象是lufylegend.js引擎中很常用的对象,关于它们的用法,请大家参照官方的API文档,或者看我以前的一些文章来了解一下,这里不加赘述了。
当然,一开始,房间里要有箱子,箱子一开始的位置也是需要预先设置好的,我们同样建立一个数组。
[javascript] view plaincopy
1. var box01 = [
2. {x:3,y:3},
3. {x:4,y:3},
4. {x:5,y:3},
5. {x:5,y:5},
6. {x:6,y:5},
7. {x:7,y:5}
8. ];
绘制箱子的函数如下
[javascript] view plaincopy
1. function drawBox(){
2. var bitmap;
3. for(var i=0;i 4. bitmap = new LBitmap(bitmapDataList[2]); 5. bitmap.x = boxList[stageIndex][i].x*STEP; 6. bitmap.y = boxList[stageIndex][i].y*STEP; 7. boxLayer.addChild(bitmap); 8. nowBoxList.push(bitmap); 9. } 10. }
上面,我同样利用LBitmap对象来显示这些箱子,nowBoxList数组用来储存这些已经加载到游戏界面上的箱子对象,之后用来判断游戏过关。
因为stage01数组中的4就表示箱子需要还原的位置,所以在判断游戏是否过关的时候,只需要判断下这些位置是否与所有箱子的位置重合,判断方法如下。
[javascript] view plaincopy
1. function checkBox(){
2. var bitmap,x,y,win=true;
3. list = [];
4. for(var i=0;i 5. list.push(stageList[stageIndex][i].join(",").split(",")); 6. } 7. 8. for(var i=0;i 9. bitmap = nowBoxList[i]; 10. x = bitmap.x / STEP; 11. y = bitmap.y / STEP; 12. if(list[y][x] == 4){ 13. bitmap.bitmapData = bitmapDataList[3]; 14. }else{ 15. bitmap.bitmapData = bitmapDataList[2]; 16. win = false; 17. } 18. list[y][x] += 10; 19. } 20. if(win)gameClearShow(); 21. }
代码我是直接从程序中截取的,所以出现了些新的数组和对象。
stageList储存了所有的关卡信息,stageIndex是当前关卡的序号,stageList[stageIndex]就可以获取当前关卡的信息,bitmapDataList数组储存了图1中小图片的LBitmapData对象,这些暂且不说,关键是下面。
[javascript] view plaincopy
1. if(list[y][x] == 4){
2. bitmap.bitmapData = bitmapDataList[3];
3. }else{
4. bitmap.bitmapData = bitmapDataList[2];
5. win = false;
6. }
函数中循环了所有箱子的位置,如果他们的位置的序号为4,则表示该箱子已经归位,全部归位表示过关,否则游戏继续,返回false。
三,主人公登场,推动箱子。
同样准备一张图片,如下
人物走动动画,当然就需要lufylegend引擎中另一个重要的对象LAnimation,它专门用来顺序播放图片以形成动画,具体用法请参照官方API文档。
下面是主人公的构造器
[javascript] view plaincopy
1. /**
2. * 循环事件
3. * @param data 图片数据
4. * @param row 图片分割行数
5. * @param col 图片分割列数
6. **/
7. function Character(data,row,col){
8. base(this,LSprite,[]);
9. var self = this;
10. //设定人物动作速度
11. self.speed = 2;
12. self.speedIndex = 0;
13. //设定人物大小
14. data.setProperties(0,0,data.image.width/col,data.image.height/row);
15. //得到人物图片拆分数组
16. var list = LGlobal.divideCoordinate(data.image.width,data.image.height,row,col);
17. //设定人物动画
18. self.anime = new LAnimation(this,data,list);
19. //设定不移动
20. self.move = false;
21. //在一个移动步长中的移动次数设定
22. self.moveIndex = 0;
23. };
主人公如何推动箱子,看下面的onmove函数
[javascript] view plaincopy
1. /**
2. * 开始移动
3. **/
4. Character.prototype.onmove = function (){
5. var self = this;
6. //设定一个移动步长中的移动次数
7. var ml_cnt = 4;
8. //计算一次移动的长度
9. var ml = STEP/ml_cnt;
10. //根据移动方向,开始移动
11. switch (self.direction){
12. case UP:
13. self.y -= ml;
14. if(box)box.y -= ml;
15. break;
16. case LEFT:
17. self.x -= ml;
18. if(box)box.x -= ml;
19. break;
20. case RIGHT:
21. self.x += ml;
22. if(box)box.x += ml;
23. break;
24. case DOWN:
25. self.y += ml;
26. if(box)box.y += ml;
27. break;
28. }
29. self.moveIndex++;
30. //当移动次数等于设定的次数,开始判断是否继续移动
31. if(self.moveIndex >= ml_cnt){
32. self.moveIndex = 0;
33. box = null;
34. self.move = false;
35. checkBox();
36. }
37. };
可以看到,箱子是不是跟着主人公一起走,关键是要看box这个变量,这个变量的值是在下面的checkRoad函数中设置的。
[javascript] view plaincopy
1. Character.prototype.checkRoad = function (dir){
2. var self = this;
3. var tox,toy;
4. //开始计算移动目的地的坐标
5. switch (dir){
6. case UP:
7. tox = 0;
8. toy = -1;
9. break;
10. case LEFT:
11. tox = -1;
12. toy = 0;
13. break;
14. case RIGHT:
15. tox = 1;
16. toy = 0;
17. break;
18. case DOWN:
19. tox = 0;
20. toy = 1;
21. break;
22. }
23. if(list[self.y/STEP + toy][self.x/STEP + tox]==1)return false;
24. if(list[self.y/STEP + toy][self.x/STEP + tox]>4){
25. if(list[self.y/STEP + toy*2][self.x/STEP + tox*2]==1 || list[self.y/STEP + toy*2][self.x/STEP + tox*2]>4)return false;
26. box = getBox(self.x + tox*STEP,self.y + toy*STEP);
27. }
28. return true;
29. };
其实,就是判断一下主人公要走的方向的前方是不是有障碍物,如果障碍物是墙则不可移动,如果是箱子的话,就要看看箱子的后面是不是有障碍物,如果有则不可移动,否则箱子就需要跟着主人公一起移动,将box设置为主人公前方的箱子即可。
上面这个函数,是在人物即将发生移动的时候被调用的,如下。
[javascript] view plaincopy
1. /**
2. * 改变人物方向,并判断是否可移动
3. **/
4. Character.prototype.changeDir = function (dir){
5. var self = this;
6. if(self.move)return;
7. self.direction = dir;
8. self.anime.setAction(dir);
9. if(!self.checkRoad(dir))return;
10. self.move = true;
11. steps.text = parseInt(steps.text) + 1;
12. };
当图1中的方向图标被按下的时候,根据点击的方向,来实现人物的移动。
点击方向图标的方法当然是鼠标事件
[javascript] view plaincopy
1. ctrlLayer.addEventListener(LMouseEvent.MOUSE_UP,onCtrl);
然后在onCtrl函数中根据点击的位置,来进行相应的移动。
[javascript] view plaincopy
1. function onCtrl(event){
2. var ctrlSize = 60;
3. if(event.selfX >= ctrlSize && event.selfX <= ctrlSize*2){
4. if(event.selfY >= 0 && event.selfY <= ctrlSize){
5. player.changeDir(UP);
6. }else if(event.selfY >= ctrlSize*2 && event.selfY <= ctrlSize*3){
7. player.changeDir(DOWN);
8. }
9. }else if(event.selfY >= ctrlSize && event.selfY <= ctrlSize*2){
10. if(event.selfX >= 0 && event.selfX <= ctrlSize){
11. player.changeDir(LEFT);
12. }else if(event.selfX >= ctrlSize*2 && event.selfX <= ctrlSize*3){
13. player.changeDir(RIGHT);
14. }
15. }
16. }
这样,游戏的主要功能部分,就介绍完了。
四,建一个开始画面
使用lufylegend.js引擎做个界面,可以说毫无难度,代码如下。
[javascript] view plaincopy
1. function GameLogo(){
2. base(this,LSprite,[]);
3. var self = this;
4.
5. var logolist = [[1,1,1,1],[1,2,4,1],[1,4,2,1],[1,1,1,1]];
6. var bitmap,logoLayer;
7.
8. logoLayer = new LSprite();
9. logoLayer.graphics.drawRect(6,"#FF7F50",[0,0,LGlobal.width,LGlobal.height],true,"#FFDAB9");
10. self.addChild(logoLayer);
11.
12. logoLayer = new LSprite();
13. logoLayer.x = 50;
14. logoLayer.y = 50;
15. for(var i=0;i 16. for(var j=0;j 17. bitmap = new LBitmap(bitmapDataList[logolist[i][j]]); 18. bitmap.x = j*STEP; 19. bitmap.y = i*STEP; 20. logoLayer.addChild(bitmap); 21. } 22. } 23. bitmap = new LBitmap(new LBitmapData(imglist["player"],0,0,STEP,STEP)); 24. bitmap.x = STEP; 25. bitmap.y = 2*STEP; 26. logoLayer.addChild(bitmap); 27. self.addChild(logoLayer); 28. 29. labelText = new LTextField(); 30. labelText.rotate = -20; 31. labelText.color = "#4B0082"; 32. labelText.font = "HG行書体"; 33. labelText.size = 100; 34. labelText.x = 300; 35. labelText.y = 50; 36. labelText.stroke = true; 37. labelText.lineWidth = 4; 38. labelText.text = "推"; 39. self.addChild(labelText); 40. 41. labelText = new LTextField(); 42. labelText.color = "#4B0082"; 43. labelText.font = "HG行書体"; 44. labelText.size = 100; 45. labelText.x = 450; 46. labelText.y = 60; 47. labelText.stroke = true; 48. labelText.lineWidth = 4; 49. labelText.text = "箱"; 50. self.addChild(labelText); 51. 52. labelText = new LTextField(); 53. labelText.rotate = 20; 54. labelText.color = "#4B0082"; 55. labelText.font = "HG行書体"; 56. labelText.size = 100; 57. labelText.x = 600; 58. labelText.y = 60; 59. labelText.stroke = true; 60. labelText.lineWidth = 4; 61. labelText.text = "子"; 62. self.addChild(labelText); 63. 64. labelText = new LTextField(); 65. labelText.color = "#B22222"; 66. labelText.font = "HG行書体"; 67. labelText.size = 40; 68. labelText.x = 100; 69. labelText.y = 250; 70. labelText.stroke = true; 71. labelText.lineWidth = 4; 72. labelText.text = "Click to Start Game !!"; 73. self.addChild(labelText); 74. 75. var social = new Social(); 76. social.x = 220; 77. social.y = 330; 78. self.addChild(social); 79. 80. labelText = new LTextField(); 81. labelText.font = "HG行書体"; 82. labelText.size = 14; 83. labelText.x = 400; 84. labelText.y = 390; 85. labelText.text = "- Html5 Game Engine lufylegend.js"; 86. self.addChild(labelText); 87. labelText = new LTextField(); 88. labelText.color = "#006400"; 89. labelText.font = "HG行書体"; 90. labelText.size = 14; 91. labelText.x = 400; 92. labelText.y = 410; 93. labelText.text = "http://www.lufylegend.com/lufylegend"; 94. self.addChild(labelText); 95. 96. self.addEventListener(LMouseEvent.MOUSE_UP,menuShow); 97. };
就是显示几张图片,以及添加一些文字,LTextField对象的使用方法请参考官方API文档。
五,建一个选择画面
如下。
图5
代码如下。
[javascript] view plaincopy
1. function GameMenu(){
2. base(this,LSprite,[]);
3. var self = this;
4.
5. var menuLayer;
6. menuLayer = new LSprite();
7. menuLayer.graphics.drawRect(6,"#ADD8E6",[0,0,LGlobal.width,LGlobal.height],true,"#E6E6FA");
8. self.addChild(menuLayer);
9.
10. labelText = new LTextField();
11. labelText.color = "#B22222";
12. labelText.font = "HG行書体";
13. labelText.size = 40;
14. labelText.x = 200;
15. labelText.y = 30;
16. labelText.stroke = true;
17. labelText.lineWidth = 4;
18. labelText.text = "Please select !!";
19. menuLayer.addChild(labelText);
20. for(var i=0;i 21. self.stageVsMenu(stageMenu[i]); 22. } 23. }; 24. GameMenu.prototype.stageVsMenu = function(obj){ 25. var self = this; 26. 27. var menuButton,btn_up; 28. if(obj.open){ 29. btn_up = new LSprite(); 30. btn_up.graphics.drawRect(2,"#000",[0,0,150,90],true,"#191970"); 31. labelText = new LTextField(); 32. labelText.color = "#ffffff"; 33. labelText.font = "HG行書体"; 34. labelText.size = 20; 35. labelText.x = 40; 36. labelText.y = 5; 37. btn_up.addChild(labelText) 38. labelText.text = "第"+(obj.index+1)+"关"; 39. 40. labelText = new LTextField(); 41. labelText.color = "#ffffff"; 42. labelText.font = "HG行書体"; 43. labelText.size = 12; 44. labelText.x = 10; 45. labelText.y = 40; 46. btn_up.addChild(labelText) 47. labelText.text = "step:"+obj.step; 48. labelText = new LTextField(); 49. labelText.color = "#ffffff"; 50. labelText.font = "HG行書体"; 51. labelText.size = 12; 52. labelText.x = 10; 53. labelText.y = 60; 54. btn_up.addChild(labelText) 55. labelText.text = "times:"+obj.times; 56. 57. 58. var btn_down = new LSprite(); 59. btn_down.graphics.drawRect(2,"#000",[0,0,150,90],true,"#2F4F4F"); 60. labelText = new LTextField(); 61. labelText.color = "#ffffff"; 62. labelText.font = "HG行書体"; 63. labelText.size = 20; 64. labelText.x = 40; 65. labelText.y = 5; 66. labelText.text = "第"+(obj.index+1)+"关"; 67. btn_down.addChild(labelText); 68. 69. labelText = new LTextField(); 70. labelText.color = "#ffffff"; 71. labelText.font = "HG行書体"; 72. labelText.size = 12; 73. labelText.x = 10; 74. labelText.y = 40; 75. btn_down.addChild(labelText) 76. labelText.text = "step:"+obj.step; 77. labelText = new LTextField(); 78. labelText.color = "#ffffff"; 79. labelText.font = "HG行書体"; 80. labelText.size = 12; 81. labelText.x = 10; 82. labelText.y = 60; 83. btn_down.addChild(labelText) 84. labelText.text = "times:"+obj.times; 85. 86. menuButton = new LButton(btn_up,btn_down); 87. menuButton.obj = obj; 88. menuButton.addEventListener(LMouseEvent.MOUSE_UP,function(event,self){ 89. gameStart(self.obj.index); 90. }); 91. }else{ 92. btn_up = new LSprite(); 93. btn_up.graphics.drawRect(2,"#000",[0,0,150,90],true,"#708090"); 94. labelText = new LTextField(); 95. labelText.color = "#ffffff"; 96. labelText.font = "HG行書体"; 97. labelText.size = 20; 98. labelText.x = 40; 99. labelText.y = 5; 100. btn_up.addChild(labelText) 101. labelText.text = "???"; 102. menuButton = btn_up; 103. }; 104. self.addChild(menuButton); 105. menuButton.x = obj.x * 220 + 100; 106. menuButton.y = obj.y * 140 + 130; 107. }
好了,游戏主要的代码已经都贴出来了。