Unity简易对象池

不需要创建多个对象池,完全以对象名称获取池

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool
{
    private static ObjectPool instance;
    private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>();
    private GameObject pool;
    public static ObjectPool Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new ObjectPool();
            }
            return instance;
        }
    }
    public GameObject GetObject(GameObject prefab)
    {
        GameObject _object;
        if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0)
        {
            _object = GameObject.Instantiate(prefab);
            PushObject(_object);
            if (pool == null)
                pool = new GameObject("ObjectPool");
            GameObject childPool = GameObject.Find(prefab.name + "Pool");
            if (!childPool)
            {
                childPool = new GameObject(prefab.name + "Pool");
                childPool.transform.SetParent(pool.transform);
            }
            _object.transform.SetParent(childPool.transform);
        }
        _object = objectPool[prefab.name].Dequeue();
        _object.SetActive(true);
        return _object;
    }
    public GameObject GetObject(GameObject prefab,Transform tran)
    {
        GameObject _object;
        if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0)
        {
            _object = GameObject.Instantiate(prefab);
            PushObject(_object);
            if (pool == null)
                pool = new GameObject("ObjectPool");
            GameObject childPool = GameObject.Find(prefab.name + "Pool");
            if (!childPool)
            {
                childPool = new GameObject(prefab.name + "Pool");
                childPool.transform.SetParent(pool.transform);
            }
            _object.transform.SetParent(childPool.transform);
        }
        _object = objectPool[prefab.name].Dequeue();

        _object.transform.position = tran.transform.position;
        _object.transform.rotation = tran.transform.rotation;
        _object.transform.localScale = tran.transform.localScale;
        _object.SetActive(true);
        return _object;
    }

    public void PushObject(GameObject prefab)
    {
        string _name = prefab.name.Replace("(Clone)", string.Empty);
        if (!objectPool.ContainsKey(_name))
            objectPool.Add(_name, new Queue<GameObject>());
        objectPool[_name].Enqueue(prefab);
        prefab.SetActive(false);
    }
}

省去了Get时还要再次更改Transform

