aircraft-war(五)

aircraft-war(五)

大體上,這個游戲已經有了最初設計的樣子,還差:

  • Hero被敵機撞擊會墜毀
  • 游戲開始暫停
  • 掉落道具——使用道具
  • 得分系統
  • 音效
  • 完善游戲開始、結束
  • 優(yōu)化

Hero被敵機撞擊會墜毀

接下來一步步來完善這些點,先來讓無敵的Hero變成平民。首先,和敵機一樣,給Hero添加爆炸動畫:

  // 碰撞組件
    onCollisionEnter: function (other, self) {
        if (other.node.name === 'doubleBullet') {
            this.bulletGroup.changeBullet(other.node.name);
        }
        if (other.node.group === 'enemy') {
            console.log(other.node);
            let anim = this.getComponent(cc.Animation);
            let animName = this.node.name + '_exploding';
            anim.play(animName);
            anim.on('finished', this.onHandleDestroy, this);
        }
    },
    onHandleDestroy: function () {
        this.node.destroy();
        // 暫停正在運行的場景,該暫停只會停止游戲邏輯執(zhí)行,但是不會停止渲染和 UI 響應
        cc.director.pause();
    }
image.png

現在Hero被敵機撞擊后,就會爆炸,然后讓游戲暫停,已然變成了平民。

游戲開始暫停

游戲暫停開始,配合剛開始做的地方main.js,只需要加上如下代碼:

let pause = false;

cc.Class({
    extends: cc.Component,

    properties: {
        pause: cc.Button,
        scoreDisplay: cc.Label,
        bombAmount: cc.Label,
        bombDisplay: cc.Node,
        pauseSprite: {
          default: [],
          type: cc.SpriteFrame,
          tooltip:'暫停按鈕圖片組',
        },
    },

    // use this for initialization
    onLoad: function () {

    },
    // 暫停
    handlePause: function () {
        if (pause) {
            this.pause.normalSprite = this.pauseSprite[0];
            this.pause.pressedSprite = this.pauseSprite[1];
            this.pause.hoverSprite = this.pauseSprite[1];
            // 暫停正在運行的場景
            cc.director.resume();
            return pause = !pause
        }
        this.pause.normalSprite = this.pauseSprite[2];
        this.pause.pressedSprite = this.pauseSprite[3];
        this.pause.hoverSprite = this.pauseSprite[3];
        // 開始正在運行的場景
        cc.director.pause();
        return pause = !pause;
    },


    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});

現在開始暫停的功能就完成了,跑起來運行一下,發(fā)現一個問題:暫停時hero還是可以被拖動。
檢查一下hero的腳步,發(fā)現是添加的監(jiān)聽沒有被關閉,所以需要把hero的移除監(jiān)聽方法交給main去執(zhí)行。
需要改變以下兩個腳本:

// hero.js
cc.Class({
      // ...
    // use this for initialization
    onLoad: function () {
        // 監(jiān)聽拖動事件
        this.onDrag();
        // 獲取碰撞檢測系統
        let manager = cc.director.getCollisionManager();
        // 開啟碰撞檢測系統
        manager.enabled = true;
    },
    // 添加拖動監(jiān)聽
    onDrag: function () {
        this.node.on('touchmove', this.onHandleHeroMove, this);
    },
    // 去掉拖動監(jiān)聽
    offDrag: function(){
        this.node.off('touchmove', this.onHandleHeroMove, this);
    },
    // Hero拖動
    onHandleHeroMove: function (event) {
        // touchmove事件中 event.getLocation() 獲取當前已左下角為錨點的觸點位置(world point)
        let position = event.getLocation();
        // 實際hero是background的子元素,所以坐標應該是隨自己的父元素進行的,所以要將“world point”轉化為“node point”
        let location = this.node.parent.convertToNodeSpaceAR(position);
        this.node.setPosition(location);
    },
    
    // 碰撞組件
    onCollisionEnter: function (other, self) {
        if (other.node.name === 'doubleBullet') {
            this.bulletGroup.changeBullet(other.node.name);
        }
        if (other.node.group === 'enemy') {
            let anim = this.getComponent(cc.Animation);
            let animName = this.node.name + '_exploding';
            anim.play(animName);
            anim.on('finished', this.onHandleDestroy, this);
        }
    },
    onHandleDestroy: function () {
        // this.node.destroy();
        // 暫停正在運行的場景,該暫停只會停止游戲邏輯執(zhí)行,但是不會停止渲染和 UI 響應
        this.offDrag();
        // this.pause();
        cc.director.pause();
    }
});
// ...
let pause = false;

cc.Class({
    extends: cc.Component,

    properties: {
        pause: cc.Button,
        scoreDisplay: cc.Label,
        bombAmount: cc.Label,
        bombDisplay: cc.Node,
        pauseSprite: {
          default: [],
          type: cc.SpriteFrame,
          tooltip:'暫停按鈕圖片組',
        },
        hero: {
            default: null,
            type: require('hero')
        },
    },
    // use this for initialization
    onLoad: function () {

    },
    // 暫停
    handlePause: function () {
        if (pause) {
            this.pause.normalSprite = this.pauseSprite[0];
            this.pause.pressedSprite = this.pauseSprite[1];
            this.pause.hoverSprite = this.pauseSprite[1];
            // 開始正在運行的場景
            cc.director.resume();
            // 添加Hero拖拽監(jiān)聽
            this.hero.onDrag();
            return pause = !pause
        }
        this.pause.normalSprite = this.pauseSprite[2];
        this.pause.pressedSprite = this.pauseSprite[3];
        this.pause.hoverSprite = this.pauseSprite[3];
        // 暫停正在運行的場景
        cc.director.pause();
        // 移除Hero拖拽監(jiān)聽
        this.hero.offDrag();
        return pause = !pause;
    },
    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});

代碼在這里

掉落道具

目前掉落的道具有兩種,一種是雙彈道子彈,一種是炸彈。前者已經實現了功能,后者還沒有實現功能。現在先來實現隨機掉落,參考enemyGroup的實現方式,沒有太大區(qū)別。
先添加一個ufo的腳步,制作一個tnt-ufo組件,再將腳本掛在兩種道具上,然后將組件從層級選擇器拖拽至資源選擇器變成Prefab:

cc.Class({
    extends: cc.Component,

    properties: {
        speedMax: 0,
        speedMin: 0,
    },

    // use this for initialization
    onLoad: function () {
        // 速度隨機[speedMax, speedMin]
        this.speed = Math.random() * (this.speedMax - this.speedMin + 1) + this.speedMin;

        let manager = cc.director.getCollisionManager();
        manager.enabled = true;
    },
    //碰撞檢測
    onCollisionEnter: function(other, self){
        this.ufoGroup.destroyUfo(this.node);
    },
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
        this.node.y -= dt * this.speed;
        //出屏幕后
        if (this.node.y < -this.node.parent.height / 2) {
            this.ufoGroup.destroyUfo(this.node);
        }
    },
});
image.png

然后又是熟悉的套路,ufoGroup:

const ufoG = cc.Class({
   name: 'ufoG',
   properties: {
       name: '',
       prefab: cc.Prefab,
       freq: 0,
       poolAmount: 0,
       delayMax: {
           default: 0,
           tooltip: '最大延時'
       },
       delayMin: {
           default: 0,
           tooltip: '最小延時'
       },
   }
});

cc.Class({
    extends: cc.Component,

    properties: {
        ufoG: {
            default: [],
            type: ufoG
        }
    },

    // use this for initialization
    onLoad: function () {
        D.common.batchInitNodePool(this, this.ufoG);
        this.startAction();
    },
    // 填充彈藥
    startAction: function () {
        for(let i = 0; i < this.ufoG.length; i++) {
            let ufoName = this.ufoG[i].name;
            let freq = this.ufoG[i].freq;
            this[ufoName] = function (ii) {
                let delay = Math.random() * (this.ufoG[ii].delayMax - this.ufoG[ii].delayMin) + this.ufoG[ii].delayMin;
                // 內存定時器,隨機掉落時間
                this.scheduleOnce(function() {
                    this.genNewUfo(this.ufoG[ii]);
                }.bind(this), delay);
            }.bind(this, i);
            // 外層定時器,循環(huán)掉落
            this.schedule(this[ufoName], freq);
        }
    },
    // 生成ufo
    genNewUfo: function (ufoInfo) {
        let poolName = ufoInfo.name + 'Pool';
        let newNode = D.common.genNewNode(this[poolName], ufoInfo.prefab, this.node);
        let pos = this.getNewEnemyPosition(newNode);
        newNode.setPosition(pos);
        newNode.getComponent('ufo').ufoGroup = this;
    },
    //隨機生成的位置
    getNewEnemyPosition: function(newEnemy) {
        //位于上方,先不可見
        var randx = cc.randomMinus1To1() * (this.node.parent.width / 2 - newEnemy.width / 2);
        var randy = this.node.parent.height / 2 + newEnemy.height / 2;
        return cc.v2(randx,randy);
    },
    // 銷毀
    destroyUfo: function (node) {
        D.common.putBackPool(this, node);
    }

    // called every frame, uncomment this function to activate update callback
    // update: function (dt) {

    // },
});
image.png

基本和之前的實現方式一樣,具體代碼可以參考代碼在這里

使用道具(TNT炸彈)

炸彈道具可以銷毀當前屏幕內所有的敵機,也就是將當前被創(chuàng)建的敵機放回自己的對象池。
在原作者A123asdo11的代碼中,他是直接使用this.enemyGroup.node.removeAllChildren();銷毀parent下所有的子節(jié)點。但是這樣對象池空了,就會創(chuàng)建新的對象,這樣不斷重復,對象池沒有被很好的利用,以下是測試結果截圖:

image.png

所以我的做法是,將已經被創(chuàng)建的敵機重新放回對象池中,如果想要效果更好,那么就引爆所有敵機。
首先,先來整理一下代碼,把組件的開關交給mainScript組件:

// main.js
properties: {
        pause: cc.Button,
        scoreDisplay: cc.Label,
        bombAmount: cc.Label,
        bombDisplay: cc.Node,
        pauseSprite: {
          default: [],
          type: cc.SpriteFrame,
          tooltip:'暫停按鈕圖片組',
        },
        hero: {
            default: null,
            type: require('hero')
        },
        bulletGroup: require('bulletGroup'),
        enemyGroup: require('enemyGroup'),
        ufoGroup: require('ufoGroup'),
    },

    // use this for initialization
    onLoad: function () {
        this.enemyGroup.startAction();
        this.bulletGroup.startAction();
        this.ufoGroup.startAction();
    },
image.png

接下來,把bulletGroup ufoGroup enemyGroup中的startAction方法從onload中去掉,交給main.js:

 // use this for initialization
    onLoad: function () {
        this.enemyGroup.startAction();
        this.bulletGroup.startAction();
        this.ufoGroup.startAction();
    },

接著就是炸彈的功能了,在創(chuàng)建對象的時候,不管是敵機組還是子彈組,都綁在各自的**Group組件上,作為他們各自的children,所以,”出現的敵機” === nemyGroup.node.children所以:

    // 使用tnt炸彈
    useBomb: function () {
        // 把當前的node.children 賦值給一個新的對象
        let enemy = new Array(...this.enemyGroup.node.children);
        for(let i = 0; i < enemy.length; i++) {
            enemy[i].getComponent('enemy').explodingAnim();
        }
    }

然后再給bombDisplay加上Button組件,然后給click events添加一個觸發(fā)函數:


image.png

現在炸彈的功能基本實現了(代碼在這里),接下來需要做的就是,Hero觸發(fā)炸彈,炸彈計數+1,沒有炸彈的時候,是不可以使用的。
回憶一下,項目剛開始的時候,有個全局變量對象,此時想一下如何使用它:

// global.js
// declare global variable "D"
window.D = {
    // singletons
    common: null, //公共方法
    commonState: {}, //定義的一些常量
};

需要做的修改比較雜,單都很好理解,代碼放在這里了??梢院煤每匆幌?a target="_blank" rel="nofollow">CCClass進階參考,有關為什么用箭頭函數,這里都會有答案。
知識點:

.toString()可以將所有的的數據都轉換為字符串,但是要排除nullundefined
String()可以將nullundefined轉換為字符串,但是沒法轉進制字符串

得分

先給enemy腳本組件添加分數屬性,然后要給Prefab的屬性選擇器中輸入數值:

image.png

enemy.js:

 properties: {
        score: {
            default: 0,
            type: cc.Integer,
            tooltip: '敵機分數',
        },
        HP: {
            default: 0,
            type: cc.Integer,
            tooltip: '敵機血量',
        },
        speedMax: 0,
        speedMin: 0,
        initSpriteFrame: {
            default: null,
            type: cc.SpriteFrame,
            tooltip: '初始化圖像'
        }
    },

然后要給敵機Prefab的屬性檢查器中的score賦值,enemyGroup腳本屬性中添加mainScript。


image.png

接下來講一下思路,敵機摧毀得分,敵機穿過戰(zhàn)區(qū)不得分,所以可以把分數傳給enemyGroup來處理,然后賦值給全局變量。

// enemy.js
   this.enemyGroup.destroyEnemy(this.node, this.score);
// enemyGroup.js
// 銷毀
    destroyEnemy: function (node, score = 0) {
        D.common.putBackPool(this, node);
        score && this.mainScript.changeScore(score);
    }
// main.js
   // 分數
    changeScore: function (score) {
        console.log(score);
        D.commonState.gameScore += score;
        this.scoreDisplay.string = D.commonState.gameScore.toString();
    }

很好理解,代碼在這里

總結

到這里,游戲的整體功能大體就已經完成了,首先非常感謝A123asdo11,他的代碼寫的質量非常好,而且通過這個小游戲,自己很快入了門。其次非常感謝cocos creator 的團隊,非常感謝你們辛苦的工作與付出,讓creator變得如此的優(yōu)秀易用。

好了,最后還剩三個部分需要做,放在最后一章總結。

  • 音效
  • 完善游戲開始、結束
  • 優(yōu)化
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容