aircraft-war(一)
Game Scene 布局
首先制作場(chǎng)景中的部件,早cocos creator中創(chuàng)建穷蛹,然后放到適當(dāng)?shù)奈恢茫?/p>
接下來(lái)將組件綁定到腳本上株婴,先創(chuàng)建一個(gè)main
腳本作為Game
場(chǎng)景的践美,首先要考慮的是界面中的固定布局元素朗兵,比如:分?jǐn)?shù),暫停按鈕柿隙,炸彈等叶洞。所以腳本中先構(gòu)造這些布局元素:
properties: {
pause: cc.Button,
scoreDisplay: cc.Label,
bombAmount: cc.Label,
bombDisplay: cc.Node
},
接下來(lái)分別給這些組件添加widge
布局組件進(jìn)行布局處理。舉一個(gè)例子禀崖,其余的也差不多類似:
接下來(lái)處理暫停按鈕衩辟,要讓暫停按鈕按下后替換成開(kāi)始的按鈕,實(shí)現(xiàn)的思路很多波附,例如切換兩個(gè)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ù)語(yǔ)法掸屡,但是this
找不到上下文封寞,有可能是兼容上還有些問(wèn)題,保持function
寫(xiě)法即可仅财。
這時(shí)可以啟動(dòng)游戲試試看是否如預(yù)期的結(jié)果那樣狈究。
Hero移動(dòng)
接下來(lái)來(lái)制作Hero。首先想一想可能面臨的問(wèn)題都有哪些盏求?
首先要處理玩家拖動(dòng)Hero移動(dòng)抖锥,接下來(lái)Hero是可以發(fā)射子彈的亿眠,當(dāng)然,Hero被敵機(jī)撞擊是會(huì)爆炸的磅废,爆炸涉及的是碰撞檢測(cè)后播放爆炸動(dòng)畫(huà)纳像。
那么先從Hero移動(dòng)開(kāi)始做起,先創(chuàng)建Hero精靈拯勉。
創(chuàng)建好Hero精靈后竟趾,需要將hero圖片添加到here節(jié)點(diǎn)上的Sprite屬性中的Sprite Frame中。然后還要添加一個(gè)動(dòng)畫(huà)組件谜喊,并且在左下角的資源區(qū)創(chuàng)建Animation
資源。然后將其添加到hero節(jié)點(diǎn)上的Animation
組件中倦始。
編輯動(dòng)畫(huà)如下所示:
接下來(lái)處理Hero移動(dòng)斗遏,游戲中按住Hero來(lái)進(jìn)行拖動(dòng),所以需要監(jiān)聽(tīng)“觸摸事件”類型鞋邑。CCC系統(tǒng)事件類型創(chuàng)建hero
腳本來(lái)處理Hero相關(guān)的事物诵次。
cc.Class({
extends: cc.Component,
properties: {
},
// use this for initialization
onLoad: function () {
// 監(jiān)聽(tīng)拖動(dòng)事件
this.node.on('touchmove', this.onHandleHeroMove, this);
},
onHandleHeroMove: function (event) {
// touchmove事件中 event.getLocation() 獲取當(dāng)前已左下角為錨點(diǎn)的觸點(diǎn)位置(world point)
let position = event.getLocation();
// 實(shí)際hero是background的子元素,所以坐標(biāo)應(yīng)該是隨自己的父元素進(jìn)行的枚碗,所以要將“world point”轉(zhuǎn)化為“node point”
let location = this.node.parent.convertToNodeSpaceAR(position);
this.node.setPosition(location);
}
});
接下來(lái)將腳本綁定到Hero精靈上就可以啟動(dòng)看看Hero已經(jīng)可以被拖動(dòng)了逾一。
Hero發(fā)射子彈
先想一下發(fā)射子彈這個(gè)動(dòng)作,需要怎么去實(shí)現(xiàn)肮雨?不知道大家有沒(méi)有注意過(guò)大街上的LED廣告牌遵堵,橫向移動(dòng)的字會(huì)讓你覺(jué)得“字”是在移動(dòng)的。而原理和幀動(dòng)畫(huà)差不多怨规,就是每個(gè)亮起的顯示單元不停的在變化陌宿,從而造成“移動(dòng)的錯(cuò)覺(jué)”。
子彈是否可以像這樣的實(shí)現(xiàn)思路去做呢波丰?把子彈依次排開(kāi)壳坪,鋪滿整個(gè)屏幕,Hero有一個(gè)出發(fā)點(diǎn)去觸發(fā)亮起的子彈掰烟,然后依次亮起該列向上所有的子彈爽蝴。我覺(jué)得是可以的,說(shuō)實(shí)話第一次看到這個(gè)游戲纫骑,第一時(shí)間想到的就是LED顯示牌蝎亚。
那么還是換一種更“cocos”的方式來(lái)做,和剛剛實(shí)現(xiàn)Hero移動(dòng)的方式一樣先馆。子彈是自動(dòng)發(fā)射颖对,所以要處理的是獲取到當(dāng)前Hero的位置,然后從當(dāng)前位置磨隘,不斷累加“positionY”的值來(lái)實(shí)現(xiàn)向上移動(dòng)缤底。
無(wú)限子彈(基礎(chǔ)版)
首先要明確一下游戲規(guī)則顾患,游戲中分為兩種類型的子彈,普通單道子彈和道具雙道子彈个唧,現(xiàn)在先來(lái)制作普通單道子彈江解。
有點(diǎn)需要注意,ccc中不斷重復(fù)創(chuàng)建的組件做成Prefab這個(gè)是很有必要的徙歼,雖然不用Prefab也是可以達(dá)到目的犁河。
首先把圖片資源從資源管理器中拖到層級(jí)管理器中,在屬性檢查器中調(diào)整好大小等參數(shù)后魄梯,將其拖拽到資源管理器相對(duì)應(yīng)的目錄下即可桨螺,然后刪除層級(jí)管理器中的原資源即可。(圖中屬性是隨意拖拽顯示的酿秸,具體請(qǐng)自己嘗試灭翔。)
接下來(lái)開(kāi)始編寫(xiě)腳本,首先需要知道的參數(shù)應(yīng)該有位置辣苏、速度這兩個(gè)參數(shù)目前就夠了肝箱,位置需要通過(guò)獲取Hero的位置原點(diǎn),速度可以由自己給出稀蟋,所以腳本如下:
// 提供思路參考用的代碼
cc.Class({
extends: cc.Component,
properties: {
speed: cc.Integer,
bullet: cc.Prefab
},
// use this for initialization
onLoad: function () {
// cc.instantiate() 克隆指定的任意類型的對(duì)象煌张,或者從 Prefab 實(shí)例化出新節(jié)點(diǎn)。
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ā)出的起始點(diǎn),但是實(shí)際運(yùn)行中萌狂,出現(xiàn)的問(wèn)題是绎谦,Hero是需要移動(dòng)的,子彈Prefab也就作為了Hero的子元素粥脚,會(huì)隨著Hero移動(dòng)而移動(dòng)窃肠,如下GIF所以:
所以子彈應(yīng)該單獨(dú)作為一層去處理,或者將bullet腳本綁在“background”層上刷允,然后將Hero的位置通過(guò)傳參的形式傳過(guò)來(lái)即可冤留。參考代碼是單獨(dú)用子彈層處理所有子彈的事件,所以也參考這種做法树灶,用單獨(dú)的層級(jí)去處理這個(gè)層級(jí)的所有相關(guān)事物纤怒。
在層級(jí)管理器中創(chuàng)建空節(jié)點(diǎn)并命名為bulletGroup,這個(gè)節(jié)點(diǎn)需要做的事就是處理Hero與Bullet的發(fā)射位置與發(fā)射頻率天通。所以首先需要的就是Hero與Bullet-Prefab泊窘,發(fā)射頻率的話,需要用到ccc中的定時(shí)器來(lái)實(shí)現(xiàn)固定間隔創(chuàng)建bullet節(jié)點(diǎn)并發(fā)射炮彈的功能,所以需要一個(gè)cc.Integer
類型的變量烘豹。
有一個(gè)非常值得注意的性能問(wèn)題:
在運(yùn)行時(shí)進(jìn)行節(jié)點(diǎn)的創(chuàng)建(cc.instantiate)和銷毀(node.destroy)操作是非常耗費(fèi)性能的瓜贾,因此在比較復(fù)雜的場(chǎng)景中,通常只有在場(chǎng)景初始化邏輯(onLoad)中才會(huì)進(jìn)行節(jié)點(diǎn)的創(chuàng)建携悯,在切換場(chǎng)景時(shí)才會(huì)進(jìn)行節(jié)點(diǎn)的銷毀祭芦。
所以,要實(shí)現(xiàn)不間斷發(fā)射子彈憔鬼,除了定時(shí)器龟劲,還需要引入對(duì)象池(cc.NodePool)。
無(wú)限子彈(進(jìn)階版)
下面開(kāi)始構(gòu)建腳本:
首先修改腳本bullet.js
轴或,bullet作為Prefab昌跌,只需要完成自己作為子彈的使命,那就是發(fā)射照雁,銷毀蚕愤,碰撞檢測(cè)。所以先來(lái)做一個(gè)只有發(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亥宿。
接著開(kāi)始編寫(xiě)bulletGroup
的腳本:
cc.Class({
extends: cc.Component,
properties: {
bullet: cc.Prefab,
hero: cc.Node,
rate: cc.Integer
},
onLoad: function () {
// 創(chuàng)建子彈對(duì)象池
this.genBulletPool();
// 設(shè)置定時(shí)器卸勺,每個(gè)0.2s創(chuàng)建一個(gè)新的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é)點(diǎn)
this.bulletPool.put(newBullet); // 通過(guò) putInPool 接口放入對(duì)象池
}
},
//獲取子彈位置
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
腳本中,直接給對(duì)象池中放了一百發(fā)子彈烫扼,打完了卻沒(méi)有回收曙求,這是不合理的,接下來(lái)要處理的就是回收資源映企。要注意的是對(duì)象池中的數(shù)量與發(fā)射子彈的關(guān)系悟狱,如果對(duì)象池中的對(duì)象用完了,而這時(shí)卻沒(méi)有及時(shí)補(bǔ)充堰氓,就會(huì)“延遲發(fā)貨”挤渐。可以試著調(diào)整bulletCount
來(lái)驗(yàn)證效果双絮。如果對(duì)象池中的對(duì)象太多浴麻,每次最多只能用10個(gè),之后就會(huì)被補(bǔ)充進(jìn)來(lái)囤攀,那么剩下的就會(huì)浪費(fèi)了软免。
如下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);
// 將對(duì)象池添加到window對(duì)象中,方便瀏覽器查看對(duì)象池狀態(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é)點(diǎn)
this.bulletPool.put(newBullet); // 通過(guò) putInPool 接口放入對(duì)象池
}
},
//獲取子彈位置
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)子彈飛出屏幕之后峰髓,將其重新放回對(duì)象池中尺栖,這樣一直都是對(duì)象池中的對(duì)象在被使用楣导,而沒(méi)有不斷創(chuàng)建新的對(duì)象闰歪。目前先在bullet
腳本中去處理對(duì)象銷毀榛泛。
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);
}
},
});
目前無(wú)限子彈類型已經(jīng)差不多完成了蝌蹂,但是只是實(shí)現(xiàn)了功能,接下來(lái)做雙彈道的子彈挟鸠。