2048:在一个4X4的方阵中,玩家需要滑动上面的数字,如果俩个数字相邻并且相等,则相加,需要达到2048,方可胜利。
因为在浏览器操作,所以此例的操作方法为:键盘上的w,s,a,d代表上下左右,也可用小键盘左边的上下左右键。
下面给一张游戏截图,也可以点击这里进行试玩:)
IDE:webstorm
好,现在开始讲解我的制作过程。
首先,新建一个项目,名字叫mini2048。我是用终端cocos new Project出来的,所以要删除一些不必要的代码,并且添加一些游戏相关的资源。
因为这个游戏主要是用做demo并且分享制作过程,所以美术啥的也没好好搞,就一个标题的2048字体用了外部的fnt资源。大家粗略看看就行~
添加游戏背景
/*一个灰色的背景和白色的线组成的方格*/ var background = new cc.LayerColor(cc.color(180,170,160,255),size.width,size.height); //灰色背景 this.addChild(background,0); var draw = cc.DrawNode.create(); this.addChild(draw, 1); for(var index=0; index<5; index++){ draw.drawSegment(cc.p(40+index*60, 20), cc.p(40+index*60, 260), 1, cc.color(255, 255, 255, 255)); //竖线 draw.drawSegment(cc.p(40, 20+index*60), cc.p(280, 20+index*60), 1, cc.color(255, 255, 255, 255)); //横线 }
再继续添加一些界面元素,标题,得分,重新游戏什么的
//标题 var title = cc.LabelBMFont.create("2 0 4 8", res.LabelFont_Fnt); title.x = size.width/2; title.y = size.height-40; this.addChild(title,1); //得分 var scoreLabelText = cc.LabelTTF.create("score","Scissor Cuts",20); scoreLabelText.x = 60; scoreLabelText.y = size.height*4/5; scoreLabelText.setColor(cc.color(0,0,0,255)); this.addChild(scoreLabelText,1); this.scoreLabel = cc.LabelTTF.create("0","Scissor Cuts",20); this.scoreLabel.x = 60; this.scoreLabel.y = size.height*4/5-25; this.scoreLabel.setColor(cc.color(0,0,0)); this.addChild(this.scoreLabel,1); //重新游戏 cc.MenuItemFont.setFontName("Arial"); var restartItem = cc.MenuItemFont.create("restart", this.restartGame, this); restartItem.setColor(cc.color(22,100,255)); restartItem.x = size.width - 60; restartItem.y = size.height*4/5-12.5; var restartMenu = cc.Menu.create(restartItem); restartMenu.x = 0; restartMenu.y = 0; this.addChild(restartMenu,1); //结束label var gameOverLabel = cc.LabelTTF.create(" 游戏结束! ","Scissor Cuts",50); gameOverLabel.setColor(cc.color(255,0,255)); gameOverLabel.x = size.width/2; gameOverLabel.y = size.height/2+60; gameOverLabel.visible = false; this.addChild(gameOverLabel,11,5); //通关label var passTheGameLabel = cc.LabelTTF.create(" 恭喜通关!","Scissor Cuts",50); passTheGameLabel.setColor(cc.color(255,255,0)); passTheGameLabel.x = size.width/2; passTheGameLabel.y = size.height/2+60; passTheGameLabel.visible = false; this.addChild(passTheGameLabel,11,6);
随后添加键盘事件来相应w,s,a,d和小键盘旁上下左右键的操作。
if (cc.sys.capabilities.hasOwnProperty('keyboard')) cc.eventManager.addListener({ event: cc.EventListener.KEYBOARD, onKeyReleased:function (key, event) { if(key==[cc.KEY.w] || key==[cc.KEY.up] ){ event.getCurrentTarget().slideUp(); //向上滑 } else if(key==[cc.KEY.a] || key==[cc.KEY.left] ){ event.getCurrentTarget().slideLeft(); //向左滑 } else if(key==[cc.KEY.d] || key==[cc.KEY.right] ){ event.getCurrentTarget().slideRight(); //向右滑 } else if(key==[cc.KEY.s] || key==[cc.KEY.down] ){ event.getCurrentTarget().slideDown(); //向下滑 } } }, this);
然后制作Card类,继承cc.Sprite,用来做游戏中的卡片。我这里的逻辑是这样的,游戏开始前,初始化4X4的16张卡片,并且每张卡片在对应的位置,并且卡片数字都设置为0,
当游戏运作的时候,不移动卡片,只改变对应卡片上的数字,并且对改变后的数字进行判断,如果是0,则隐藏数字,如果是>0的值,则显示数字并根据值来对数字进行美化。
this.backgroundPic = new cc.LayerColor(cc.color(0, 0, 255, 111), 50, 50); //卡片底色 this.labelText = cc.LabelTTF.create("0", "Trebuchet MS", 21); //卡片数字 this.backgroundPic.ignoreAnchorPointForPosition(false); this.addChild(this.backgroundPic, 0); this.addChild(this.labelText,1);
并且给Card类添加一些方法,如设置卡片数字,获取卡片数字的值,对卡片数字大小和卡片背景进行美化等等,因为这些比较简单,所以就不贴码了,尽量避免此博文显得过于冗长。
下面介绍下2048的算法,我觉得也是这部游戏唯一的难点~
因为游戏是上下左右进行滑动,所以只要知道一个方向上是如何运作的,那么也就可以举一反三了,譬如向左滑动这个操作,再对其进行剖析,
我们会发现算法对4排上的数字都是进行同样的操作,所以我们只研究一行数字的算法就可以了。其他的只要循环3次就行。
/*因为算法比较简单,所以我就用注释解释下*/ for(var y=0; y<4; y++){ for(var x=0; x<4; x++){ for(var xRight=x+1; xRight<4; xRight++){ if(game2048array[xRight][y].getCardNumber() != 0){ //game2048array是存放16张卡片的数组,y代表某一行,如果对应卡片右边的卡片数值为0,则对下一个右边的卡片进行循环判断 if(game2048array[x][y].getCardNumber() == 0){ //如果对应卡片右边的值不为0,并且对应卡片的值为0,则将对应卡片的值改为其右边卡片的值 game2048array[x][y].setCardNumber(game2048array[xRight][y].getCardNumber()); game2048array[xRight][y].setCardNumber(0); //然后设置其右边卡片的值为0 this.zeroCardIndex.removeZeroCard(x,y); //this.zeroCardIndex是一个存放值为0的卡片的数组,后面会用到 this.zeroCardIndex.push({coordX:xRight,coordY:y}); x--; } else if(game2048array[x][y].getCardNumber() == game2048array[xRight][y].getCardNumber()){ game2048array[x][y].setCardNumber(game2048array[x][y].getCardNumber()*2); //如果对应卡片右边的值不为0,并且对应卡片的值和其右边卡片的值相等,则对应卡片的值位置为原来的两倍 game2048array[xRight][y].setCardNumber(0); //同样设置其右边卡片的值为0 if(game2048array[x][y].getCardNumber()==2048){ //进行判断,如果对应卡片的值达到2048,则游戏通关 this.gameStop = true; this.passGameLabelAppear(); //通关 } else{ this.zeroCardIndex.push({coordX:xRight,coordY:y}); } } break; //如果对应卡片其右边的卡片值非0,并且对应卡片的值和其右边卡片的值不相等,那么跳出此轮循环,继续判断下一个卡片 } } } }
OK,2048的核心算法已经充分展示,接着,每滑动一次就需要添加一张值为2或者4的卡片。我是这么做的,把游戏中非0卡片的位置都记录到一个数组this.zeroCardIndex中,然后每一次滑动后在此数组中随机取一个元素,
并且在元素上对应的位置信息上添加2或者4的数字。看代码
addOneCard:function(){ var cardIndex = parseInt(Math.random()*this.zeroCardIndex.length); //随机获取一张值为0的卡片 var card = game2048array[this.zeroCardIndex[cardIndex].coordX][this.zeroCardIndex[cardIndex].coordY]; //获取对应位置信息上的卡片 card.setCardNumber(Math.random()<0.2?4:2); //给卡片设置数字 20%的几率会随机到4的卡片 this.zeroCardIndex.splice(cardIndex, 1); //从数组中删除此张卡片 },
最后,对游戏“死亡”进行判定,首先对卡片0的数组的长度(this.zeroCardIndex.lenght)进行判断,如果为0,则判断横向或者竖向的数字,俩俩是不是都不相同,如果是,则表示游戏结束。
isGameOver:function(){ for(var x=0;x<4;x++){ for(var y=0;y<4;y++){ if(x<3 && (game2048array[x][y].getCardNumber()==game2048array[x+1][y].getCardNumber())){ //横向俩俩判断 return false; } if(y<3 && (game2048array[x][y].getCardNumber()==game2048array[x][y+1].getCardNumber())){ //竖向俩俩判断 return false; } } } return true; }
OK!至此,2048的基本游戏逻辑都介绍完毕,剩下的譬如分数累加什么的简单逻辑就不罗列了,第一次写博客教程,大家多多包涵,有不合适的地方尽管吐槽砸墙!
希望每一个游戏人都能实现自己的游戏梦!:)