Cocos Creator开发游戏消灭星星——星星消除

上一篇文章写了星星生成的逻辑,详情请看Cocos Creator开发游戏消灭星星——星星生成

写在消除星星之前

星星消除是发生在用户点击之后,所以需要处理用户触摸操作。在上一篇制作星星预制时有提及,在脚本组件starCtr.js的start函数里监听触摸。

start () {
    this.node.on(cc.Node.EventType.TOUCH_START, function (event) {
        //TODO:触摸处理
    }, this);
},

消除星星是消除上下左右相连的星星,所以需要根据用户点击的星星找到其他相连的星星。在Utils中增加方法needRemoveList:

//Utils.js
//检测数组array中是否坐标p
function indexOfV2 (array, p) {
    return array.some(function (elem, index, arr) {
        return elem.x == p.x && elem.y == p.y
    });
};

//根据矩阵数据查找消除的星星
function needRemoveList (data, p) {
    var list = [];
    var travelList = [];
    travelList.push(p);

    var tag = data[p.x][p.y];
    do {
        var any = travelList.pop();
        //左
        if (any.y - 1 >= 0 && tag == data[any.x][any.y-1]) {
            var tp = cc.v2(any.x, any.y-1);
            if (!this.indexOfV2(list, tp) && !this.indexOfV2(travelList, tp)) {
                travelList.push(tp);
            }
        }
        //右
        if (any.y + 1 < Config.matrixCol && tag == data[any.x][any.y+1]) {
            var tp = cc.v2(any.x, any.y+1);
            if (!this.indexOfV2(list, tp) && !this.indexOfV2(travelList, tp)) {
                travelList.push(tp);
            }
        }
        //下
        if (any.x - 1 >= 0 && tag == data[any.x-1][any.y]) {
            var tp = cc.v2(any.x-1, any.y);
            if (!this.indexOfV2(list, tp) && !this.indexOfV2(travelList, tp)) {
                travelList.push(tp);
            }
        } 
        //上
        if (any.x + 1 < Config.matrixRow && tag == data[any.x+1][any.y]) {
            var tp = cc.v2(any.x+1, any.y);
            if (!this.indexOfV2(list, tp) && !this.indexOfV2(travelList, tp)) {
                travelList.push(tp);
            }
        }
        list.push(any);
    } while (travelList.length > 0);
    return list;
};

消除星星

现在来完成触摸处理逻辑:

//starCtr.js
//触摸处理
var list = Utils.needRemoveList(GameData.starMatrix, cc.v2(this._gx, this._gy));
if (list.length >= 2) {
    var event = new cc.Event.EventCustom("delete_stars", true);
    event.detail = list;
    this.node.dispatchEvent(event);
}

通过用户点击的星星坐标找到与其相连的星星们,然后发射delete_stars事件,通知地图消除星星。关于监听和发射时间参考官方文档监听和发射事件

在matrixCtr.js的onLoad方法中添加事件监听

//matrixCtr.js
onLoad () {
    this.node.on("delete_stars", this.deleteSprites, this);
    //其他无关代码这里省略
},
onDestroy () {
    this._starPool.clear();
    this.node.off("delete_stars", this.deleteSprites, this); //移除监听
},

先添加几个属性来记录消除数据

//matrixCtr.js
properties: {
    //其他无关属性这里省略
    _totalCounts: 0, //总的消除星星个数
    _currCount: 0, //已经消除的星星个数
    _bombList: [], //存储待消除的星星的坐标
    _tamping: false, //夯实数组的标记,就是消除星星后的地图处理(星星下落动画、整列星星左移动画等)
},

在回调函数中处理消除逻辑

//matrixCtr.js
// 监听回调函数
deleteSprites (event) {
    if (this._tamping) {
        return;
    }
    var bombList = event.detail;
    //防止重复消除
    if (Utils.indexOfV2(this._bombList, bombList[0])) {
        return;
    }
    this._totalCounts += bombList.length;
    this._bombList = this._bombList.concat(bombList);
    this.showComboEffect(bombList.length);
    GameData.cleanStarData(bombList);
    this.bomb(bombList, bombList.length);
}

播放combo特效

上一篇说过,动画和特效主要放在节点ActionRoot中处理。如图,combo特效就在combNode节点中播放。

ActionRoot

特效是用骨骼动画制作的,所以在combNode上添加dragonBones的渲染组件,同时,再添加脚本组件dragonBonesCtr来控制逻辑。

combNode节点需要播放不同的动画,所以组件中没有指定资源,这个在脚本中控制。看一下dragonBonesCtr.js的属性:

//dragonBonesCtr.js
properties: {
    asset: {
        default: [],
        type: dragonBones.DragonBonesAsset,
    },
    atlasAsset: {
        default: [],
        type: dragonBones.DragonBonesAtlasAsset,
    },
    combName: {
        default: [],
        type: cc.String,
    },
    _anim: dragonBones.ArmatureDisplay,
},

asset、atlasAsset分别存储骨骼动画资源,combName中存储骨骼动画的名字,和资源数组一一对应,_anim是dragonBones组件。

//dragonBonesCtr.js
onLoad () {
    this._anim = this.node.getComponent(dragonBones.ArmatureDisplay);
},
playComb (type) {
    var i = this.combName.indexOf(type);
    if (i >= 0) {
        this._anim.dragonAsset = this.asset[I];
        this._anim.dragonAtlasAsset = this.atlasAsset[I];
        this._anim.armatureName = "armatureName";
        this._anim.playAnimation("Animation1");
    }
},

playComb即是播放特效的方法。

//matrixCtr.js
showComboEffect (count) {
    if (count == 5) {
        this.combCtr.playComb("GOOD");
    }
    else if (count >= 6 && count <= 7) {
        this.combCtr.playComb("NICE");
    }
    else if (count >= 8 && count <= 9) {
        this.combCtr.playComb("EXCELLENT");
    }
    else if (count >= 10) {
        this.combCtr.playComb("UNBELIEVABLE");
    }
},

combCtr是脚本组件matrixCtr中的属性,即是场景中ActionRoot节点的脚本组件。

//matrixCtr.js
properties: {
    //省略其他属性
    combCtr: cc.Node,
},
onLoad () {
    //其他无关代码这里省略
    this.combCtr = this.combCtr.getComponent("dragonBonesCtr");
},

数据处理

将需要消除的星星对应的坐标清空(赋值-1)

//gamedata.js
//清除星星
function cleanStarData (list) {
    list.forEach(function (elem, index, arr) {
        this.starMatrix[elem.x][elem.y] = -1;
    }, this);
};

消除星星

按规则星星是一个一个消除的,所以bomb会递归调用,直到所有星星都消除。在消除星星的同时,有分数计算和动画逻辑。

//matrixCtr.js
bomb (list, count) {
    if (list.length > 0) {
        var gridPos = list.shift();
        var index = Utils.indexValue(gridPos.x, gridPos.y);
        this.bombStar(GameData.starSprite[index]);
        GameData.starSprite[index] = null;
        ++this._currCount;
        //单个方块的分数动画
        var wp = this.convertGridPositionToWorldSpaceAR(gridPos);
        var starScore = Utils.getOneScore(count-list.length);
        this.actCtr.playSingleScoreAction(starScore, wp);
        
        this.scheduleOnce(function () {
            this.bomb(list, count);
        }, 0.1);
        
        if (list.length == 0) {
            //消除总得分动画
            var wp = this.convertGridPositionToWorldSpaceAR(gridPos);
            this.actCtr.showScoreAction(Utils.getScore(count), wp);
            this.uiCtr.updateScoreSchedule(starScore); //当前总分滚动累计效果
        }
        else {
            this.uiCtr.updateScore(starScore);
        }
        this.checkIsSuccessed(); //检测是否达到目标分
    }
    else {
        //TODO: 星星消除完的逻辑处理
    }
},

星星的移除是在方法bombStar中处理的,在创建星星的时候使用了对象池,所以移除时把它重新放入对象池。

//matrixCtr.js
bombStar (node) {
    if (node) {
        var p = node.getPosition();
        var type = node.getComponent("starCtr")._starType;
        this._starPool.put(node); //移除星星,把它放入对象池中
        // 星星爆炸动画
        var particle = cc.instantiate(this.starParticle);
        particle.setPosition(p);
        this.node.addChild(particle);
        particle.getComponent("particleCtr").init(type);
    }
},

在移除星星的同时,伴随有星星爆炸的特效。starParticle是一个预制,层级很简单,在一个空节点中,添加Particle System组件和脚本组件particleCtr。

particleCtr

starSpriteFrames中存储了粒子系统使用的纹理资源,对应每一种星星。

//particleCtr.js
properties: {
    particle: cc.ParticleSystem,
    starSpriteFrames: {
        default: [],
        type: cc.SpriteFrame,
    },
},
init (type) {
    this.particle.spriteFrame = this.starSpriteFrames[type];
    this.particle.resetSystem();
},

Particle System组件设置自动移除,在属性检查器中勾选Auto Remove On Finish选项。

分数计算

我们知道一次消除星星方块越多,得分越高。

//Utils.js
//消除得分计算
function getScore (count) {
    var score = 0;
    for(var i = 0; i < count; i++) {
        score += this.getOneScore(i);
    }
    return score;
};
//消除第i个方块的分数
function getOneScore (i) {
    return 10 + (i-1) * 5;
};

分数动画有几种:

  • 单个方块的分数动画
  • 消除总得分动画
  • 当前总分有滚动累计的效果
//matrixCtr.js
//格子的世界坐标
convertGridPositionToWorldSpaceAR (gp) {
    var p = Utils.grid2Pos(gp.x, gp.y);
    var wp = this.node.convertToWorldSpaceAR(p);
    return wp;
},

动画在actionCtr.js中处理:

//actionCtr.js
//单个方块的分数动画
playSingleScoreAction (score, wp) {
    var label = null;
    if (this._pScorePool.size() > 0) {
        label = this._pScorePool.get();
    }
    else {
        label = cc.instantiate(this.partScore);
    }
    label.getComponent("partScore").setScore(score);
    this.node.addChild(label);
    label.setPosition(this.convertPosition(wp));

    var action = cc.spawn(cc.scaleTo(0.5, 0.4), cc.moveTo(0.5, this.refrencePoint.getPosition()));
    label.runAction(cc.sequence(action, cc.delayTime(0.2), cc.callFunc(function(){
        this._pScorePool.put(label);
    }, this)));
},
// 世界坐标转成当前节点中的坐标
convertPosition (wp) {
    return this.node.convertToNodeSpaceAR(wp);
},

因为分数也会被频繁的创建和移除,所以也使用了对象池,分数的预制制作后面介绍。

//actionCtr.js
properties: {
    // 省略其他无关属性
    totalScore: cc.Prefab,
    _tScorePool: null,
    
    partScore: cc.Prefab,
    _pScorePool: null,
    poolCapacity: 30,
},
onLoad () {
    this._pScorePool = new cc.NodePool();
    for (var i = 0; i < this.poolCapacity; ++i) {
        var partscore = cc.instantiate(this.partScore);
        this._pScorePool.put(partscore);
    }
    this._tScorePool = new cc.NodePool();
    for (var i = 0; i < 3; ++i) {
        var totalscore = cc.instantiate(this.totalScore);
        this._tScorePool.put(totalscore);
    }
},

与单个方块的分数动画一样,消除总得分动画:

//actionCtr.js
//消除总得分动画
showScoreAction (score, wp) {
    var node = null;
    if (this._tScorePool.size() > 0) {
        node = this._tScorePool.get();
    }
    else {
        node = cc.instantiate(this.totalScore);
    }
    this.node.addChild(node);

    node.getComponent("totalScore").setScore(score);
    var lw = node.getComponent("totalScore").scoreLabel.node.width;
    var ddd = lw * 0.5 * 1.2;
    var eddd = ddd - cc.winSize.width*0.5;
    var p = this.convertPosition(wp);
    if (p.x < eddd) {
        p.x = eddd;
    }
    if (p.x > cc.winSize.width*0.5 - ddd) {
        p.x = cc.winSize.width*0.5 - ddd;
    }
    node.setPosition(p);
},

分数预制

分数预制

层级结构很简单,都是空节点下加一个Label节点。父节点上都有一个脚本组件partScore、totalScore。

//partScore.js
properties: {
    scoreLabel: cc.Label,
},
setScore (score) {
    this.scoreLabel.string = score;
},

脚本也很简单,setScore方法给Label赋值。

//totalScore.js
properties: {
    scoreLabel: cc.Label,
},
setScore (score) {
    this.scoreLabel.string = "+"+score;
    this.playAnim();
},
playAnim () {
    var anim = this.scoreLabel.getComponent(cc.Animation);
    anim.play();
},

与单个分数不同的,总得分的Label动画使用Creator的Animation编辑器制作。所以,预制中需要在节点label中添加Animation组件,在这里我们在添加一个脚本组件totalScoreLabel,这个脚本主要处理Animation动画的事件回调方法。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容