为什么要应用对象池
对象池优化是游戏开发中十分重要的优化形式,也是影响游戏性能的重要因素之一。
在游戏中有许多对象在不停的创立与移除,比方角色攻打子弹、特效的创立与移除,NPC 的被毁灭与刷新等,在创立过程中十分耗费性能,特地是数量多的状况下。
对象池技术能很好解决以上问题,在对象移除隐没的时候回收到对象池,须要新对象的时候间接从对象池中取出应用。
长处是缩小了实例化对象时的开销,且能让对象重复应用,缩小了新内存调配与垃圾回收器运行的机会。
Cocos 官网文档阐明的应用形式
https://docs.cocos.com/creator/manual/zh/scripting/pooling.html
- 这样的一个对象池,其实严格意义上来说更像是节点池,因为它曾经解决了节点移除等操作。
- 无奈将一般的 TS 对象放入 cc.NodePool 进行治理。那么当咱们须要对一般的 TS 对象进行治理的时候还是须要本人再写一个对象池。
- 益处就是回收节点的时候不须要对节点做任何操作。
- 将节点增加到场景中时不须要思考是否存在的问题,间接 addChild 就能够了,因为存在于对象池中的节点必然是从场景中移除的节点。
- 在应用的过程中频繁移除和增加有性能问题。
针对以上问题,我分享一下本人应用对象池的教训。
对象池的封装
- 节点对象池
import {IPool} from "./IPool";
export default class CCNodePool implements IPool{
private pool: cc.NodePool;
private resItem: cc.Prefab;
private name: string = ''
/**
*
* @param prefab 预制体
* @param conut 初始化个数
*/
constructor(name: string, resItem: cc.Prefab, conut: number) {
this.name = name
this.pool = new cc.NodePool();
this.resItem = resItem;
for (let i = 0; i < conut; i++) {let obj: cc.Node = this.getNode(); // 创立节点
this.pool.put(obj); // 通过 putInPool 接口放入对象池
}
}
getName() {return this.name}
get() {let go: cc.Node = this.pool.size() > 0 ? this.pool.get() : this.getNode();
return go;
}
getNode() {if(this.resItem){return cc.instantiate(this.resItem);
}else{console.error('预制体没有赋值')
return null;
}
}
size() {return this.pool.size();
}
put(go: cc.Node) {this.pool.put(go);
}
clear() {this.pool.clear();
}
}
- 非节点对象池
export default class TSObjectPool<T> {private pool:any [] = []
private className:string;
constructor(className:string,type: { new(): T ;},count:number = 0){
this.className = className;
for (let index = 0; index < count; index++) {this.pool.push(new type());
}
}
getClassName(){return this.className;}
get<T>(type: { new(): T ;} ): T {let go = this.pool.length > 0 ? this.pool.shift() : null;
if(!go){go = new type();
}
return go;
}
put(instance:T){this.pool.push(instance);
}
clear(){this.pool = [];
}
}
对象池管理器
不论是节点对象池还是非节点对象池。我都习惯通过一个管理器封装起来应用。
这样的益处就是集中管理,批改时也十分不便。
- 节点对象池管理器
import CCNodePool from "./CCNodePool";
import SelfPool from "./SelfPool";
export default class CCPoolManager {
private static ins: CCPoolManager;
static instance(): CCPoolManager {if (!this.ins) {this.ins = new CCPoolManager();
}
return this.ins;
}
// 对象池表
private pools = {};
// 对象名称 和给定 key 的 映射表 这样在回收对象的时候就不须要传入 key 了。通过节点的 name 就能够找到 key。private nameMap = {};
init(key: string, resItem: cc.Prefab, count: number) {if (!this.pools[key]) {this.pools[key] = new SelfPool(new CCNodePool(key, resItem, count))
}
}
getPool(key: string) {return this.pools[key].getPool();}
get(key: string): cc.Node {if (this.pools[key]) {let go: cc.Node = this.pools[key].get();
if (!this.nameMap[go.name] && go.name != key) {this.nameMap[go.name] = key;
}
return go;
}
return null;
}
put(go: cc.Node, nodePool: boolean = false) {let key = this.nameMap[go.name];
if (!key) {key = go.name;}
if (!this.pools[key]) {cc.warn("not have name", key, ',go.name', go.name);
return;
}
this.pools[key].put(go, nodePool);
}
clear(name: string) {if (this.pools[name]) {this.pools[name].clear();
this.pools[name] = null;
}
}
clealAll() {for (const key in this.pools) {this.clear(key);
}
this.pools = {};}
}
- 非节点对象池管理器
import TSObjectPool from "./TSObjectPool";
export default class TSPoolManager {
// 对象池表
private pools = {}
private static ins: TSPoolManager;
static instance(): TSPoolManager {if (!this.ins) {this.ins = new TSPoolManager();
}
return this.ins;
}
init<T>(key: string, type: { new(): T; }, count: number = 1): void {if (!this.pools[key]) {this.pools[key] = new TSObjectPool(key, type, count);
}
}
/**
* 取得被销毁的对象
* @param key
*/
get<T>(key: string, type: { new(): T; }, count: number = 1): T {if (!this.pools[key]) {this.pools[key] = new TSObjectPool(key, type, count);
}
return this.pools[key].get(type);
}
put(key: string, obj) {let pool = this.pools[key]
if (pool) {pool.put(obj);
}
}
}
通用对象池
对象由内部创立。不必思考是否为预制体创立的节点对象。
- 对象池
export default class ObjectPool<T>{private buffList: T[] = []
private key: string;
constructor(key: string) {this.key = key;}
get(func: () => T) {let item = this.buffList.length > 0 ? this.buffList.shift() : func();
return item;
}
put(obj: T) {this.buffList.push(obj)
}
size() {return this.buffList.length}
destroy() {this.buffList.length = 0;}
}
- 对象池管理器
import ObjectPool from "./ObjectPool";
import TSMap from "../struct/TSMap";
export default class PoolManager {
private static ins: PoolManager
static instance() {if (!this.ins) {this.ins = new PoolManager();
}
return this.ins;
}
private map: TSMap<string, ObjectPool<any>> = new TSMap();
get(key: any, func: () => any) {if (!this.map.has(key)) {this.map.set(key, new ObjectPool(key))
}
return this.map.get(key).get(func)
}
put(key: any, obj: any) {if (this.map.has(key)) {this.map.get(key).put(obj)
} else {}}
size(key: string) {if (this.map.has(key)) {return this.map.get(key).size()}
return 0;
}
destroy() {this.map.clear();
}
}
针对 Cocos 对象池的优化
针对 Cocos 的这一性能问题,我利用装璜模式,自定义了 SelfPool 类扭转了获取和回收时的操作。
import CCNodePool from "./CCNodePool";
import {IPool} from "./IPool";
/**
* 应用 opacity 形式暗藏对象
*/
export default class SelfPool implements IPool{private list:cc.Node[] = []
private pool:CCNodePool;
constructor(pool:CCNodePool){this.pool = pool;}
get(){let go:cc.Node = this.list.length > 0 ? this.list.shift() : this.pool.get();
go.opacity = 255;
return go;
}
getPool(){return this.pool}
size(){return this.pool.size() + this.list.length;
}
/**
*
* @param go
* @param nodePool 是否放入 NodePool 中
*/
put(go:cc.Node,nodePool:boolean = false){if(nodePool){this.pool.put(go)
}else{this.list.push(go);
go.stopAllActions();
go.opacity = 0;
}
}
clear(){this.pool.clear();
this.list.length = 0;
}
}
在对象池初始化的时候做了这样的解决
如果不想应用暗藏形式,能够去掉这一层封装,接口都是一样的。
对象池回收的偷懒形式
在回收对象时的一贯操作是 put(key,obj)
如果 obj 必定领有 name 或者其余某个能够标识类别的属性,能够将 key 与 name 做一个映射。通过 name 间接取得 key,从而找到对应的对象池,那么在 put 的时候也就不须要传入 key 了。
结语
以上就是我在游戏开发中应用对象池的几种的形式,分享进去,供大家参考应用。
欢送扫码关注公众号《微笑游戏》,浏览更多内容。
欢送扫码关注公众号《微笑游戏》,浏览更多内容。