aircraft-war(一)
Game Scene 布局
首先制作場景中的部件,早cocos creator中創(chuàng)建,然后放到適當(dāng)?shù)奈恢茫?/p>

接下來將組件綁定到腳本上,先創(chuàng)建一個main腳本作為Game場景的,首先要考慮的是界面中的固定布局元素,比如:分數(shù),暫停按鈕,炸彈等。所以腳本中先構(gòu)造這些布局元素:
properties: {
pause: cc.Button,
scoreDisplay: cc.Label,
bombAmount: cc.Label,
bombDisplay: cc.Node
},

接下來分別給這些組件添加widge布局組件進行布局處理。舉一個例子,其余的也差不多類似:

接下來處理暫停按鈕,要讓暫停按鈕按下后替換成開始的按鈕,實現(xiàn)的思路很多,例如切換兩個button組件,這里使用替換圖片的方式。首先要在main腳本中添加按鈕圖片組:
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];
return pause = !pause
}
this.pause.normalSprite = this.pauseSprite[2];
this.pause.pressedSprite = this.pauseSprite[3];
this.pause.hoverSprite = this.pauseSprite[3];
return pause = !pause;
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
這里需要注意
在handlePause中使用了ES6的箭頭函數(shù)語法,但是this找不到上下文,有可能是兼容上還有些問題,保持function寫法即可。

這時可以啟動游戲試試看是否如預(yù)期的結(jié)果那樣。
Hero移動
接下來來制作Hero。首先想一想可能面臨的問題都有哪些?
首先要處理玩家拖動Hero移動,接下來Hero是可以發(fā)射子彈的,當(dāng)然,Hero被敵機撞擊是會爆炸的,爆炸涉及的是碰撞檢測后播放爆炸動畫。
那么先從Hero移動開始做起,先創(chuàng)建Hero精靈。

創(chuàng)建好Hero精靈后,需要將hero圖片添加到here節(jié)點上的Sprite屬性中的Sprite Frame中。然后還要添加一個動畫組件,并且在左下角的資源區(qū)創(chuàng)建Animation資源。然后將其添加到hero節(jié)點上的Animation組件中。
編輯動畫如下所示:

接下來處理Hero移動,游戲中按住Hero來進行拖動,所以需要監(jiān)聽“觸摸事件”類型。CCC系統(tǒng)事件類型創(chuàng)建hero腳本來處理Hero相關(guān)的事物。
cc.Class({
extends: cc.Component,
properties: {
},
// use this for initialization
onLoad: function () {
// 監(jiān)聽拖動事件
this.node.on('touchmove', this.onHandleHeroMove, this);
},
onHandleHeroMove: function (event) {
// touchmove事件中 event.getLocation() 獲取當(dāng)前已左下角為錨點的觸點位置(world point)
let position = event.getLocation();
// 實際hero是background的子元素,所以坐標應(yīng)該是隨自己的父元素進行的,所以要將“world point”轉(zhuǎn)化為“node point”
let location = this.node.parent.convertToNodeSpaceAR(position);
this.node.setPosition(location);
}
});
接下來將腳本綁定到Hero精靈上就可以啟動看看Hero已經(jīng)可以被拖動了。
Hero發(fā)射子彈
先想一下發(fā)射子彈這個動作,需要怎么去實現(xiàn)?不知道大家有沒有注意過大街上的LED廣告牌,橫向移動的字會讓你覺得“字”是在移動的。而原理和幀動畫差不多,就是每個亮起的顯示單元不停的在變化,從而造成“移動的錯覺”。
子彈是否可以像這樣的實現(xiàn)思路去做呢?把子彈依次排開,鋪滿整個屏幕,Hero有一個出發(fā)點去觸發(fā)亮起的子彈,然后依次亮起該列向上所有的子彈。我覺得是可以的,說實話第一次看到這個游戲,第一時間想到的就是LED顯示牌。
那么還是換一種更“cocos”的方式來做,和剛剛實現(xiàn)Hero移動的方式一樣。子彈是自動發(fā)射,所以要處理的是獲取到當(dāng)前Hero的位置,然后從當(dāng)前位置,不斷累加“positionY”的值來實現(xiàn)向上移動。
無限子彈(基礎(chǔ)版)
首先要明確一下游戲規(guī)則,游戲中分為兩種類型的子彈,普通單道子彈和道具雙道子彈,現(xiàn)在先來制作普通單道子彈。
有點需要注意,ccc中不斷重復(fù)創(chuàng)建的組件做成Prefab這個是很有必要的,雖然不用Prefab也是可以達到目的。
首先把圖片資源從資源管理器中拖到層級管理器中,在屬性檢查器中調(diào)整好大小等參數(shù)后,將其拖拽到資源管理器相對應(yīng)的目錄下即可,然后刪除層級管理器中的原資源即可。(圖中屬性是隨意拖拽顯示的,具體請自己嘗試。)