/*
 *  跨场景对象池,切换场景时对象池保留
 *  
 *  
 *  首先,U3D官方没有提供对象池,只提供了实例化(Instantiate)和销毁(Destroy),然而这两个方法的成本都很高,如果是物体生成销毁频率低的游戏还没多大问题,如果是一个严谨的枪战游戏,一把枪一秒钟10个子弹物体,算作场上10个人,这游戏没法玩了
 *  
 *  为了解决这个问题就要把对象池搬出来了
 *  
 *  Unity还有一个类似实例化和销毁的功能是 启用/禁用物体,也就是SetActive(bool),禁用物体的效果和销毁类似,只不过内存继续占用,启用一个被禁用的物体效果则和实例化很像,重点在于这个操作成本特别低,一帧执行几千次也不会卡
 *  
 *  那么对象池的功能就很简单了:先准备一个容器用来存储物体的引用,场上有物体要销毁的时候不进行销毁而是禁用后把引用存起来,需要实例化的时候通过引用启用这个物体,这期间物体一直在场上不需要实例化和销毁
 *  
 *  
 *  为了达到替代 Instantiate 和 Destroy 的效果,按照 Instantiate 和 Destroy 的所有重载写了 Get 和 Set
 */

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MtC.Tools.ObjectPool
{
    public class MPool : MonoBehaviour
    {
        static Transform poolParent     //所有池内物体的父级
        {
            get
            {
                if (_poolParent != null)
                    return _poolParent;

                GameObject poolObject = new GameObject("Object Pool");
                DontDestroyOnLoad(poolObject);                              //设置对象池物体不会随场景加载被销毁,这步很重要,能不能跨场景都在这了
                _poolParent = poolObject.transform;
                return _poolParent;

                //经典单例模式,保证场上有且只有一个对象池物体
            }
        }
        static Transform _poolParent;

        static MPool poolComponent       //挂载的对象池组件
        {
            get
            {
                if (_poolComponent != null)
                    return _poolComponent;

                _poolComponent = poolParent.gameObject.AddComponent<MPool>();
                return _poolComponent;
            }
        }
        static MPool _poolComponent;


        Dictionary<GameObject, GameObject> _poolObjects = new Dictionary<GameObject, GameObject>();                     //总表,所有对象池创建的物体,不管在池里还是在场上
        Dictionary<GameObject, Stack<GameObject>> _insidePoolObjects = new Dictionary<GameObject, Stack<GameObject>>(); //内表,对象池里的物体
        //字典(Dictionary):通过键值查找元素的数据类型,创建格式是:Dictionary<Tkey, TValue> 前一个是键值类型,后一个是元素类型

        Stack<GameObject> _currentFrameSetObjects = new Stack<GameObject>();    //当前帧需要存入池的物体,为了模仿 Destroy 的销毁总在当前帧最后进行,把当前帧需要存入池的物体先暂存起来,到 SceneManager.sceneLoaded 再存入



        private void Awake()
        {
            SceneManager.sceneLoaded += OnSceneLoaded;      //订阅场景加载事件,OnLevelWasLoaded过时了,会在最近版本弃用,新方法暂时是订阅事件
            Camera.onPreCull += OnPerCull;                  //订阅摄像机开始剔除事件,剔除是渲染的第一步,这个事件是渲染前最后一个事件
        }



        //存入
        public static void Set(GameObject setObject, float delay = 0)
        {
            poolComponent.StartCoroutine(DelaySet(setObject, delay));
        }
        static IEnumerator DelaySet(GameObject setObject, float delay)
        {
            if (delay > 0)
                yield return new WaitForSeconds(delay);
            poolComponent._currentFrameSetObjects.Push(setObject);
        }

        void OnPerCull(Camera cam)
        {
            SetObjectsIntoPool();
        }
        void SetObjectsIntoPool()
        {
            while (_currentFrameSetObjects.Count > 0)
                SetAnObject(_currentFrameSetObjects.Pop());         //在 SceneManager.sceneLoaded 把当前帧需要存入池的物体全都存入池,如果其他脚本有在 SceneManager.sceneLoaded 存入池的情况,我敬他是条汉子。
        }
        void SetAnObject(GameObject setObject)
        {
            if (setObject == null) return;

            GameObject prefab = null;
            if (_poolObjects.TryGetValue(setObject, out prefab))    //通过总表获取预制,获取得到说明是池物体
            {
                if (InPool(setObject, prefab)) return;              //在内表里则不需要任何操作

                DoSet(setObject, prefab);
            }
            else
            {
                Destroy(setObject);                                 //没取到预制说明不是池物体,无法存入池,直接销毁
            }
        }
        bool InPool(GameObject go, GameObject prefab)
        {
            Stack<GameObject> stack;
            if (_insidePoolObjects.TryGetValue(prefab, out stack))
                return stack.Contains(go);
            return false;
        }
        void DoSet(GameObject setObject, GameObject prefab)
        {
            setObject.SetActive(false);                                     //禁用物体同时物体的所有协程也一起停止了,不用特别处理

            DoOnSet(setObject);

            if (!_insidePoolObjects.ContainsKey(prefab))                    //Dictionary.ContainsKey():查找字典里有没有这个键值
                _insidePoolObjects.Add(prefab, new Stack<GameObject>());    //Dictionary.Add():向字典里增加一对键值和元素,字典不会自动增加键值和元素,只能手动进行

            _insidePoolObjects[prefab].Push(setObject);                     //字典获取元素的方法类似于数组,是方括号里写键值:[键值]
            setObject.transform.SetParent(poolParent, false);               //将存入池的物体移到对象池物体下作为子物体
                                                                            //这一步很重要,如果不转移到对象池物体下的话有可能会因为原本的父物体销毁而导致对象池出现空位造成资源浪费
                                                                            //同时对象池物体已经设置加载场景时不销毁,他的子物体同样不会在加载场景时销毁,对象池就可以跨场景使用
        }

        void DoOnSet(GameObject setObject)
        {
            IOnSetIntoPool[] resetComponents = setObject.GetComponents<IOnSetIntoPool>();
            foreach (IOnSetIntoPool resetComponent in resetComponents)
                resetComponent.OnSetIntoPool();
        }



        //取出
        public static GameObject Get(GameObject prefab)
        {
            GameObject instance = GetInactiveObjectFromPool(prefab);

            if (instance != null)
            {
                instance.transform.position = prefab.transform.position;
                instance.transform.rotation = prefab.transform.rotation;
                instance.transform.localScale = prefab.transform.localScale;
                instance.SetActive(true);
                return instance;
            }
            else
            {
                instance = Instantiate(prefab);
                poolComponent._poolObjects.Add(instance, prefab);       //加入到总表,只要物体还存在就不会被移出总表,所以只要在实例化时加入一次
                return instance;
            }
        }
        public static GameObject Get(GameObject prefab, Transform parent)
        {
            GameObject instance = GetInactiveObjectFromPool(prefab);

            if (instance != null)
            {
                instance.transform.position = prefab.transform.position;
                instance.transform.rotation = prefab.transform.rotation;
                instance.transform.localScale = prefab.transform.localScale;
                instance.transform.SetParent(parent, false);
                instance.SetActive(true);
                return instance;
            }
            else
            {
                instance = Instantiate(prefab, parent);
                poolComponent._poolObjects.Add(instance, prefab);
                return instance;
            }
        }
        public static GameObject Get(GameObject prefab, Transform parent, bool instantiateInWorldSpace)
        {
            GameObject instance = GetInactiveObjectFromPool(prefab);

            if (instance != null)
            {
                instance.transform.position = prefab.transform.position;
                instance.transform.rotation = prefab.transform.rotation;
                instance.transform.localScale = prefab.transform.localScale;
                instance.transform.SetParent(parent, instantiateInWorldSpace);
                instance.SetActive(true);
                return instance;
            }
            else
            {
                instance = Instantiate(prefab, parent, instantiateInWorldSpace);
                poolComponent._poolObjects.Add(instance, prefab);
                return instance;
            }            
        }
        public static GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation)
        {
            GameObject instance = GetInactiveObjectFromPool(prefab);

            if (instance != null)
            {
                instance.transform.position = position;
                instance.transform.rotation = rotation;
                instance.transform.localScale = prefab.transform.localScale;
                instance.SetActive(true);
                return instance;
            }
            else
            {
                instance = Instantiate(prefab, position, rotation);
                poolComponent._poolObjects.Add(instance, prefab);
                return instance;
            }
        }
        public static GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent)
        {
            GameObject instance = GetInactiveObjectFromPool(prefab);

            if (instance != null)
            {
                instance.transform.position = position;
                instance.transform.rotation = rotation;
                instance.transform.SetParent(parent);
                instance.transform.localScale = prefab.transform.localScale;
                instance.SetActive(true);
                return instance;
            }
            else
            {
                instance = Instantiate(prefab, position, rotation, parent);
                poolComponent._poolObjects.Add(instance, prefab);
                return instance;
            }
        }

        
        static GameObject GetInactiveObjectFromPool(GameObject prefab)      //从池里获取未激活的物体,因为要处理后再激活
        {
            Stack<GameObject> stack;
            if (poolComponent._insidePoolObjects.TryGetValue(prefab, out stack) && stack.Count > 0)
            {
                GameObject instance = stack.Pop();
                if (instance != null)
                {
                    instance.transform.SetParent(null, false);      //首先把这个物体从对象池物体下移出来
                    CancelDontDestroyOnLoad(instance);              //取消物体的加载场景不销毁效果

                    DoOnGet(instance);                              //重置这个物体

                    return instance;
                }
            }

            return null;
        }

        static void CancelDontDestroyOnLoad(GameObject go)
        {
            SceneManager.MoveGameObjectToScene(go, SceneManager.GetActiveScene());
            //把物体移动到当前激活场景就能取消 DontDestroyOnLoad 效果,具体原理见 https://github.com/MrTrueChina/Unity-Cancel-DontDestroyOnLoad
        }

        static void DoOnGet(GameObject setObject)           //在取出时重置物体,因为找不到重置整个物体的方法,所以通过获取所有需要重置的组件并调用重置方法来达到类似效果
        {
            IOnGetFromPool[] resetComponents = setObject.GetComponents<IOnGetFromPool>();
            foreach (IOnGetFromPool resetComponent in resetComponents)
                resetComponent.OnGetFromPool();
        }


        
        private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            StopAllCoroutines();                            //没有在加载场景前发出的事件,无法回收在场上的物体,中断所有协程,防止空存入
            ClearDestroyedObjectInPoolObjects();
        }

        void ClearDestroyedObjectInPoolObjects()
        {
            _poolObjects = (from kv in _poolObjects where kv.Key != null select kv).ToDictionary(kv => kv.Key, kv => kv.Value);     //用Linq清除总表里的键值为空的元素
        }



        private void OnDestroy()
        {
            SceneManager.sceneLoaded -= OnSceneLoaded;      //被销毁时取消对场景加载的订阅
            Camera.onPreCull -= OnPerCull;                  //取消对摄像机开始剔除事件的订阅
        }
    }



    /*
     *  需要在取出池时重置或者执行方法的组件可以实现这个接口
     *  
     *  
     *  我找了好长时间也找不到自带的在运行时重置物体或者组件的方法,然后我找了别人写的对象池发现也不能重置物体或组件,我猜测重置物体或组件的功能应该不会比实例化和挂载组件节省多少资源,所以官方才没有提供这个方法
     *  
     *  于是只能曲线救国了,写一个接口,写上重置方法之后让对象池在取出的时候调用,之所以是取出是因为对象在池里时有可能被以其他方式访问并修改,在取出时重置明显更安全
     */
    public interface IOnGetFromPool
    {
        void OnGetFromPool();
    }

    /*
     *  这个是在存入时执行的,可以用来取消订阅和引用,防止内存泄漏
     */
    public interface IOnSetIntoPool
    {
        void OnSetIntoPool();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容