三消项目相比之前的拼图项目,逻辑上要稍微复杂一些.拓展方面的难度也更大.拆解开来看,主要需要搞清楚五个逻辑:

一,数据与显示分离逻辑:

俗话说万事开头难,是因为想把事儿办成,想让路走的更远,就需要前期做好十足的准备,确保万无一失;三消游戏的开局很好的说明了这样一点.
a,首先需要搭建界面,首先加载一张背景图片作为背景,然后给每个方块添加一个透明方框.这个过程当中需要注意精灵的加载顺序,和坐标的计算.这一段不算复杂.
b,所有的方块都会已精灵的形式加载,但是所有的方块在游戏的过程当中会不断的改变显示图片,并且每一个方块都会有自己的点击事件,以及点击后的处理逻辑.显然比起精灵类Sprite,我们的方块有更多特有的属性,所以我们需要自定义一个继承于Sprite的派生类,用于创建我们后续需要使用的方块精灵对象.代码如下:

”Shard.js”

var Shard = cc.Sprite.extend({//碎片显示类型type:0,listener:null,ctor:function() {this._super();var that = this;var listener = cc.EventListener.create({event: cc.EventListener.TOUCH_ONE_BY_ONE,swallowTouches: true,onTouchBegan: function (touch, event) {if(!that.visible)   return false;var target = event.getCurrentTarget();var location = target.convertToNodeSpace(touch.getLocation());var targetSize = target.getContentSize();//{width:100,height:100}var targetRectangle = cc.rect(0, 0, targetSize.width, targetSize.height);if (cc.rectContainsPoint(targetRectangle, location)) {cc.eventManager.dispatchCustomEvent(USER_CLICK_SHARD_EVENT, that);return true;}return false;},});this.listener = cc.eventManager.addListener(listener.clone(), this);},});

c:所有的精灵您按位置加载好以后,其实只是一个空节点,因为我们在创建自定义的shard对象时,并不会想创建普通的Sprite对象那样传一个图片路径过去用于渲染精灵,之所以如此是因为精灵的显示我们要写成一个函数,以便我们可以随时调用,由此决定什么时候让方块显示,显示哪张图片.所以我们给shard类添加了一个成员函数setType(type),该函数接收一个参数type,type中保存的是用于渲染当前精灵的图片名的数字后缀.(能这样用的前提是我们的图片名称有统一的格式,如icon_1.png, icon_2.png….),代码如下:

”Shard.js”

//设置显示状态setType:function(_type){this.type = _type;if(_type == 0){this.visible = false;return}else{this.visible = true;this.initWithFile("res/icon1_"+this.type+".png");}},

d,该项目设计中有一个重要的思想,叫数据与显示分离.所谓兵马未动,粮草先行.显示只是驱壳,背后驱动显示的数据才是灵魂.所以我们所有的显示都是在数据处理完毕之后.所以我们会在游戏的主逻辑脚本中定义两个数组,一个用于存储数据(就是每个方块的图片名中的数字后缀),另一个数组用于存储我们自定义的精灵对象即方块.两个数组中的元素时一一对应的,即显示上的位置和数组中的下标对应.该部分代码如下:

”game.js”

var SHARD_TYPE = 8;      //方块种类
var SHARD_NUM_W = 8;     //横向碎片数量
var SHARD_NUM_H = 8;    //纵向碎片数量
var DELAY_TIME = 500;       //操作间隔时间
var USER_CLICK_SHARD_EVENT = 'user_click_shard_event';  //自定义事件名称var GameLayer = cc.Layer.extend({//碎片数据数组shard_date_arr:[],//碎片精灵数组shard_sprite_arr:[],//碎片边长shard_width:0,//是否有碎片在下落isInDrop:false,//已经被选中的碎片oldShard:null,//游戏结束状态标签isGameOver:null,ctor: function () {this._super();this.shard_width = (new cc.Sprite(res.icon_1)).width;this.shard_date_arr = Creat2X2Arr(SHARD_NUM_W, SHARD_NUM_H, 1);this.shard_sprite_arr = Creat2X2Arr(SHARD_NUM_W, SHARD_NUM_H, 0);//初始化游戏this.initData();//创建一个即不存在三消也不是死局的开局界面this.buildWithout3Arr();//数据处理完毕可以显示界面,开始游戏this.flushShardShowWithArr();//自定义事件cc.eventManager.addCustomListener(USER_CLICK_SHARD_EVENT, this._getTouchOneShard.bind(this));},//初始化游戏initData:function(){this.isGameOver = false;var _size = cc.winSize;var basePoint = cc.p((_size.width - this.shard_width *SHARD_NUM_W + this.shard_width )/2,(_size.height + this.shard_width *SHARD_NUM_H - this.shard_width )/2);for(var i=0;i<SHARD_NUM_W;i++) {for(var j=0;j<SHARD_NUM_H;j++) {//计算左上角基准点坐标var pos = cc.p(basePoint.x + i * this.shard_width, basePoint.y - j * this.shard_width);//添加背景框var bgBoard = new cc.Sprite('res/bg1.png');bgBoard.setPosition(pos);bgBoard.setOpacity(150);this.addChild(bgBoard,1);//添加显示下标的文本(调试用,需要显示下标时取消注释)//var indexLable = new cc.LabelTTF(i+","+j,'',10);//indexLable.enableStroke(cc.color(255,0,0,255),1);//bgBoard.addChild(indexLable);//创建游戏结束标签this.gameoverLable = new cc.LabelTTF('GAME OVER','',150);this.gameoverLable.enableStroke(cc.color(255,0,0,200),1);this.addChild(this.gameoverLable,3);this.gameoverLable.setPosition(cc.p(_size.width/2,_size.height/2));this.gameoverLable.visible = false;//创建方块精灵var shard = new Shard();shard.setPosition(pos);this.addChild(shard,2);//图片数据域精灵对象同步保存到对应容器this.shard_sprite_arr[i][j] = shard;this.shard_date_arr[i][j] = parseInt(Math.random()*SHARD_TYPE + 1);}}},
});