接下來開始編寫腳本,首先需要知道的參數(shù)應(yīng)該有位置、速度這兩個參數(shù)目前就夠了,位置需要通過獲取Hero的位置原點,速度可以由自己給出,所以腳本如下:
// 提供思路參考用的代碼
cc.Class({
extends: cc.Component,
properties: {
speed: cc.Integer,
bullet: cc.Prefab
},
// use this for initialization
onLoad: function () {
// cc.instantiate() 克隆指定的任意類型的對象,或者從 Prefab 實例化出新節(jié)點。
this.newNode = cc.instantiate(this.bullet);
this.node.addChild(this.newNode);
this.newNode.setPosition({x: 0, y: 0});
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
this.newNode.y += dt * this.speed;
},
});
起初我的想法是,將上述腳本綁在Hero上,這樣{x: 0, y: 0}就是子彈發(fā)出的起始點,但是實際運行中,出現(xiàn)的問題是,Hero是需要移動的,子彈Prefab也就作為了Hero的子元素,會隨著Hero移動而移動,如下GIF所以:

所以子彈應(yīng)該單獨作為一層去處理,或者將bullet腳本綁在“background”層上,然后將Hero的位置通過傳參的形式傳過來即可。參考代碼是單獨用子彈層處理所有子彈的事件,所以也參考這種做法,用單獨的層級去處理這個層級的所有相關(guān)事物。
在層級管理器中創(chuàng)建空節(jié)點并命名為bulletGroup,這個節(jié)點需要做的事就是處理Hero與Bullet的發(fā)射位置與發(fā)射頻率。所以首先需要的就是Hero與Bullet-Prefab,發(fā)射頻率的話,需要用到ccc中的定時器來實現(xiàn)固定間隔創(chuàng)建bullet節(jié)點并發(fā)射炮彈的功能,所以需要一個cc.Integer類型的變量。
有一個非常值得注意的性能問題:
在運行時進行節(jié)點的創(chuàng)建(cc.instantiate)和銷毀(node.destroy)操作是非常耗費性能的,因此在比較復(fù)雜的場景中,通常只有在場景初始化邏輯(onLoad)中才會進行節(jié)點的創(chuàng)建,在切換場景時才會進行節(jié)點的銷毀。
所以,要實現(xiàn)不間斷發(fā)射子彈,除了定時器,還需要引入對象池(cc.NodePool)。
無限子彈(進階版)
下面開始構(gòu)建腳本:
首先修改腳本bullet.js,bullet作為Prefab,只需要完成自己作為子彈的使命,那就是發(fā)射,銷毀,碰撞檢測。所以先來做一個只有發(fā)射的基礎(chǔ)子彈腳本。
cc.Class({
extends: cc.Component,
properties: {
speed: cc.Integer,
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
this.node.y += dt * this.speed;
},
});
然后將腳本添加到bullet的Prefab上,設(shè)定速度為1500。

