CocosCreator 快速开发推箱子游戏,附代码
游戏总共分为4个功能模块:
- 开始游戏(menuLayer)
- 关卡选择(levelLayer)
- 游戏(gameLayer)
- 游戏结算(gameOverLayer)
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_col
node.name = "wall_" + row + "_" + col;
node.attr({"_type_" : type});
}
else if(type == boxType.NONE){ //空白区域,//墙面,命名为none_row_col
node.name = "none_" + row + "_" + col;
node.attr({"_type_" : type});
}
else{ //游戏界面,命名为land_row_col
node.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();
}
},
Creator组件布局如下:

本游戏免费提供游戏源码,需要源码请关注公众号『一枚小工』获取

CocosCreator 快速开发推箱子游戏,附代码的更多相关文章
- 用 CocosCreator 快速开发推箱子游戏
游戏总共分为4个功能模块: - 开始游戏(menuLayer) - 关卡选择(levelLayer) - 游戏(gameLayer) - 游戏结算(gameOverLayer) Creator内组件效 ...
- C语言实现推箱子游戏完整代码
C语言实现推箱子游戏完整代码 前言 自己做的,可能有些代码不够工整,或者有些小问题,但游戏的基本操作是可以实现的 代码效果 代码一共分为8个部分,4个控制上下左右移动,2个判断输赢,1个统计归为的个数 ...
- [转]Flash ActionScript2.0面向对象游戏开发-推箱子
本文转自:http://www.alixixi.com/Dev/W3C/Flash/2007/2007070868666.html 概述: Flash ActionScript2.0是一种面向对向的编 ...
- JavaScript写一个小乌龟推箱子游戏
推箱子游戏是老游戏了, 网上有各种各样的版本, 说下推箱子游戏的简单实现,以及我找到的一些参考视频和实例: 推箱子游戏的在线DEMO : 打开 如下是效果图: 这个拖箱子游戏做了移动端的适配, 我使用 ...
- 用HTML5+原生js实现的推箱子游戏
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- JavaScript 推箱子游戏
推箱子游戏的 逻辑非常简单,但是如果不动手的话,还是不太清楚.我在这里讲一下自己的思路. 制作推箱子,首先要有自己的设计素材.如下我也是网上找的素材 第二步,理清游戏的规则. 游戏规则: 1.小人将箱 ...
- three.js 制作一个三维的推箱子游戏
今天郭先生发现大家更喜欢看我发的three.js小作品,今天我就发一个3d版本推箱子的游戏,其实webGL有很多框架,three.js并不合适做游戏引擎,但是可以尝试一些小游戏.在线案例请点击博客原文 ...
- 用C写一个简单的推箱子游戏(二)
下面接着上一篇随笔<用C写一个简单的推箱子游戏(一)>来写 tuidong()函数是用来判断游戏人物前方情况的函数,是推箱子游戏中非常重要的一个函数,下面从它开始继续介绍推箱子的小程序怎么 ...
- 用C写一个简单的推箱子游戏(一)
我现在在读大二,我们有一门课程叫<操作系统>,课程考查要求我们可以写一段程序或者写Windows.iOS.Mac的发展历程.后面我结合网上的资料参考,就想用自己之前简单学过的C写一关的推箱 ...
随机推荐
- pt工具校验主从数据一致性之dsns方式
mysql主从数据一致性校验,常用的方法是Percona-Toolkit的组件pt-table-checksum,这东西怎么用网上一大堆,就不啰嗦了.主要说一下通过dsns方式发现从库的一种方式. p ...
- 【Offer】[46] 【把数字翻译成字符串】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 给定一个数字,我们按照如下规则把它翻译为字符串: 0翻译成"a",1翻译成"b",..... 1 ...
- 【Offer】[36] 【二叉搜索树与双向链表】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的节点,只能调整树中节点指针的指向.比如,输入下图中左边的 ...
- Vue组件间通信方式
一.Props传递数据 在父组件中使用子组件,本质通过v-bind绑定属性传入子组件,子组件通过props接收父组件传入的属性 <template> <div> 父组件:{{m ...
- 干货| 外卖点餐系统(App及后台)
简单总结一下,这个系统是一个外卖点餐系统,也就是仿美团饿了么,也是当时我的毕业设计,花费了我很多精力,主要包括了移动端App.服务端(中台)和商家的后台管理.答辩完后我就将移动端源码放到了GitHub ...
- 阿里云CentOS7.3服务器通过Docker安装Nginx
前言 小编环境: 阿里云CentOS7.3服务器 docker 下面分享一次小编在自己的阿里云CentOS7.3服务器上使用Docker来安装Nginx的一次全过程 温馨小提示: 如果只是希望单纯使用 ...
- 基于RMAN搭建DataGuard,使用Broker管理DataGuard
一.环境准备 1.数据库软件准备 (1).在主节点,安装单机数据库软件并创建数据库. (2).在备库, 安装单机数据库软件, 但是不创建数据库. 2.操作系统配置 在/etc/hosts下面配置主机名 ...
- 豆瓣电影TOP250和书籍TOP250爬虫
豆瓣电影 TOP250 和书籍 TOP250 爬虫 最近开始玩 Python , 学习爬虫相关知识的时候,心血来潮,爬取了豆瓣电影TOP250 和书籍TOP250, 这里记录一下自己玩的过程. 电影 ...
- [Leetcode] 第299题 猜数字游戏
一.题目描述 请写出一个根据秘密数字和朋友的猜测数返回提示的函数,用 A 表示公牛,用 B 表示奶牛. 请注意秘密数字和朋友的猜测数都可能含有重复数字. 示例 1: 输入: secret = &quo ...
- 生物医学命名实体识别(BioNER)研究进展
生物医学命名实体识别(BioNER)研究进展 最近把之前整理的一些生物医学命名实体识别(Biomedical Named Entity Recognition, BioNER)相关的论文做了一个Bio ...