天天看點

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

遊戲總共分為4個功能子產品:

- 開始遊戲(menuLayer)

- 關卡選擇(levelLayer)

- 遊戲(gameLayer)

- 遊戲結算(gameOverLayer)

Creator内元件效果如下:

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

       遊戲開始預設顯示menuLayer,遊戲中,通過控制各個層級的顯示和隐藏,實作不同子產品的切換。例如開始遊戲,點選開始以後,觸發回調函數,切換到遊戲關卡選擇界面,綁定關系如下圖:

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

      實作代碼如下:

// 開始按鈕回調
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下JavaScript 快速開發推箱子遊戲,附代碼

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下JavaScript 快速開發推箱子遊戲,附代碼

預制腳本挂在到預制上:

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

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下JavaScript 快速開發推箱子遊戲,附代碼

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下JavaScript 快速開發推箱子遊戲,附代碼

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

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

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下JavaScript 快速開發推箱子遊戲,附代碼

本遊戲免費提供遊戲源碼,需要源碼請關注公衆号『一枚小工』擷取

CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼
CocosCreator下JavaScript 快速開發推箱子遊戲,附代碼

繼續閱讀