如何使用CocosCreator對(duì)象池
前言:
在運(yùn)行時(shí)進(jìn)行節(jié)點(diǎn)的創(chuàng)建( cc.instantiate )和銷毀( node.destroy )操作是非常耗費(fèi)性能的,因此我們?cè)诒容^復(fù)雜的場(chǎng)景中,通常只有在場(chǎng)景初始化邏輯( onLoad )中才會(huì)進(jìn)行節(jié)點(diǎn)的創(chuàng)建,在切換場(chǎng)景時(shí)才會(huì)進(jìn)行節(jié)點(diǎn)的銷毀。如果制作有大量敵人或子彈需要反復(fù)生成和被消滅的動(dòng)作類游戲,我們要如何在游戲進(jìn)行過(guò)程中隨時(shí)創(chuàng)建和銷毀節(jié)點(diǎn)呢?這里就需要對(duì)象池的幫助了。
對(duì)象池就是一組可回收的節(jié)點(diǎn)對(duì)象,我們通過(guò)創(chuàng)建cc.NodePool的實(shí)例來(lái)初始化一種節(jié)點(diǎn)的對(duì)象池。通常當(dāng)我們有多個(gè) prefab 需要實(shí)例化時(shí),應(yīng)該為每個(gè) prefab 創(chuàng)建一個(gè) cc.NodePool 實(shí)例。
當(dāng)我們需要?jiǎng)?chuàng)建節(jié)點(diǎn)時(shí),向?qū)ο蟪厣暾?qǐng)一個(gè)節(jié)點(diǎn),如果對(duì)象池里有空閑的可用節(jié)點(diǎn),就會(huì)把節(jié)點(diǎn)返回給用戶,用戶通過(guò)node.addChild將這個(gè)新節(jié)點(diǎn)加入到場(chǎng)景節(jié)點(diǎn)樹中。
當(dāng)我們需要銷毀節(jié)點(diǎn)時(shí),調(diào)用對(duì)象池實(shí)例的put(node) 方法,傳入需要銷毀的節(jié)點(diǎn)實(shí)例,對(duì)象池會(huì)自動(dòng)完成把節(jié)點(diǎn)從場(chǎng)景節(jié)點(diǎn)樹中移除的操作,然后返回給對(duì)象池。
這樣就實(shí)現(xiàn)了少數(shù)節(jié)點(diǎn)的循環(huán)利用。
具體操作
第一步:準(zhǔn)備好 Prefab
把你想要?jiǎng)?chuàng)建的節(jié)點(diǎn)事先設(shè)置好并做成 Prefab 資源,有些朋友可能不會(huì)制作預(yù)制體?
(只需要將資源管理器里的資源 拉拽到 層級(jí)管理器中 然后 將節(jié)點(diǎn)在拉拽回 資源管理器)
這樣就完成預(yù)制體的創(chuàng)建了!
第二步:初始化對(duì)象池
在場(chǎng)景加載的初始化腳本中,我們可以將需要數(shù)量的節(jié)點(diǎn)創(chuàng)建出來(lái),并放進(jìn)對(duì)象池:
properties: {
enemyPrefab: cc.Prefab // 所需要的預(yù)制體
},
onLoad: function () {
// 創(chuàng)建對(duì)象池
this.enemyPool = new cc.NodePool();
let initCount = 5;
for (let i = 0; i < initCount; ++i) {
let enemy = cc.instantiate(this.enemyPrefab); // 創(chuàng)建節(jié)點(diǎn)
this.enemyPool.put(enemy); // 通過(guò) put 接口放入對(duì)象池
}
}
對(duì)象池里需要的初始節(jié)點(diǎn)數(shù)量可以根據(jù)游戲的需要來(lái)控制,即使我們對(duì)初始節(jié)點(diǎn)數(shù)量的預(yù)估不準(zhǔn)確也不要緊,后面我們會(huì)進(jìn)行處理。
第三步:從對(duì)象池請(qǐng)求對(duì)象
接下來(lái)在我們的運(yùn)行時(shí)代碼中就可以用下面的方式來(lái)獲得對(duì)象池中儲(chǔ)存的對(duì)象了:
createEnemy: function (parentNode) {
let enemy = null;
if (this.enemyPool.size() > 0) { // 通過(guò) size 接口判斷對(duì)象池中是否有空閑的對(duì)象
// get()獲取對(duì)象
enemy = this.enemyPool.get();
} else { // 如果沒(méi)有空閑對(duì)象,也就是對(duì)象池中備用對(duì)象不夠時(shí),我們就用 cc.instantiate 重新創(chuàng)建
enemy = cc.instantiate(this.enemyPrefab);
}
enemy.parent = parentNode; // 將生成的敵人加入節(jié)點(diǎn)樹
enemy.getComponent('Enemy').init(); //接下來(lái)就可以調(diào)用 enemy 身上的腳本進(jìn)行初始化
}
安全使用對(duì)象池的要點(diǎn)就是在 get 獲取對(duì)象之前,永遠(yuǎn)都要先用 size 來(lái)判斷是否有可用的對(duì)象,如果沒(méi)有就使用正常創(chuàng)建節(jié)點(diǎn)的方法,雖然會(huì)消耗一些運(yùn)行時(shí)性能,但總比游戲崩潰要好!另一個(gè)選擇是直接調(diào)用 get ,如果對(duì)象池里沒(méi)有可用的節(jié)點(diǎn),會(huì)返回 null ,在這一步進(jìn)行判斷也可以。
第四步:將對(duì)象返回對(duì)象池
當(dāng)我們殺死敵人時(shí),需要將敵人節(jié)點(diǎn)退還給對(duì)象池,以備之后繼續(xù)循環(huán)利用,我們用這樣的方法:
onEnemyKilled: function (enemy) {
// enemy 應(yīng)該是一個(gè) cc.Node
this.enemyPool.put(enemy); // 和初始化時(shí)的方法一樣,將節(jié)點(diǎn)放進(jìn)對(duì)象池,這個(gè)方法會(huì)同時(shí)調(diào)用節(jié)點(diǎn)的 removeFromParent
}
這樣我們就完成了一個(gè)完整的循環(huán),主角需要刷多少怪都不成問(wèn)題了!將節(jié)點(diǎn)放入和從對(duì)象池取出的操作不會(huì)帶來(lái)額外的內(nèi)存管理開銷,因此只要是可能,應(yīng)該盡量去利用。
第五步:使用組件來(lái)處理回收和復(fù)用的事件
使用構(gòu)造函數(shù)創(chuàng)建對(duì)象池時(shí),可以指定一個(gè)組件類型或名稱,作為掛載在節(jié)點(diǎn)上用于處理節(jié)點(diǎn)回收和復(fù)用事件的組件。
在創(chuàng)建對(duì)象池時(shí)可以用:
let menuItemPool = new cc.NodePool('MenuItem'); // 指定一個(gè)組件類型
這樣當(dāng)使用 menuItemPool.get() 獲取節(jié)點(diǎn)后,就會(huì)調(diào)用 MenuItem 里的 reuse方法,完成點(diǎn)擊事件的注冊(cè)。
當(dāng)使用menuItemPool.put(menuItemNode)回收節(jié)點(diǎn)后,會(huì)調(diào)用 MenuItem 里的 unuse方法,完成點(diǎn)擊事件的反注冊(cè)。
cc.Class({
extends: cc.Component,
onLoad: function () {
this.node.selected = false;
this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
},
// put() 收回對(duì)象池時(shí)會(huì)調(diào)用
unuse: function () {
this.node.off(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
},
// get()獲取對(duì)象池內(nèi)對(duì)象時(shí)會(huì)調(diào)用
reuse: function () {
this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
}
});
另外 cc.NodePool.get() 可以傳入任意數(shù)量類型的參數(shù),這些參數(shù)會(huì)被原樣傳遞給 reuse 方法:
// BulletManager.js
let myBulletPool = new cc.NodePool('Bullet'); //創(chuàng)建子彈對(duì)象池
let newBullet = myBulletPool.get(this); // 傳入 manager 的實(shí)例,用于之后在子彈腳本中回收子彈
// Bullet.js
reuse (bulletManager) {
this.bulletManager = bulletManager; // get 中傳入的管理類實(shí)例
}
hit () {
this.bulletManager.put(this.node); // 通過(guò)之前傳入的管理類實(shí)例回收子彈
}
第六步:清除對(duì)象池
如果對(duì)象池中的節(jié)點(diǎn)不再被需要,我們可以手動(dòng)清空對(duì)象池,銷毀其中緩存的所有節(jié)點(diǎn):
myPool.clear(); // 調(diào)用這個(gè)方法就可以清空對(duì)象池
當(dāng)對(duì)象池實(shí)例不再被任何地方引用時(shí),引擎的垃圾回收系統(tǒng)會(huì)自動(dòng)對(duì)對(duì)象池中的節(jié)點(diǎn)進(jìn)行銷毀和回收。但這個(gè)過(guò)程的時(shí)間點(diǎn)不可控,另外如果其中的節(jié)點(diǎn)有被其他地方所引用,也可能會(huì)導(dǎo)致內(nèi)存泄露,所以最好在切換場(chǎng)景或其他不再需要對(duì)象池的時(shí)候手動(dòng)調(diào)用 clear 方法來(lái)清空緩存節(jié)點(diǎn)。
以上就是如何使用CocosCreator對(duì)象池的詳細(xì)內(nèi)容,更多關(guān)于CocosCreator對(duì)象池的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 詳解cocoscreater預(yù)制體prefab
- 如何在CocosCreator中利用常駐節(jié)點(diǎn)做圖層管理
- 游戲開發(fā)中如何使用CocosCreator進(jìn)行音效處理
- CocosCreator ScrollView優(yōu)化系列之分幀加載
- 詳解CocosCreator項(xiàng)目結(jié)構(gòu)機(jī)制
- CocosCreator如何實(shí)現(xiàn)劃過(guò)的位置顯示紋理
- 整理CocosCreator常用知識(shí)點(diǎn)
- 全面講解CocosCreator熱更新
- CocosCreator經(jīng)典入門項(xiàng)目之flappybird
- CocosCreator通用框架設(shè)計(jì)之網(wǎng)絡(luò)
- 如何用CocosCreator實(shí)現(xiàn)射擊小游戲
- 怎樣在CocosCreator中使用游戲手柄
相關(guān)文章
JavaScript實(shí)現(xiàn)手寫原生任務(wù)定時(shí)器
定時(shí)器顧名思義就是在某個(gè)特定的時(shí)間去執(zhí)行一些任務(wù),現(xiàn)代的應(yīng)用程序早已不是以前的那些由簡(jiǎn)單的增刪改查拼湊而成的程序了,高復(fù)雜性早已是標(biāo)配,而任務(wù)的定時(shí)調(diào)度與執(zhí)行也是對(duì)程序的基本要求了。本文將利用JavaScript手寫原生任務(wù)定時(shí)器,需要的可以參考一下2022-03-03
javascript異常處理實(shí)現(xiàn)原理詳解
這篇文章主要介紹了javascript異常處理實(shí)現(xiàn)原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02-
最新評(píng)論

