The rapid development of Sokoban game with CocosCreator

Total game is divided into four functional modules:

- to start the game ( MenuLayer )

- Level Select ( levelLayer )

- Game ( gameLayer )

- Game settlement ( gameOverLayer )

Creator inner assembly results are as follows:

       Games start the default display menuLayer, game, by controlling the display and hide at all levels to achieve different switching modules. For example, to start the game, click Start later, triggering a callback function, to switch to the game level selection interface, binding relationship in the following figure:

      Codes are as follows:


// 开始按钮回调
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();
        }
    )));
},

Other functional modules to achieve similar. The following points will be about four modules are implemented each module.

1. menuLayer start the game 
       to start the game module, the default display after the start of the game, other modules hidden, function relatively simple, after the interface layout is complete, add a button to start the game to respond to events, implementation code as above, in this interface to add a little animation so start the game constantly zoom button, the code is as follows:

// 主界面动画
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)
    )));
},

Achieve effect after:

2. Select the level levelLayer
       level selection in two steps: first, the interface displays, through the configuration file, load prefabricated files, display all levels; the second step, depending on the game situation, updated every level information.

2.1 The first step level display
       all of the game levels ScrollView control placed on every level, using a pre-file (levelItem), by reading level configuration file, load all levels, load recalculated ScrollView content after the completion of height, loading level code is as follows:


// 创建关卡界面子元素
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;
},

FIG i.e. all lower levels prefabricated parent node:

Pre-scripted hang on to pre-:

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. settlement gameOverLayer game
       after the end of the game, based on the number of boxes successfully pushed to determine whether a game is successful, the future success of the game, you can update the checkpoint information.

Analyzing logic is as follows:


// 游戏结束检测
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 component layout is as follows:

This game provides the free game source code, please pay attention to the public need the source code numbers "a coolie" get

Guess you like

Origin www.cnblogs.com/caizj/p/11607266.html