2048在一个4X4的方阵中,玩家需要滑动上面的数字,如果俩个数字相邻并且相等,则相加,需要达到2048,方可胜利。

因为在浏览器操作,所以此例的操作方法为:键盘上的w,s,a,d代表上下左右,也可用小键盘左边的上下左右键。

下面给一张游戏截图,也可以点击这里进行试玩:)

【cocos2d-js 3.0】制作2048-LMLPHP

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的基本游戏逻辑都介绍完毕,剩下的譬如分数累加什么的简单逻辑就不罗列了,第一次写博客教程,大家多多包涵,有不合适的地方尽管吐槽砸墙!

希望每一个游戏人都能实现自己的游戏梦!:)

04-14 08:55