'用 CocosCreator 快速開發推箱子游戲'

倉庫番 腳本語言 動畫 蒲公英 2019-08-15
"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

用 CocosCreator 快速開發推箱子游戲

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);
}
}
},

最終的顯示效果如下圖:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

用 CocosCreator 快速開發推箱子游戲

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);
}
}
},

最終的顯示效果如下圖:

用 CocosCreator 快速開發推箱子游戲

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的上:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

用 CocosCreator 快速開發推箱子游戲

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);
}
}
},

最終的顯示效果如下圖:

用 CocosCreator 快速開發推箱子游戲

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的上:

用 CocosCreator 快速開發推箱子游戲

遊戲開始後,顯示的效果如下(第一關,其他關類似)

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

用 CocosCreator 快速開發推箱子游戲

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);
}
}
},

最終的顯示效果如下圖:

用 CocosCreator 快速開發推箱子游戲

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的上:

用 CocosCreator 快速開發推箱子游戲

遊戲開始後,顯示的效果如下(第一關,其他關類似)

用 CocosCreator 快速開發推箱子游戲

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組件佈局如下:

"

遊戲總共分為4個功能模塊:

- 開始遊戲(menuLayer

- 關卡選擇(levelLayer

- 遊戲(gameLayer

- 遊戲結算(gameOverLayer

Creator內組件效果如下:

用 CocosCreator 快速開發推箱子游戲

遊戲開始默認顯示menuLayer,遊戲中,通過控制各個層級的顯示和隱藏,實現不同模塊的切換。例如開始遊戲,點擊開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關係如下圖:

用 CocosCreator 快速開發推箱子游戲

實現代碼如下:

// 開始按鈕回調
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)
)));
},

實現後的效果:

用 CocosCreator 快速開發推箱子游戲

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;
},

下圖即是所有關卡預製的父節點:

用 CocosCreator 快速開發推箱子游戲

預製腳本掛在到預製上:

用 CocosCreator 快速開發推箱子游戲

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);
}
}
},

最終的顯示效果如下圖:

用 CocosCreator 快速開發推箱子游戲

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的上:

用 CocosCreator 快速開發推箱子游戲

遊戲開始後,顯示的效果如下(第一關,其他關類似)

用 CocosCreator 快速開發推箱子游戲

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 快速開發推箱子游戲

# Talk is cheap. Show me the code. #

需要遊戲源碼的,留言裡留下聯繫方式。

"

相關推薦

推薦中...