Unity3D 基於預設(Prefab)的泛型對象池實現


背景

在研究Inventory Pro插件的時候,發現老外實現的一個泛型對象池,覺得設計的小巧實用,不敢私藏,特此共享出來。

以前也看過很多博友關於對象池的總結分享,但是世界這么大,這么復雜到底什么樣的對象池才是好的呢,我們發現通用的對象池未必適應所有的環境,比如基於UI的局部(從某個Scene,到某個Dialog)對象池,范圍不同,需要的對象池就有不同的要求。本文就是介紹一種基於預設(Prefab)的局部UI對象池。

通用信息提示窗口的實現http://www.manew.com/thread-94647-1-1.html

聲明:本文首發於蠻牛,次發博客園,本人原創。

原文鏈接1http://www.manew.com/thread-94650-1-1.html,原文鏈接2

演示

左下角信息提示窗口NoticeUI中的信息提示比較多時,具有滾動條和超出自動隱藏的功能,就是通過對象池技術實現,從而提高性能和效率

u1

下面代碼是在NoticeUI中 設置對象池的相關代碼

code1

分析

老規矩先上類圖,不過其實沒有什么好分析的,就是對象池的標准接口,取Get和回收這里是Destroy。還有出現兩個類一個是泛型的,一個是非泛型的,至於為什么是結構體而不是類,這塊我也沒有深究。

InventoryPool

下面我們仔細分析下這個UI對象池的類,首先看下類的

聲明和構造函數

class1

 

1、類注釋寫的很清楚,告訴你這個對象池支持的對象是GameObjects,限制了必須是Unity3D的類型,該類的功能僅為了提高速度。

它並不完善,建議使用小規模的數據。明白了這點很重要,也就是我在背景里說的每種場景使用的對象池是不一樣的,一定不能混用和濫用。

2、泛型結構體的聲明,這里我們T限定了類型是UnityEngine.Component也就是Unity3D的組件基類,所以為什么注釋里面說了只適合GameObjects了,剩下的就是new()關鍵字約束,確保類是可以new的。IPoolableObject是面向接口的一種實現約束(框架層考慮就是不一樣),實際是強制要求在代碼級別做好對象池類的管理。

3、構造函數是用了缺省值StatSize64很貼心,下面三句初始化代碼,創建了一個_PoolParent GameObject,並掛到了全局的組件樹上,我覺得的這點特別重要,這使得在Edit下可以看見池對象的動態創建和狀態,特別是讓組件樹特別清晰,點個贊,第三句,也就是保存了泛型對象

poolParent = new GameObject("_PoolParent").transform;
poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
this.baseObject = baseObject;

 

泛型池對象的初始化

池對象初始化創建

class2

泛型池對象的初始化其實是調用了GameObject.Instantiate<T>的泛型方法重載,也就是深克隆。這也就是解釋了為什么可以完成對於預設(Prefab)的對象池了,然后設置其父對象,掛接到組件樹上,最后調用gameObject.SetActivie(false)讓UI對象不可見,其實是一個開關。

池對象的獲取和銷毀

池對象的獲取

class3

有了創建時候的開關,獲取UI池對象就可以通過這個開關來判斷對象是否已經使用了,然后如果active是false的,通過設置成true打開就完成了池對象的創建,這里createWhenNoneLeft參數默認是true,用來解決當構造函數預設池對象都被使用后如何擴展的問題,這里其實很簡單了當所有預設都用完了直接再創建一個即可,多虧了c#的動態數組List<T>,不然還要像c++那樣動態調整數組的大小。

池對象的銷毀

class4

對象的銷毀其實是回收,這里對象最簡單的一步就是SetActive(false)也就是把UI開關給關上,讓對象不可見。其實重要的是調用IPoolObject接口的Reset函數實現,進行一些善后工作,讓然也要通過 transform.SetParent()進行組件樹的重置。所有對象的銷毀和回收就很簡單了只需要循環遍歷銷毀即可。

