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

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

然后又是熟悉的套路,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) {
// },
});

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

所以我的做法是,將已經被創(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();
},

接下來,把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ā)函數:

現在炸彈的功能基本實現了(代碼在這里),接下來需要做的就是,Hero觸發(fā)炸彈,炸彈計數+1,沒有炸彈的時候,是不可以使用的。
回憶一下,項目剛開始的時候,有個全局變量對象,此時想一下如何使用它:
// global.js
// declare global variable "D"
window.D = {
// singletons
common: null, //公共方法
commonState: {}, //定義的一些常量
};
需要做的修改比較雜,單都很好理解,代碼放在這里了??梢院煤每匆幌?a target="_blank" rel="nofollow">CCClass進階參考,有關為什么用箭頭函數,這里都會有答案。
知識點:
.toString()可以將所有的的數據都轉換為字符串,但是要排除null和undefined
String()可以將null和undefined轉換為字符串,但是沒法轉進制字符串
得分
先給enemy腳本組件添加分數屬性,然后要給Prefab的屬性選擇器中輸入數值:

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。

接下來講一下思路,敵機摧毀得分,敵機穿過戰(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)化