遊戲開發性能優化之對象池

爲何要使用對象池

對象池優化是遊戲開發中很是重要的優化方式,也是影響遊戲性能的重要因素之一。
在遊戲中有許多對象在不停的建立與移除,好比角色攻擊子彈、特效的建立與移除,NPC的被消滅與刷新等,在建立過程當中很是消耗性能,特別是數量多的狀況下。
對象池技術能很好解決以上問題,在對象移除消失的時候回收到對象池,須要新對象的時候直接從對象池中取出使用。
優勢是減小了實例化對象時的開銷,且能讓對象反覆使用,減小了新內存分配與垃圾回收器運行的機會。html

Cocos官方文檔說明的使用方式

https://docs.cocos.com/creator/manual/zh/scripting/pooling.html
image.pngnode

  1. 這樣的一個對象池,其實嚴格意義上來講更像是節點池,由於它已經處理了節點移除等操做。
  2. 沒法將普通的TS對象放入cc.NodePool 進行管理。那麼當咱們須要對普通的TS對象進行管理的時候仍是須要本身再寫一個對象池。
  3. 好處就是回收節點的時候不須要對節點作任何操做。
  4. 將節點添加到場景中時不須要考慮是否存在的問題,直接addChild就能夠了,由於存在於對象池中的節點一定是從場景中移除的節點。
  5. 在使用的過程當中頻繁移除和添加有性能問題。

針對以上問題,我分享一下本身使用對象池的經驗。微信

對象池的封裝

  1. 節點對象池
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();
    }

}
  1. 非節點對象池
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 = [];
    }
 
}

對象池管理器

不管是節點對象池仍是非節點對象池。我都習慣經過一個管理器封裝起來使用。
這樣的好處就是集中管理,修改時也很是方便。性能

  1. 節點對象池管理器
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 = {};
    }
}
  1. 非節點對象池管理器
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);
        }
    }
}

通用對象池

對象由外部建立。不用考慮是否爲預製體建立的節點對象。優化

  1. 對象池
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;

    }
}
  1. 對象池管理器
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對象池的優化

image.png
針對Cocos的這一性能問題,我利用裝飾模式,自定義了SelfPool類改變了獲取和回收時的操做。this

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;     
    }
}

在對象池初始化的時候作了這樣的處理
image.png
若是不想使用隱藏方式,能夠去掉這一層封裝,接口都是同樣的。code

對象池回收的偷懶方式

在回收對象時的一向操做是put(key,obj)
若是obj確定擁有name或者其餘某個能夠標識類別的屬性,能夠將key與name作一個映射。經過name直接得到key,從而找到對應的對象池,那麼在put的時候也就不須要傳入key了。
image.png
image.pnghtm

結語

以上就是我在遊戲開發中使用對象池的幾種的方式,分享出來,供你們參考使用。對象

歡迎掃碼關注公衆號《微笑遊戲》,瀏覽更多內容。

微信圖片_20190904220029.jpg
歡迎掃碼關注公衆號《微笑遊戲》,瀏覽更多內容。blog

相關文章
相關標籤/搜索