上面的initData方法,只是完成了数据的初始化,如果要显示方块我们只需要用数据数组shard_date_arr,去刷精灵数组shard_sprite_arr中每一个方块的显示即可,在game.js中添加显示的函数flushShardShowWithArr:

”game.js”

//遍历二维数组,刷新显示flushShardShowWithArr: function () {for (var i = 0; i < SHARD_NUM_W; i++)for (var j = 0; j < SHARD_NUM_H; j++)this.shard_sprite_arr[i][j].setType(this.shard_date_arr[i][j]);}

这样,创建好的方块精灵,就会按照我们准备好的数据,显示指定的图片.但是到这里我们只是完成了最基本的准备工作.要显示方块还为时过早,因为我们的数据可能还有一些问题,比如显示出来有三个连在一起肯定不行,死局更不行,所以我们要现将这两个雷给排除,就有了我们接下来的,三消检测逻辑和死局检测逻辑,这两步检测任意一个不通过游戏都不可以开始,所以我们检测的过程大概就是这样:检查是否有三消,如果有找到它们并刷新它们的值,然后复检,如果没有三消,继续检查是否为死局,如果是刷新所有数据,然后复检,注意,每一次复检都要同时检查是否有三消和是否为死局,只有当两个检测都通过才可以显示方块,开始游戏,数据处理部分代码如下:

