數(shù)據(jù)庫(kù)支持的網(wǎng)站怎么做北京疫情最新情況
版本: cocosCreator 3.4.0
語(yǔ)言: TypeScript
環(huán)境: Mac
NodePool
在項(xiàng)目中頻繁的使用instantiate
和node.destory
對(duì)性能有很大的耗費(fèi),比如飛機(jī)射擊中的子彈使用和銷(xiāo)毀。
因此官方提供了NodePool
,它被作為管理節(jié)點(diǎn)對(duì)象的緩存池使用。定義如下:
export class NodePool {// 緩沖池處理組件,用于節(jié)點(diǎn)的回收和復(fù)用邏輯,這個(gè)屬性可以是組件類(lèi)名或組件的構(gòu)造函數(shù)poolHandlerComp?: Constructor<IPoolHandlerComponent> | string;// 構(gòu)造函數(shù),可傳遞組件或名稱(chēng),用于處理節(jié)點(diǎn)的復(fù)用和回收constructor(poolHandlerComp?: Constructor<IPoolHandlerComponent> | string);// 獲取當(dāng)前緩沖池的可用對(duì)象數(shù)量size(): number;// 銷(xiāo)毀對(duì)象池中緩存的所有節(jié)點(diǎn)clear(): void;/*@func: 向緩沖池中存入一個(gè)不再需要的節(jié)點(diǎn)對(duì)象@param: 回收的目標(biāo)節(jié)點(diǎn)注意:1. 該函數(shù)會(huì)自動(dòng)將目標(biāo)節(jié)點(diǎn)從父節(jié)點(diǎn)上移除,但是不會(huì)進(jìn)行 cleanup 操作2. 如果存在poolHandlerComp組件和函數(shù),會(huì)自動(dòng)調(diào)用 unuse函數(shù)*/put(obj: Node): void;/*@func: 獲取對(duì)象池中的對(duì)象,如果對(duì)象池沒(méi)有可用對(duì)象,則返回空@param: 如果組件和函數(shù)存在,會(huì)向 poolHandlerComp 中的 'reuse' 函數(shù)傳遞的參數(shù)*/get(...args: any[]): Node | null;
}
接口匯總:
接口 | 說(shuō)明 |
---|---|
new() | 創(chuàng)建對(duì)象池 |
put() | 將節(jié)點(diǎn)放到對(duì)象池中 |
get() | 從對(duì)象池中獲取節(jié)點(diǎn) |
size() | 獲取對(duì)象池中對(duì)象的數(shù)目 |
clear() | 銷(xiāo)毀對(duì)象池中的所有對(duì)象 |
使用NodePool
的大概思路:
- 通過(guò)
NodePool
創(chuàng)建對(duì)象池 - 獲取節(jié)點(diǎn)時(shí),可先檢測(cè)對(duì)象池的數(shù)目;如果 =0 則克隆節(jié)點(diǎn)并放到對(duì)象池中,如果 >0 則從對(duì)象池中獲取
- 節(jié)點(diǎn)不使用的時(shí)候,如果沒(méi)有對(duì)象池,則調(diào)用
node.destory
,否則將節(jié)點(diǎn)放到對(duì)象池中
在對(duì)象池中,有個(gè)get(...args: any[])
的方法,方法參數(shù)的使用主要針對(duì)于:對(duì)象池創(chuàng)建時(shí)添加了可選參數(shù)。
以飛機(jī)射擊中子彈的構(gòu)建為目的,看下關(guān)于對(duì)象池的使用示例相關(guān):
// GameManager.ts 游戲管理類(lèi)
import { BulletItem } from '../bullet/BulletItem';@ccclass('GameManager')
export class GameManager extends Component {@property(Prefab) bullet: Prefab = null; // 子彈預(yù)制體private _bulletPool: NodePool = null; // 子彈對(duì)象池onLoad() {// 創(chuàng)建子彈對(duì)象池this._bulletPool = new NodePool();}// 創(chuàng)建玩家子彈private createPlayerBullet() {// 獲取子彈節(jié)點(diǎn)const bulletNode = this.getBulletNode();const bulletItem = bulletNode.getComponent(BulletItem);// 此處將子彈對(duì)象池傳入子彈對(duì)象腳本bulletItem.init(this._bulletPool);}// 獲取子彈節(jié)點(diǎn)private getBulletNode(): Node {const size = this._bulletPool.size();if (size <= 0) {// 克隆子彈節(jié)點(diǎn)const bulletNode = instantiate(this.bullet);// 將子彈節(jié)點(diǎn)添加到對(duì)象池中this._bulletPool.put(bulletNode);}// 從對(duì)象池中獲取節(jié)點(diǎn)return this._bulletPool.get();}onDestroy() {// 銷(xiāo)毀對(duì)象池this._bulletPool.clear();}
}// BulletItem.ts 子彈對(duì)象組件腳本
export class BulletItem extends Component {private _bulletPool: NodePool = null;public init(bulletPool: NodePool) {this._bulletPool = bulletPool;}private destroyBullet() {// 檢測(cè)是否存在對(duì)象池,如果存在,則將對(duì)象放到對(duì)象池中,否則銷(xiāo)毀if (this._bulletPool) {this._bulletPool.put(this.node);}else {this.node.destory();}}
}
如上例子,簡(jiǎn)單演示了下NodePool
對(duì)象池的使用,但需要注意:
- 最好存儲(chǔ)同類(lèi)型的節(jié)點(diǎn),方便管理
- 注意檢測(cè)對(duì)象池內(nèi)的對(duì)象數(shù)目或通過(guò)
get
獲取對(duì)象后,進(jìn)行安全判定,避免null
- 注意對(duì)象池對(duì)象的釋放
構(gòu)造函數(shù)的可選參數(shù)
在上面的定義文件中,針對(duì)于對(duì)象池的構(gòu)建,有著可選參數(shù)的支持,代碼如下:
// 緩沖池處理組件,用于節(jié)點(diǎn)的回收和復(fù)用邏輯,這個(gè)屬性可以是組件類(lèi)名或組件的構(gòu)造函數(shù)
poolHandlerComp?: Constructor<IPoolHandlerComponent> | string;
// 構(gòu)造函數(shù),可傳遞組件或名稱(chēng),用于處理節(jié)點(diǎn)的復(fù)用和回收事件邏輯
constructor(poolHandlerComp?: Constructor<IPoolHandlerComponent> | string);
可選參數(shù)的支持主要有兩種形式:
string
字符串形式IPoolHandlerComponent
緩存池處理組件形式
對(duì)于這兩種形式,其本質(zhì)就是增加了對(duì)對(duì)象池中對(duì)象的自定義邏輯處理,以組件為參數(shù),看下它的定義:
export interface IPoolHandlerComponent extends Component {// 在對(duì)象被放入對(duì)象池的時(shí)候進(jìn)行調(diào)用unuse(): void;// 從對(duì)象池中獲取對(duì)象的時(shí)候被調(diào)用reuse(args: any): void;
}
這兩個(gè)方法的調(diào)用,看下源碼的實(shí)現(xiàn):
// 來(lái)源于 node-pool.ts
export class NodePool {// 向?qū)ο缶彺娉卮嫒氩恍枰膶?duì)象public put (obj: Node) {if (obj && this._pool.indexOf(obj) === -1) {// 從父節(jié)點(diǎn)移除,但并不cleanupobj.removeFromParent();// 獲取組件poolHandlerComp,并檢測(cè)是否存在 unuse方法,如果存在則調(diào)用const handler = this.poolHandlerComp?obj.getComponent(this.poolHandlerComp):null;if (handler && handler.unuse) {handler.unuse();}this._pool.push(obj);}}// 獲取對(duì)象池中的對(duì)象public get (...args: any[]): Node | null {// 檢測(cè)對(duì)象池中是否有對(duì)象const last = this._pool.length - 1;if (last < 0) {return null;} else {// 將對(duì)象從緩存池中取出const obj = this._pool[last];this._pool.length = last;// 獲取組件poolHandlerComp,并檢測(cè)是否存在reuse方法,如果存在則調(diào)用const handler=this.poolHandlerComp?obj.getComponent(this.poolHandlerComp):null;if (handler && handler.reuse) {handler.reuse(arguments);}return obj;}}
}
上面的代碼有助于對(duì)兩個(gè)方法的調(diào)用時(shí)機(jī)增加一些了解。
下面我們依然以飛機(jī)的子彈構(gòu)建為例,代碼增加一些拓展,用于支持對(duì)象池的自定義邏輯處理。
// GameManager.ts 游戲管理類(lèi)
import { BulletItem } from '../bullet/BulletItem';@ccclass('GameManager')
export class GameManager extends Component { onLoad() {// 創(chuàng)建子彈對(duì)象池, 參數(shù)設(shè)定為子彈類(lèi)的名字this._bulletPool = new NodePool("BulletItem");}private getBulletNodePool() {const size = this._bulletPool.size();if (size <= 0) {const bulletNode = instantiate(this.bullet_1);this._bulletPool.put(bulletNode);}// 獲取子彈節(jié)點(diǎn)時(shí),可以設(shè)置自定義的參數(shù)相關(guān)return this._bulletPool.get();}
}// BulletItem.ts 子彈對(duì)象組件腳本,增加
export class BulletItem extends Component implements IPoolHandlerComponent {unuse(): void {console.log("------ 調(diào)用了組件的 unuse 方法");}reuse(args: any): void {console.log("------ 調(diào)用了組件的 reuse 方法");}
}
增加對(duì)對(duì)象的自定義邏輯處理,其要點(diǎn)就是:
- 構(gòu)建對(duì)象池時(shí),需要添加可選參數(shù),參數(shù)的名字或組件一定要是對(duì)象的腳本組件相關(guān)
- 對(duì)象的組件腳本類(lèi),需要增加
implements IPoolHandlerComponent
的實(shí)現(xiàn),也就是unuse
和reuse
方法 - 根據(jù)情況,自定義設(shè)定
NodePool.get
的參數(shù)相關(guān)
到這里,關(guān)于NodePool的基本使用介紹完畢。
NodePool管理器
在上面的例子中,關(guān)于對(duì)象池的使用存在著幾個(gè)問(wèn)題:
-
從對(duì)象池獲取對(duì)象和將對(duì)象放入對(duì)象池的調(diào)用在不同的腳本文件中,可能會(huì)出現(xiàn)維護(hù)比較困難的問(wèn)題
-
對(duì)象池的構(gòu)建不僅針對(duì)于子彈,而且可能還有敵機(jī),道具等,可能會(huì)出現(xiàn)多個(gè)對(duì)象池且代碼重復(fù)的問(wèn)題。
因此,我們可構(gòu)建一個(gè)對(duì)象池的管理類(lèi),來(lái)統(tǒng)一管理多個(gè)不同的對(duì)象池,類(lèi)似于cocos2d-x中的PoolManager。
大致的屬性和接口是:
屬性或方法 | 說(shuō)明 |
---|---|
_nodePoolMap | 保存所有對(duì)象池的容器,結(jié)構(gòu)是Map<對(duì)象池名, NodePool池> |
getNodePoolByName():NodePool | 通過(guò)名字從容器中獲取對(duì)象池,如果沒(méi)有則創(chuàng)建,如果存在則獲取 |
getNodeFromPool():Node | 通過(guò)名字從對(duì)象池中獲取節(jié)點(diǎn),如果沒(méi)有則克隆,如果存在則獲取 |
putNodeToPool() | 將節(jié)點(diǎn)放入對(duì)象池中 |
clearNodePoolByName() | 通過(guò)名字將對(duì)象池從容器中移除 |
clearAll() | 移除容器中的所有對(duì)象池 |
該類(lèi)使用的是單例模式,詳細(xì)的代碼如下:
// 對(duì)象池管理器
import { _decorator, Component, instantiate, NodePool, Prefab} from 'cc';
const { ccclass } = _decorator;export class NodePoolManager {private static _instance: NodePoolManager = null;private _nodePoolMap: Map<string, NodePool> = null;static get instance() {if (this._instance) {return this._instance;}this._instance = new NodePoolManager();return this._instance;}constructor() {this._nodePoolMap = new Map<string, NodePool>();}/*@func 通過(guò)對(duì)象池名字從容器中獲取對(duì)象池@param name 對(duì)象池名字@return 對(duì)象池*/private getNodePoolByName(name: string): NodePool {if (!this._nodePoolMap.has(name)) {let nodePool = new NodePool(name);this._nodePoolMap.set(name, nodePool);}let nodePool = this._nodePoolMap.get(name);return nodePool;}/*@func 通過(guò)對(duì)象池名字從對(duì)象池中獲取節(jié)點(diǎn)@param name 對(duì)象池名字@param prefab 可選參數(shù),對(duì)象預(yù)制體@return 對(duì)象池中節(jié)點(diǎn) */public getNodeFromPool(name: string, prefab?: Prefab): Node | null {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {let node = instantiate(prefab);nodePool.put(node);}return nodePool.get();}/*@func 將節(jié)點(diǎn)放入對(duì)象池中@param name 對(duì)象池名字@param node 節(jié)點(diǎn)*/public putNodeToPool(name: string, node: Node) {let nodePool = this.getNodePoolByName(name);nodePool.put(node);}// 通過(guò)名字將對(duì)象池從容器中移除public clearNodePoolByName(name: string) {// 銷(xiāo)毀對(duì)象池中對(duì)象let nodePool = this.getNodePoolByName(name);nodePool.clear();// 刪除容器元素this._nodePoolMap.delete(name);}// 移除所有對(duì)象池public clearAll() {this._nodePoolMap.forEach((value: NodePool, key: string) => {value.clear();});this._nodePoolMap.clear();}static destoryInstance() {this._instance = null;}
}
測(cè)試示例:
// GameManager.ts
const BULLET_POOL_NAME = "BulletItem" // 子彈內(nèi)存池// 創(chuàng)建玩家子彈
private createPlayerBullet() {// 獲取子彈節(jié)點(diǎn),參數(shù):節(jié)點(diǎn)名,子彈預(yù)制體const poolManager = NodePoolManager.instance;const bulletNode = poolManager.getNodeFromPool(BULLET_POOL_NAME, this.bulletPrefab);bulletNode.parent = this.bulletRoot;
}// BulletItem.ts
private destroyBullet() {// 檢測(cè)是否存在對(duì)象池,如果存在,則將對(duì)象放到對(duì)象池中,否則銷(xiāo)毀if (this._bulletPool) {//this._bulletPool.put(this.node);const poolManager = NodePoolManager.instance;poolManager.putNodeToPool(BULLET_POOL_NAME, this.node);}else {this.node.destory();}
}
管理類(lèi)中有個(gè)接口叫做getNodeFromPool(name: string, prefab?: Prefab)
,第二個(gè)參數(shù)也可以為prefabName,然后通過(guò)resource.load
進(jìn)行動(dòng)態(tài)加載,類(lèi)似實(shí)現(xiàn):
public getNodeFromPool(name: string, prefabName?: string): Node | null {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {const url = "prefab/" + prefabName;resources.load(url, (err, prefab) => {if (err) {return console.err("getNodeFromPool resourceload failed:" + err.message);}let node = instantiate(prefab);nodePool.put(node);});}return nodePool.get();
}
因resouces.load
屬于異步操作,可能會(huì)出現(xiàn)代碼未加載完成就獲取的問(wèn)題,因此可使用異步編程:
public getNodeFromPool(name: string, prefabName?: string): Promise<Node | null> {return new Promise<Node | null>((resolve, reject) => {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {const url = "prefab/" + prefabName;resources.load(url, (err, prefab) => {if (err) {console.error("getNodeFromPool resourceload failed:" + err.message);reject(err);} else {let node = instantiate(prefab);nodePool.put(node);resolve(nodePool.get());}});} else {resolve(nodePool.get());}});
}
關(guān)于一些TypeScript的語(yǔ)法相關(guān),可參考博客:
TypeScript 之 Map
TypeScript 之 異步編程
因工作的某些緣故,可能對(duì)NodePool
的理解及編寫(xiě)示例有所不當(dāng),請(qǐng)不吝賜教,感激不盡!
最后祝大家學(xué)習(xí)生活愉快!