接著開始編寫bulletGroup的腳本:
cc.Class({
extends: cc.Component,
properties: {
bullet: cc.Prefab,
hero: cc.Node,
rate: cc.Integer
},
onLoad: function () {
// 創(chuàng)建子彈對象池
this.genBulletPool();
// 設(shè)置定時器,每個0.2s創(chuàng)建一個新的bullet
this.schedule(function () {
this.startShoot(this.bulletPool)
}.bind(this), this.rate);
},
genBulletPool: function () {
this.bulletPool = new cc.NodePool();
let initCount = 100;
for (let i = 0; i < initCount; ++i) {
let newBullet = cc.instantiate(this.bullet); // 創(chuàng)建節(jié)點
this.bulletPool.put(newBullet); // 通過 putInPool 接口放入對象池
}
},
//獲取子彈位置
getBulletPosition: function(){
let heroP = this.hero.getPosition();
let newV2_x = heroP.x;
let newV2_y = heroP.y;
return cc.p(newV2_x, newV2_y);
},
// 發(fā)射子彈
startShoot: function (pool) {
let newNode = null;
if (pool.size() > 0) {
newNode = pool.get();
this.node.addChild(newNode);
let p = this.getBulletPosition();
newNode.setPosition(p);
}
},
//銷毀子彈
destroyBullet: function (bullet) {
}
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
然后將組建綁到腳本上:

在bulletGroup腳本中,直接給對象池中放了一百發(fā)子彈,打完了卻沒有回收,這是不合理的,接下來要處理的就是回收資源。要注意的是對象池中的數(shù)量與發(fā)射子彈的關(guān)系,如果對象池中的對象用完了,而這時卻沒有及時補充,就會“延遲發(fā)貨”。可以試著調(diào)整bulletCount來驗證效果。如果對象池中的對象太多,每次最多只能用10個,之后就會被補充進來,那么剩下的就會浪費了。
如下bulletGroup.js:
cc.Class({
extends: cc.Component,
properties: {
bullet: cc.Prefab,
hero: cc.Node,
rate: cc.Integer,
bulletCount: {
default: 10,
type: cc.Integer
}
},
onLoad: function () {
this.genBulletPool();
this.schedule(function () {
this.startShoot(this.bulletPool)
}.bind(this), this.rate);
// 將對象池添加到window對象中,方便瀏覽器查看對象池狀態(tài)
window.pool = this.bulletPool;
},
genBulletPool: function () {
this.bulletPool = new cc.NodePool();
for (let i = 0; i < this.bulletCount; ++i) {
let newBullet = cc.instantiate(this.bullet); // 創(chuàng)建節(jié)點
this.bulletPool.put(newBullet); // 通過 putInPool 接口放入對象池
}
},
//獲取子彈位置
getBulletPosition: function(){
let heroP = this.hero.getPosition();
let newV2_x = heroP.x;
let newV2_y = heroP.y;
return cc.p(newV2_x, newV2_y);
},
startShoot: function (pool) {
let newNode = null;
if (pool.size() > 0) {
newNode = pool.get();
this.node.addChild(newNode);
let p = this.getBulletPosition();
newNode.setPosition(p);
newNode.getComponent('bullet').bulletGroup = this;
}
},
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});

子彈的銷毀就是當(dāng)子彈飛出屏幕之后,將其重新放回對象池中,這樣一直都是對象池中的對象在被使用,而沒有不斷創(chuàng)建新的對象。目前先在bullet腳本中去處理對象銷毀。
cc.Class({
extends: cc.Component,
properties: {
speed: cc.Integer,
},
// use this for initialization
onLoad: function () {
},
// 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){
this.bulletGroup.bulletPool.put(this.node);
}
},
});
目前無限子彈類型已經(jīng)差不多完成了,但是只是實現(xiàn)了功能,接下來做雙彈道的子彈。