”game.js”

 //创建一个不存在三消的数组buildWithout3Arr:function() {//检测是否存在三消块,函数返回一个数组,数组中保存搜索到的三消块的下标;var returnArr = this.checkArrHas3(this.shard_date_arr);//根据返回的数组的长度来判定当前局面是否存在三消块,如果有,则重刷三消块的显示数据,再递归复检;if(returnArr.length > 0) {for(var index in returnArr) {var randomPo = returnArr[index];this.shard_date_arr[randomPo.x][randomPo.y] = parseInt(Math.random()*SHARD_TYPE + 1)}this.buildWithout3Arr();return;}//判断是否为死局,如果是,则刷新所有显示数据,并递归复检if(this.checkIsDeath()) {for(var i=0;i<SHARD_NUM_W;i++) {for(var j=0;j<SHARD_NUM_H;j++) {this.shard_date_arr[i][j] = parseInt(Math.random()*SHARD_TYPE + 1);}}this.buildWithout3Arr();return;}},

二,3消检测逻辑:

检查有没有三消块,其实就是判断当前保存数据的数组当中,有没有存在3个或3个以上相邻的元素值相同的情况,我们检测的逻辑是从左上角开始遍历所有的方块,每一个方块都与右一,右二,下一,下二做相等性判断,如果连续三个相等就将这些元素的下标保存到一个我们事先定义的空数组.最后我们只需要查看数组的长度就可以知道到底有没有找到三消块.该部分代码如下:

”game.js”

//找出三消块,并将所有找到的三消块下标保存在数组中返回checkArrHas3:function(in_arr) {var with3Arr = [];for(var i=0;i<SHARD_NUM_W;i++) {for (var j = 0; j < SHARD_NUM_H; j++) {if( i < SHARD_NUM_W - 2 && in_arr[i][j] == in_arr[i+1][j] && in_arr[i+1][j] == in_arr[i+2][j]) {with3Arr.push(cc.p(i,j),cc.p(i+1,j),cc.p(i+2,j))}if( j < SHARD_NUM_H - 2 && in_arr[i][j] == in_arr[i][j+1] && in_arr[i][j+1] == in_arr[i][j+2]) {with3Arr.push(cc.p(i,j),cc.p(i,j+1),cc.p(i,j+2))}}}return with3Arr},

三,死局判定逻辑:

要判定当前局面是否为死局,是一件需要非常慎重的事情.就跟要判一个人死刑一样,需要有足够的证据.我们可以反向思考一下,如何才能否认当前的局面是死局?很简单,只要我移动任何一颗方块能产生三消块,就可以立即下结论.而如果要判断一个局面是死局,只能是我们将所有可以移动的方式都遍历一遍之后仍然无法形成三消块才可以下结论.那么问题就转化成了,如何才能将所有移动的可能全部遍历.我们把所有的方块全部拜访一遍,每拜访到一个方块,都让它跟上下左右邻居做一次交换(注意交换时要判断当前方向的邻居存不存在),再将交换后的局面做一次三消检测,只要发现形成三消我们立马可以判定局面未死.所有碎片的所有移动都做完了还没发现三消,就可以判定为死局,代码如下:

”game.js”

//死局判断checkIsDeath:function() {//可走方向对应的下标偏移量var checkArr = [[-1,0],[1,0],[0,-1],[0,1]];//遍历所有方块for (var i = SHARD_NUM_W - 1; i >= 0 ; i--) {for (var j = 0; j < SHARD_NUM_H; j++) {//遍历方块的每一个方向for(var index in checkArr) {var arr = checkArr[index];//如果可移动则模拟移动并检测移动后是否能形成三消if(i+arr[0] >= 0 && i+arr[0]<SHARD_NUM_W &&j+arr[1] >= 0 && j+arr[1]<SHARD_NUM_H) {var resultArr = this.checkAfterExchange([i,j],[i+arr[0],j+arr[1]], true);if(resultArr.length > 0) {return false;}}}}}return true;},

上面的代码中,有一个很关键的动作,那就是交换数组中两个位置的数据.但是我们此时并是想真的交换,确切的说只是一种搜索,搜索完了以后我们其实是要将刚才动过的地方给还原的.既然如此,为什么我们不将原始的数组复制一份,拿那个复制的数组去做模拟移动,得到的结果也是一样的,而且我们不用担心还原的问题.同时考虑到游戏正式开始之后,我们操作方块移动时的动作跟模拟移动的动作是一样的,所以我们完全可以共用一套移动的逻辑,只是操作的对象我们要做一个区分,即模拟移动时我们用替身,游戏开始以后移动我们就用真身,什么时候用什么我们可以通过一个标签来决定,交换数据部分代码如下:

”game.js”

//移动并返回移动后的三消检测数据checkAfterExchange:function(arr1, arr2, useCloneArr) {var copyArr = useCloneArr ? Clone2xArr( this.shard_date_arr) : this.shard_date_arr;var temp = copyArr[arr1[0]][arr1[1]];copyArr[arr1[0]][arr1[1]] = copyArr[arr2[0]][arr2[1]];copyArr[arr2[0]][arr2[1]] = temp;var result = this.checkArrHas3(copyArr);return result},

以上部分完成以后,我们就可以得到一组满足要求的显示数据,此时用来刷新界面,就可以得到一个既没有三消,也不是死局的界面:如下图所示:

四:方块点击后处理逻辑:

通过时间对象获取到正在被点击的方块A
当前局面中是否存在已经被点击的方块B如果B存在判断A与B是否为同一块如果不是判断AB是否相邻如果是判断AB是否能交换如果能执行交换如果不能指向交换失败动作取消B的被点中状态,并将B赋空如果不是取消B的被点中状态将A设置为被点中将A赋值给B(B始终对应着当前局面中被点中的方块)如果是取消B的被点中状态,并将B赋空如果B不存在将A设置为被点中将A赋值给B(B始终对应着当前局面中被点中的方块)

代码部分如下:

”game.js”

//碎片点击后的响应事件_getTouchOneShard: function (event){if(this.isInDrop || this.isGameOver) return;var shard = event.getUserData(); //获取时间发送者的对象if(this.oldShard == null) {shard.setSelected(true);this.oldShard = shard;} else {if(this.oldShard == shard) {shard.setSelected(false);this.oldShard = null;} else {var arr_index = this.getIndexArrByShard(shard);var old_index = this.getIndexArrByShard(this.oldShard);if((arr_index[0] == old_index[0] || arr_index[1] == old_index[1])&& Math.abs(arr_index[0]-old_index[0]+arr_index[1]-old_index[1]) == 1) {var result = this.checkAfterExchange(old_index, arr_index, true);if(result.length > 0){this.isInDrop = true;this.checkAfterExchange(old_index, arr_index, false);this.checkAllAndClear3();} else {//显示换位回去的动画cc.log('没有掉落  回位');}this.oldShard.setSelected(false);this.oldShard = null;} else {this.oldShard.setSelected(false);shard.setSelected(true);this.oldShard = shard;}}}},

交换数据及通过查找碎片下标部分代码如下:

”game.js”

 //移动并返回移动后的三消检测数据checkAfterExchange:function(arr1, arr2, useCloneArr) {var copyArr = useCloneArr ? Clone2xArr( this.shard_date_arr) : this.shard_date_arr;var temp = copyArr[arr1[0]][arr1[1]];copyArr[arr1[0]][arr1[1]] = copyArr[arr2[0]][arr2[1]];copyArr[arr2[0]][arr2[1]] = temp;var result = this.checkArrHas3(copyArr);return result},//获取碎片下标getIndexArrByShard: function (shard) {for (var i = 0; i < SHARD_NUM_W; i++)for (var j = 0; j < SHARD_NUM_H; j++)if (this.shard_sprite_arr[i][j] === shard) return [i, j];},

五:方块交换成功的消除逻辑:

在四部分我们已经清楚了方块点击以后需要进行的处理逻辑,其中跟这里能衔接上的就是交换成功的情况,要判断两个方块能否交换成功,和判断死局如初一辙,都是投石问路的套路,先模拟交换,能换咱就真换,不能换咱的也不要紧,反正模拟时用的是一份拷贝.
如果能交换,必然是交换以后能形成三消的情况.所以该部分的逻辑关系是这样:
1交换完成->2找到三消块,将刷为空白->3空白上浮至顶部->4空白处刷新出新块->5检查新快刷出后是否又能形成三消如果能跳到2如果不能检测局面是否变成了死局如果是游戏结束如果不是本次操作完成,将游戏设置为可操作状态

消除部分代码如下:

/检查所有超过3个相邻的块儿计算完成后清除checkAllAndClear3:function() {clearArr = this.checkArrHas3(this.shard_date_arr);if(clearArr.length > 0) {for (var index in clearArr) {var ccpOb = clearArr[index];this.shard_date_arr[ccpOb.x][ccpOb.y] = 0;}this.flushShardShowWithArr();setTimeout(this.shardDrop.bind(this), DELAY_TIME);}else {//每次结束都进行死局检测if (this.checkIsDeath()) {//cc.log('进入无解状态');this.isGameOver = true;this.gameoverLable.visible = true;} else {this.isInDrop = false;}}},

空白上浮部分代码如下:

//上方的块儿掉落  填补缺失shardDrop: function () {for (var i = 0; i < SHARD_NUM_W; i++) {//从下往上遍历for (var j = SHARD_NUM_H - 1; j >= 0; j--) {var downNum = 0;  //下坠值if (this.shard_date_arr[i][j] != 0){//计算每一个块的下坠值for (var k = j + 1; k < SHARD_NUM_H; k++) {if (this.shard_date_arr[i][k] == 0)  //只有空缺上方的才会进这downNum++;}if (downNum != 0) {//空格上浮至顶部var temp = this.shard_date_arr[i][j + downNum];this.shard_date_arr[i][j + downNum] = this.shard_date_arr[i][j];this.shard_date_arr[i][j] = temp;}}}}//刷新显示并在空白处刷出新的方块this.flushShardShowWithArr();setTimeout(this.flushEmptyWithRandom.bind(this),DELAY_TIME);},

刷出新快部分代码如下:

//将空缺区域刷出新的随机块儿flushEmptyWithRandom:function() {for (var i = 0; i < SHARD_NUM_W; i++) {for (var j = 0; j < SHARD_NUM_H; j++) {if (this.shard_date_arr[i][j] == 0) {this.shard_date_arr[i][j] = parseInt(Math.random() * SHARD_TYPE + 1);}}}this.flushShardShowWithArr();setTimeout(this.checkAllAndClear3.bind(this),DELAY_TIME);},

COCOS2d_js三消项目基本功能实现相关推荐

  1. atitit 音频 项目 系列功能表 音乐 v3 t67.docx Atitit 音频 项目 系列功能表 1.音频 音乐 语言领域的功能表 听歌识曲功能 酷我功能。 铃声 功能。。 音频切割(按

    atitit 音频 项目 系列功能表 音乐 v3 t67.docx Atitit 音频 项目 系列功能表 音频 音乐 语言领域的功能表 听歌识曲功能 酷我功能. 铃声 功能.. 音频切割(按照副歌部分 ...

  2. atitit 音频 项目 系列功能表 音乐 v3 t67.docx Atitit 音频 项目 系列功能表 音频 音乐 语言领域的功能表 听歌识曲功能 酷我功能。 铃声 功能。。 音频切割(按照副歌部

    atitit 音频 项目 系列功能表 音乐 v3 t67.docx Atitit 音频 项目 系列功能表 音频 音乐 语言领域的功能表 听歌识曲功能 酷我功能. 铃声 功能.. 音频切割(按照副歌部分 ...

  3. day17--途牛旅游项目-激活功能

    day17–途牛旅游项目-激活功能 UUID介绍 (1)什么是uuid 全球唯一的,不会重复的 固定长度的随机字符串 25fd9bcf50ad4dc39aa38f084d1801c8 (2)复制UUI ...

  4. 个人负责项目的功能模块分析

    什么是项目模块 首先要知道,一个项目的目的是什么以及最终要达到一个什么样效果.简单说,一个项目实现的最终结果就是实现对数据库的增删查改,然后返回最终的视图或者数据给前端,这就是一个项目的目的,这也是最 ...

  5. CRM客户资源管理系统项目——系统管理功能的实现

    动力节点SSM框架项目实战|Spring+Mybatis+Springmvc框架项目实战整合-[CRM客户管理系统]_哔哩哔哩_bilibilihttps://www.bilibili.com/vid ...

  6. 项目_功能模块_基于Spring Boot的文件上传下载功能的设计与实现

    文章目录 基于Spring Boot的文件上传下载功能模块的设计与实现 1.前言 2.技术栈 3.关键源码 4.实现效果 4.1.登录 4.2.文件列表 4.3.上传文件测试 4.3.1.测试图片 4 ...

  7. Flask OA项目的功能开发

    Flask OA项目的功能开发 首页 ​ 全局的用户身份 ​ 中间件添加全局模板变量 # 添加全局变量 @app.app_template_global("base") #如果是蓝 ...

  8. 目前见过最好的物联网系统项目,功能完整,代码结构清晰!

    今天,推荐一个物联网系统项目.我第一次使用就有点上头,爱不释手,必须要推荐给大家. 上次是谁要的物联网系统项目啊,我帮你找到了. 这是我目前见过最好的物联网系统项目.功能完整,代码结构清晰.值得推荐. ...

  9. 【PS小贴士】PS项目管理中 项目汇总功能的演示

    [业务背景]在很多企业的项目管理过程中,对于成本的归集除了细致以外,很多时候是不同"维度"的统计,就是我不仅仅想知道成本是按照这个项目的阶段的统计,还想知道按不同的项目类型.项目负 ...

最新文章

  1. 当顶流厂商谈论智能手表,他们到底在谈论什么
  2. 解题报告——蓝桥杯 试题 基础练习 2n皇后问题(附n皇后代码)
  3. 使用Azure Functions玩转Serverless
  4. android获取详细地址,Android获取当前子网掩码地址(亲测可用)
  5. 树莓派PI2编译天猫魔盘驱动,附编译好ko文件
  6. 土建中级工程师考试用书电子版_对没错!2020年湖南土建中级职称考试教材只是指导用书...
  7. TGBUS主页面 HTML的编写
  8. MAC OS 下QQ音乐下载存放的位置
  9. C语言编译器开发之旅(二):解析器
  10. AI还原乾隆后妃样貌,延禧攻略众生相。
  11. [BT_Books]《无线蓝牙技术深入探讨》笔记
  12. 下列python语句的输出结果是_下列Python语句的输出结果是
  13. 百度地图level对应距离(比例尺级别对应的多少米)
  14. 图像处理(一):傅里叶变换简单讲解
  15. Android工作经验三年总结。(零基础自学Android)
  16. linux视频日记软件下载,Linux(Ubunt)使用日记------常用软件汇总(不定时更新)
  17. 顶级文案到底需要怎么嗨?
  18. Salesforce Integration - OAuth2.0 JWT Bearer Flow
  19. ES整合SpringBoot并实现京东搜索
  20. 如何提高自己的学习能力?

热门文章

  1. Qcon会议之所见所想
  2. 【每日积累】玫瑰图加雷达图的制作 echarts实现
  3. 2021FME博客大赛 —— FME与“快递小哥”的故事
  4. 家庭整理-《怦然心动的人生整理魔法》书中的精髓:如何利用怦然心动家庭整理法,拥有整洁、高效、全新的人生?
  5. android 四大组件之activity总结
  6. java求面积_用Java做个计算长方形面积的程序(3)
  7. xshell下载使用wget下载tomcat出现错误:无法验证,组织机构的证书,无用证书时
  8. 关于我发布的iApp登录界面教程的问题
  9. 5. 百度地图api1.0-添加信息窗口
  10. Python 信号与系统