前言:
在運(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ì)象池:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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ì)象了:
1
2
3
4
5
6
7
8
9
10
11
12
|
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)利用,我們用這樣的方法:
1
2
3
4
|
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)存管理開(kā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í)可以用:
1
|
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è)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 方法:
1
2
3
4
5
6
7
8
9
10
11
|
// 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):
1
|
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)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://blog.csdn.net/qq_45021180/article/details/104484744