本文首发于公众号: 一枚小工(caizj_cn)

Cocos 已获得转载授权!

作者:蒲公英,开发工程师,7 年 Cocos 引擎使用经验。


游戏总共分为 4 个功能模块:

  • 开始游戏(menuLayer

  • 关卡选择(levelLayer

  • 游戏(gameLayer

  • 游戏结算(gameOverLayer

Cocos Creator 内组件效果如下:

游戏开始默认显示 menuLayer,游戏中,通过控制各个层级的显示和隐藏,实现不同模块的切换。例如开始游戏,点击开始以后,触发回调函数,切换到游戏关卡选择界面,绑定关系如下图:

实现代码如下:

// 开始按钮回调
startBtnCallBack(event, customEventData){
if(this.curLayer == 1){
return;}
this.curLayer = 1;this.playSound(sound.BUTTON);       this.menuLayer.runAction(cc.sequence(cc.fadeOut(0.1),cc.callFunc(() => {
this.startBtn.stopAllActions();
this.startBtn.scale = 1.0;
this.menuLayer.opacity = 255;
this.menuLayer.active = false;})));this.levelLayer.active = true;
this.levelLayer.opacity = 0;
this.levelLayer.runAction(cc.sequence(cc.delayTime(0.1), cc.fadeIn(0.1), cc.callFunc(() => {
this.updateLevelInfo();})));
},

其他功能模块实现类似,以下将分4个模块分别讲述各个模块的实现。

1. 开始游戏 menuLayer

开始游戏模块,开始游戏后默认显示,其他模块隐藏,功能实现相对简单,界面布局完成以后,开始游戏按钮添加响应事件即可,实现代码如上,在此界面添加了一个小动画,让开始游戏按钮不断的放大缩小,代码如下:

// 主界面动画
menuLayerAni(){
this.startBtn.scale = 1.0;
this.startBtn.runAction(cc.repeatForever(cc.sequence(cc.scaleTo(0.6, 1.5), cc.scaleTo(0.6, 1.0))));
},

实现后的效果:

2. 关卡选择 levelLayer

关卡选择分两步:第一步,界面显示,通过配置文件,加载预制文件,显示所有关卡;第二步,根据游戏情况,更新每一关卡信息。

2.1 第一步显示关卡

游戏中所有关卡置于 ScrollView 控件上,每一个关卡,使用一个预制文件(levelItem),通过读取关卡配置文件,加载所有关卡,加载完成后重新计算 ScrollView 内容的高度,加载关卡代码如下:

// 创建关卡界面子元素
createLavelItem (){
// 进入关卡level
let callfunc = level => {
this.selectLevelCallBack(level);};for(let i = 0; i < this.allLevelCount; i++){
let node = cc.instantiate(this.levelItemPrefab);node.parent = this.levelScroll;
let levelItem = node.getComponent("levelItem");levelItem.levelFunc(callfunc);
this.tabLevel.push(levelItem);}
// 设置容器高度
this.levelContent.height = Math.ceil(this.allLevelCount / 5) * 135 + 20;
},

下图即是所有关卡预制的父节点:

预制脚本挂在到预制上:

2.2 第二步更新关卡

每一个 levelItem 预制上挂一个 levelItem 脚本组件,levelItem 脚本组件负责更新信息,主要控制是否可点击、通关星数、关卡等级、点击进入,levelItem 脚本组件更新 UI 代码如下:

/*** @description: 显示星星数量* @param {boolean} isOpen 是否开启* @param {starCount} 星星数量* @param {cc.SpriteAtlas} levelImgAtlas 纹理图* @param {number} level 关卡* @return: */
showStar(isOpen, starCount, levelImgAtlas, level){
this.itemBg.attr({"_level_" : level});
if(isOpen){
this.itemBg.getComponent(cc.Sprite).spriteFrame = levelImgAtlas.getSpriteFrame("pass_bg");
this.starImg.active = true;
this.starImg.getComponent(cc.Sprite).spriteFrame = levelImgAtlas.getSpriteFrame("point" + starCount);
this.levelTxt.opacity = 255;
this.itemBg.getComponent(cc.Button).interactable = true;}
else{
this.itemBg.getComponent(cc.Sprite).spriteFrame = levelImgAtlas.getSpriteFrame("lock");
this.starImg.active = false;
this.levelTxt.opacity = 125;
this.itemBg.getComponent(cc.Button).interactable = false;}
this.levelTxt.getComponent(cc.Label).string = level;
},

玩家的通过的信息,通过配置存储文件,保存玩家通关信息,分为已通关、刚开启和未开启三种状态,具体实现如下:

// 刷新关卡上的信息
updateLevelInfo(){let finishLevel = parseInt(cc.sys.localStorage.getItem("finishLevel") || 0);  //已完成关卡
for(let i = 1; i <= this.allLevelCount; i++){
// 完成的关卡
if(i <= finishLevel){let data = parseInt(cc.sys.localStorage.getItem("levelStar" + i) || 0);
this.tabLevel[i - 1].showStar(true, data, this.levelImgAtlas, i);}
// 新开的关卡
else if(i == (finishLevel + 1)){
this.tabLevel[i - 1].showStar(true, 0, this.levelImgAtlas, i);}
// 未开启关卡图
else{
this.tabLevel[i - 1].showStar(false, 0, this.levelImgAtlas, i);}}
},

最终的显示效果如下图:

3. 游戏 gameLayer

游戏也分为两步:第一步,显示界面;第二步,游戏操作判断。

3.1 显示界面

游戏内使用 levelConfig.json 配置每一关卡信息,每个关卡游戏部分由多行多列的方格组成,每一个关卡信息包含 content、allRow、allCol、heroRow、heroCol、allBox 属性,allRow 和 allCol 记录总共行数和列数,heroRow、heroCol 记录英雄所在位置,allBox 记录箱子的总数,content 是核心,记录每个方格的属性,根据不同的属性显示不同的物体,如墙面、地面、物体、箱子,可以通过修改配置,增加任意关卡。

读取关卡所有数据,并根据每一个位置的属性,显示不同的实物。

根据配置创建关卡信息:

// 创建关卡
createLevelLayer(level){
this.gameControlLayer.removeAllChildren();
this.setLevel();
this.setCurNum();
this.setBestNum();let levelContent = this.allLevelConfig[level].content;
this.allRow = this.allLevelConfig[level].allRow;
this.allCol = this.allLevelConfig[level].allCol;
this.heroRow = this.allLevelConfig[level].heroRow;
this.heroCol = this.allLevelConfig[level].heroCol;// 计算方块大小
this.boxW = this.allWidth / this.allCol;
this.boxH = this.boxW;// 计算起始坐标let sPosX = -(this.allWidth / 2) + (this.boxW / 2);let sPosY = (this.allWidth / 2) - (this.boxW / 2);// 计算坐标的偏移量,运算规则(宽铺满,设置高的坐标)let offset = 0;
if(this.allRow > this.allCol){offset = ((this.allRow - this.allCol) * this.boxH) / 2;}
else{offset = ((this.allRow - this.allCol) * this.boxH) / 2;}
this.landArrays = [];   //地图容器
this.palace = [];       //初始化地图数据
for(let i = 0; i < this.allRow; i++){
this.landArrays[i] = [];
this.palace[i] = [];}for(let i = 0; i < this.allRow; i++){    //每行
for(let j = 0; j < this.allCol; j++){     //每列let x = sPosX + (this.boxW * j);let y = sPosY - (this.boxH * i) + offset;let node = this.createBoxItem(i, j, levelContent[i * this.allCol + j], cc.v2(x, y));
this.landArrays[i][j] = node;node.width = this.boxW;node.height = this.boxH;}}// 显示人物
this.setLandFrame(this.heroRow, this.heroCol, boxType.HERO);
},

根据类型创建元素:

// 创建元素
createBoxItem(row, col, type, pos){let node = new cc.Node();let sprite = node.addComponent(cc.Sprite);let button = node.addComponent(cc.Button);sprite.spriteFrame = this.itemImgAtlas.getSpriteFrame("p" + type);node.parent = this.gameControlLayer;node.position = pos;
if(type == boxType.WALL){  //墙面,//墙面,命名为wall_row_colnode.name = "wall_" + row + "_" + col;node.attr({"_type_" : type});}
else if(type == boxType.NONE){  //空白区域,//墙面,命名为none_row_colnode.name = "none_" + row + "_" + col;node.attr({"_type_" : type});}
else{  //游戏界面,命名为land_row_colnode.name = "land_" + row + "_" + col;node.attr({"_type_" : type});node.attr({"_row_" : row});node.attr({"_col_" : col});button.interactable = true;button.target = node;button.node.on('click', this.clickCallBack, this);
if(type == boxType.ENDBOX){  //在目标点上的箱子,直接将完成的箱子数加1
this.finishBoxCount += 1;}}
this.palace[row][col] = type;return node;
},

游戏的所有元素,放置在下图中 gameControlLayer 的上:

游戏开始后,显示的效果如下(第一关,其他关类似)

3.2 游戏操作判断

路线计算好后,玩家移动,若玩家点击的是箱子区域,先检测箱子前方是否有障碍物,若没有则推动箱子,通过切换地图的图片和修改位置类型达到推动箱子的效果。

点击地图位置,获取最优路径,人物跑到指定点,实现如下:

// 点击地图元素
clickCallBack : function(event, customEventData){let target = event.target;
//最小路径长度
this.minPath = this.allCol * this.allRow + 1;
//最优路线
this.bestMap = [];//终点位置
this.end = {};
this.end.row  = target._row_;
this.end.col = target._col_;//起点位置
this.start = {};
this.start.row = this.heroRow;
this.start.col = this.heroCol;//判断终点类型let endType = this.palace[this.end.row][this.end.col];
if((endType == boxType.LAND) || (endType == boxType.BODY)){  //是空地或目标点,直接计算运动轨迹
this.getPath(this.start, 0, []);if(this.minPath <= this.allCol * this.allRow){cc.log("从起点[", this.start.row, ",", this.start.col, "]到终点[",
this.end.row, ",", this.end.col, "]最短路径长为:", this.minPath, "最短路径为:");cc.log("[", this.start.row, ",", this.start.col, "]");
for(let i = 0; i< this.bestMap.length;i++){cc.log("=>[",this.bestMap[i].row,",",this.bestMap[i].col,"]");}
this.bestMap.unshift(this.start);
this.runHero();}else{console.log("找不到路径到达");}}
else if((endType == boxType.BOX) || (endType == boxType.ENDBOX)){ //是箱子,判断是否可以推动箱子
//计算箱子和人物的距离let lr = this.end.row - this.start.row;let lc = this.end.col - this.start.col;
if((Math.abs(lr) + Math.abs(lc)) == 1){  //箱子在人物的上下左右方位
//计算推动方位是否有障碍物let nextr = this.end.row + lr;let nextc = this.end.col + lc;let t = this.palace[nextr][nextc];
if(t && (t != boxType.WALL) && (t != boxType.BOX) && (t != boxType.ENDBOX)){  //前方不是障碍物,也不是墙壁,推动箱子
this.playSound(sound.PUSHBOX);
//人物位置还原
this.setLandFrame(this.start.row, this.start.col, this.palace[this.start.row][this.start.col]);//箱子位置类型let bt = this.palace[this.end.row][this.end.col];
if(bt == boxType.ENDBOX){      //有目标物体的箱子类型,还原成目标点
this.palace[this.end.row][this.end.col] = boxType.BODY;
this.finishBoxCount -= 1;}
else{
this.palace[this.end.row][this.end.col] = boxType.LAND;}
//箱子位置变成人物图,但类型保存为空地或目标点
this.setLandFrame(this.end.row, this.end.col, boxType.HERO);//箱子前面位置变成箱子let nt = this.palace[nextr][nextc];
if(nt == boxType.BODY){  //有目标点,将箱子类型设置成有目标箱子
this.palace[nextr][nextc] = boxType.ENDBOX;
this.finishBoxCount += 1;}
else {
this.palace[nextr][nextc] = boxType.BOX;}
this.setLandFrame(nextr, nextc, this.palace[nextr][nextc]);this.curStepNum += 1;
//刷新步数
this.setCurNum();//刷新人物位置
this.heroRow = this.end.row;
this.heroCol = this.end.col;this.checkGameOver();}
else{
this.playSound(sound.WRONG);console.log("前方有障碍物");}}
else{   //目标点错误
this.playSound(sound.WRONG);console.log("目标点错误");}}
},

获取最优路径算法:

//curPos记录当前坐标,step记录步数
getPath : function(curPos, step, result){
//判断是否到达终点
if((curPos.row == this.end.row) && (curPos.col == this.end.col)){
if(step < this.minPath){
this.bestMap = [];
for(let i = 0; i < result.length; i++){
this.bestMap.push(result[i]);}
this.minPath = step; //如果当前抵达步数比最小值小,则修改最小值result = [];}}//递归
for(let i = (curPos.row - 1); i <= (curPos.row + 1); i++){
for(let j = (curPos.col - 1); j <= (curPos.col + 1); j++){
//越界跳过
if((i < 0) || (i >= this.allRow) || (j < 0) || (j >= this.allCol)){
continue;}
if((i != curPos.row) && (j != curPos.col)){//忽略斜角
continue;}
else if(this.palace[i][j] && ((this.palace[i][j] == boxType.LAND) || (this.palace[i][j] == boxType.BODY))){let tmp = this.palace[i][j];
this.palace[i][j] = boxType.WALL;  //标记为不可走//保存路线let r = {};r.row = i;r.col = j;result.push(r);this.getPath(r, step + 1, result);
this.palace[i][j] = tmp;  //尝试结束,取消标记result.pop();}}}
},

4. 游戏结算 gameOverLayer

游戏结束后,根据成功推到箱子数,判断游戏是否成功,游戏成功以后,更新关卡信息即可。

判断逻辑如下:

// 游戏结束检测
checkGameOver(){let count = this.allLevelConfig[this.curLevel].allBox;
// 全部推到了指定位置
if(this.finishBoxCount == count){
this.gameOverLayer.active = true;
this.gameOverLayer.opacity = 1;
this.gameOverLayer.runAction(cc.sequence(cc.delayTime(0.5), cc.fadeIn(0.1)));// 刷新完成的关卡数let finishLevel = parseInt(cc.sys.localStorage.getItem("finishLevel") || 0);
if(this.curLevel > finishLevel){cc.sys.localStorage.setItem("finishLevel", this.curLevel);}// 刷新星星等级cc.sys.localStorage.setItem("levelStar" + this.curLevel, 3);// 刷新最优步数let best = parseInt(cc.sys.localStorage.getItem("levelBest" + this.curLevel) || 0);
if((this.curStepNum < best) || (best == 0)){cc.sys.localStorage.setItem("levelBest" + this.curLevel, this.curStepNum);}
this.playSound(sound.GAMEWIN);
this.clearGameData();}
},

Cocos Creator 组件布局如下:


以上便是转载全文,感兴趣的开发者可以关注公众号“一枚小工”与作者进行更加深入的交流互动。

Cocos 将定期原创或转载优质的技术分享教程,我们也欢迎有创作想法想要投稿的开发者与本公众号进行联系,感谢支持!

一图看懂 Cocos 2019 新品发布会

5G 云游戏亮相 Chinajoy,大作一键秒玩

Cocos 技术派 | 实时竞技小游戏技术分享

Cocos 技术派 |《野蛮人大作战》从开发到上线

Cocos 技术派 |  插件 CC_inspector +

Cocos 技术派 |  摄像机的灵活运用

3D 小游戏《飞跃地平线 Plus》开发分享

Cocos Creator 接入微信小游戏引擎插件指引

创意小游戏《荒野日记》Cocos专访

垃圾分类搞不清?快试试这款小游戏

Cocos Creator:快速开发推箱子游戏相关推荐

  1. cocos creator实例--Cocos Creator 3D开发 投篮 小游戏

    效果预览 游戏介绍 ● 点击屏幕,根据按住屏幕的时间,进行蓄力,时间越短,发出去的力越小,时间越长,发出去的力越大,超过了最大力,再次从最小里开始,球从篮筐中穿过得1分,否则视为不得分,由于做的是de ...

  2. 微信小程序案例源码-cocos creator跨平台开发小游戏案例

    前言 本人一直想学习游戏开发,以前做过Android游戏开发,但电脑坏了,所有源码都没有了,后面也就没有开发了,近来下班之余又想开发游戏了.开发游戏之前,需要考虑开发平台及语言,以前基于Android ...

  3. 一文教你使用java开发一款推箱子游戏

    导读:社会在进步,人们生活质量也在日益提高.高强度的压力也接踵而来.社会中急需出现新的有效方式来缓解人们的压力.此次设计符合了社会需求,Java推箱子游戏可以让人们在闲暇之余,体验游戏的乐趣.具有操作 ...

  4. 安卓游戏开发推箱子_保持冷静并砍箱子-开发

    安卓游戏开发推箱子 Hack The Box (HTB) is an online platform allowing you to test your penetration testing ski ...

  5. 【猿码】java swing实现喜羊羊与灰太狼推箱子游戏附带视频开发教程可做为Java毕设大作业

    大家好,今天给大家演示一下由Java swing实现的推箱子小游戏的一款项目,其图标用的是喜羊羊与灰太狼,所以又称喜羊羊与灰太狼版的推箱子游戏,该项目运行环境为普通的Java环境,jdk版本不限,下面 ...

  6. 喜羊羊与灰太狼java_java swing实现喜羊羊与灰太狼推箱子游戏附带视频开发教程...

    <p style="font-family:" font-size:15px;text-indent:2em;color:#555555;background-color:# ...

  7. android开发--推箱子小游戏(二)

    一.前言 迎来第二章的更新啦:使用ListView实现关卡的选择. 本章的内容有点小复杂,毕竟涉及使用了安卓开发中最难用也是最常用的控件之一:ListView 本章可以说是复杂但是单一吧.主要是想大家 ...

  8. Android开发入门——推箱子游戏开发实战(十二)

    绘制游戏局面 本文是推箱子游戏程序开发的第七步.系列文章前五篇描述准备工作,故本文编号是(十二).本文讲解如何绘制游戏局面. 本文目标 本文讲解如何绘制游戏局面.游戏局面的示例如图1,图2所示.这两幅 ...

  9. C语言开发《推箱子游戏》,亲自手把手教会大家

    [C语言经典算法100道实战题]适合具备C语言基础语法的同学学习,提高编写程序的逻辑思维能力和算法设计能力专门精心设计.100个经典的算法供大家练习及配套对应的录播视频.为我们今后学习其它的编程语言和 ...

最新文章

  1. 【Sql Server】Database-存储过程
  2. 【 FPGA 】FIR 滤波器之Single-rate FIR滤波器的系数数据(Filter Coefficient Data)
  3. python3.6手册中文版-python3.6文档中文版
  4. legend3---用Homestead配置后报错“No input file specified.”
  5. 打开计算机硬盘有声音,开机时硬盘会发出声音了,怎么办
  6. mckinsey game
  7. 汇编语言Makefile
  8. mac搭建PHP开发环境
  9. Frameworks.Entity.Core 1
  10. python创建类和类方法
  11. 在实验室服务器毫无阻拦的安装pip3
  12. Access新手到高手视频教程 109讲
  13. Rufus制作USB启动盘
  14. opencv学习十三:图像金字塔和图像梯度
  15. iOS内购 - 服务端票据验证及漏单引发的思考
  16. 服务器七雄争霸官方网站,腾讯七雄争霸微端登录器
  17. 网易2018校园招聘:相反数 [python]
  18. 【Games104-现代游戏引擎0102】引擎架构分层个人笔记
  19. antd 表格分页功能
  20. 网间互联-国家级互联网骨干直联点

热门文章

  1. 趣学呗老师整理:初中英语60个常见介词短语!
  2. 转:如何跨越“说”与“做”的鸿沟?
  3. PHP伪原创同义词替代代码示意
  4. 百度无人车开进长沙!完成全国首例高速多车型车路协同演示
  5. 【uniapp】微信小程序端总结
  6. Go基于共享变量的并发
  7. 初学python100例-案例35 数字反转 少儿编程python编程实例讲解
  8. 【数字IC验证快速入门】2、通过一个SoC项目实例,了解SoC的架构,初探数字系统设计流程
  9. nginx文件服务器中文编码,nginx不支持中文路径问题处理
  10. pgsql-Create_ALTER_GRANT_REVOKE命令语法