核心源碼

using System;
using System.Collections.Generic;
using Devdog.InventorySystem;
using Devdog.InventorySystem.Models;
using UnityEngine;

namespace Devdog.InventorySystem
{
    /// <summary>
    /// Only supports GameObjects and no unique types, just to speed some things up.
    /// It's not ideal and I advice you to only use it on small collections.
    /// </summary>
    public struct InventoryPool<T> where T : UnityEngine.Component, IPoolableObject, new()
    {
        private List<T> itemPool;
        private Transform poolParent;
        private T baseObject;

        public InventoryPool(T baseObject, int startSize = 64)
        {
            poolParent = new GameObject("_PoolParent").transform;
            poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
            this.baseObject = baseObject;

            itemPool = new List<T>(startSize);
            for (int i = 0; i < startSize; i++)
            {
                Instantiate();
            }
        }


        /// <summary>
        /// Create an object inside the pool
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        public T Instantiate()
        {
            var a = GameObject.Instantiate<T>(baseObject);
            a.transform.SetParent(poolParent);
            a.gameObject.SetActive(false); // Start disabled

            itemPool.Add(a);
            return a;
        }
    

        /// <summary>
        /// Get an object from the pool
        /// </summary>
        public T Get(bool createWhenNoneLeft = true)
        {
            foreach (var item in itemPool)
            {
                if(item.gameObject.activeSelf == false)
                {
                    item.gameObject.SetActive(true);
                    return item;
                }
            }

            if (createWhenNoneLeft)
            {
                Debug.Log("New object created, considering increasing the pool size if this is logged often");
                return Instantiate();
            }

            return null;
        }
    

        /// <summary>
        /// Mark an object as inactive so it can be recycled.
        /// </summary>
        /// <param name="item"></param>
        public void Destroy(T item)
        {
            item.Reset(); // Resets the item state
            item.transform.SetParent(poolParent);
            item.gameObject.SetActive(false); // Up for reuse
        }

        public void DestroyAll()
        {
            foreach (var item in itemPool)
                Destroy(item);
        }
    }

    /// <summary>
    /// InventoryPool only good for gameObjects
    /// </summary>
    public struct InventoryPool
    {
        private List<GameObject> itemPool;
        private Transform poolParent;
        private GameObject baseObject;

        public InventoryPool(GameObject baseObject, int startSize = 64)
        {
            poolParent = new GameObject("_PoolParent").transform;
            poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
            this.baseObject = baseObject;

            itemPool = new List<GameObject>(startSize);
            for (int i = 0; i < startSize; i++)
            {
                Instantiate();
            }
        }


        /// <summary>
        /// Create an object inside the pool
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        public GameObject Instantiate()
        {
            GameObject a = null;
            if (baseObject != null)
                a = GameObject.Instantiate<GameObject>(baseObject);
            else
                a = new GameObject();

            a.transform.SetParent(poolParent);
            a.gameObject.SetActive(false); // Start disabled

            itemPool.Add(a);
            return a;
        }


        /// <summary>
        /// Get an object from the pool
        /// </summary>
        public GameObject Get(bool createWhenNoneLeft = true)
        {
            foreach (var item in itemPool)
            {
                if (item.gameObject.activeInHierarchy == false)
                {
                    item.gameObject.SetActive(true);
                    return item;
                }
            }

            if (createWhenNoneLeft)
            {
                Debug.Log("New object created, considering increasing the pool size if this is logged often");
                return Instantiate();
            }

            return null;
        }


        /// <summary>
        /// Mark an object as inactive so it can be recycled.
        /// </summary>
        /// <param name="item"></param>
        public void Destroy(GameObject item)
        {
            item.transform.SetParent(poolParent);
            item.gameObject.SetActive(false); // Up for reuse
        }


        public void DestroyAll()
        {
            foreach (var item in itemPool)
                Destroy(item);
        }
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM