通用化的对象池设计

通用化的对象池

对象池顾名思义就是把对象池化。将一定数量的相同对象放入一个池中,当有需要用到时再从池中取出未被引用的对象,反之对象用完之后就推入池中。当某一时刻池中对象已经全被取出时,就立即动态生成出一个对象。《Patterns in Java》作者Mark Grand指出:“对象池是一种设计模式,它通过管理有限对象复用来共享某些稀少或必须付出昂贵代价的资源。”[9]从而对“对象”进行缓存和复用,减少频繁创建和销毁对象带来的成本。

一般对于大量生成且重复的创建 会使用到对象池设计思想来降低开销。如像子弹这样频繁生成的物体,如果我们直接销毁掉,那在这个销毁过程中会牵制到子弹身上相关的所有组件销毁,如网格数据、渲染数据等组件这些数据不仅GC时会产生大量开销,在下次生成时也将重新计算数据,那么对象池就是为了解决这类开销问题,将对象生成降到最低,但随之出现的问题就是对象池随着游戏时间的长短所占内存会越来越大,一般这种情况下我们就可以考虑在合适时机对“对象池”进行一个释放的操作。一般手段就是插入一段剧情、CG或者是进行一次场景切换来释放对象池。

我们平常用到的缓存列表、缓存字典,他们都是将内存空间中的一段实例引用到当前片段中,通过这样来降低访问开销,笔者认为这种其实也算是对象池池化概念。在本项目中,将这概念应用到了大部分类型上,对“对象池”进行通用化。代码如下:

public abstract class IObjectPool<T>  where T:class
{
    public List<T> objectPool = new List<T>();
    public T prefab;
    public string symbol;

    public virtual  T GetObject<T1>() where T1:T
    {
        return GetObject(typeof(T1));
    }
    public virtual T GetObject()
    {
        return GetObject<T>();
    }
    public abstract T GetObject(Type type);
    public abstract void PushItem(T go);

    public bool IsExist(T go)
    {
        return objectPool.Find(g => g.Equals(go)) != null;
    }
}

对象池抽象类

public class ObjectPool<T>:IObjectPool<T> where T:class,new()
{
    public override void PushItem(T go)
    {
        objectPool.Add(go);
    }

    public override T GetObject(Type type)
    {
        if (type == null || (type.IsSubclassOf(typeof(T)) == false)&&type!=(typeof(T)))
        {
            MyDebug.DebugError($"-[objectPool]传入的Type{type}不是T{typeof(T)}的继承类");
            return null;
        }
        if (objectPool.Count == 0)
        {
            return Activator.CreateInstance(type) as T;
        }
        T newObj = objectPool[objectPool.Count - 1];
        objectPool.RemoveAt(objectPool.Count - 1);
        return newObj;
    }
}

泛型对象池类

以上代码对通用部分进行抽象化,对于特殊类型的对象池只需要继承重写相关方法即可。如游戏物体对象池代码所示:

public class GameObjectPool: IObjectPool<GameObject>
{
    public Transform root;
    
    public GameObject GetGameObject()
    {
        return GetObject();
    }
    public GameObject FindNoActive()
    {
        GameObject objectGo = null;
        foreach (var item in objectPool)
        {
            if (item!=null&&item.activeSelf==false)
            {
                objectGo = item;
                break;
            }
        }
        return objectGo;
    }

    public GameObject NewBuildGameObject()
    {
      GameObject go=  GameObject.Instantiate(prefab,root);
        objectPool.Add(go);
        return go;
    }
    
    public override void PushItem(GameObject go)
    {
        if (go!=root)
        {
            go.transform.SetParent(root, false);//不变换局部比例,就是比例数值仍然还是原先那个,不经过世界变换
            go.gameObject.SetActive(false);
        }
       
    }

    public override GameObject GetObject(Type type)
    {
        GameObject go = FindNoActive();
        if (go == null)
        {
            go = NewBuildGameObject();
        }
        go.gameObject.SetActive(true);
        return go;
    }
}

游戏对象对象池

对象池创建完成以后,还需要一个对象池工厂,我们可以称之为“大对象池”,管理这些小对象池。

对象池工厂代码如下:

public interface IObjectFactory<ObjectT> :IFactory where ObjectT : new()
{
    public  void PushItem(ObjectT objectEntity);
    public   ObjectT PopItem(string symbol);
}

泛型对象池工厂接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class GameObjectFactory : ResFactory<GameObject, GameObjectFactory>
    {
    public Dictionary<string, GameObjectPool> objectPools = new Dictionary<string, GameObjectPool>();
        
    public Transform root;
    public Transform uiRoot;

    public GameObjectFactory()
    {
        Init();
    }

    public override GameObject PopItem(string path)
    {
        GameObjectPool objectPool = null;

        if (objectPools.ContainsKey(path)==false)
        {
            objectPool = new GameObjectPool();
            objectPool.prefab = LoadPrefab(path);
            if (objectPool.prefab.layer == LayerMask.NameToLayer("UI") == false)
            {
                objectPool.root = root;
            }
            else
            {
                objectPool.root = uiRoot;
            }
            
            objectPools.Add(path,objectPool);
        }
        else
        {
            objectPool=objectPools[path];
        }
        if (objectPool!=null)
        {
          return  objectPool.GetGameObject();
        }
        return null;
    }
    /// <summary>
    /// 用此方法要保证prefab的名字不能重复
    /// </summary>
    /// <param name="prefab"></param>
    /// <returns></returns>
    public  GameObject PopItem(GameObject prefab)
    {
        if (prefab==null)
        {
            return null;
        }
        GameObjectPool objectPool = null;
        Debug.Log(prefab);
        string path = prefab.name;
       
        if (objectPools.ContainsKey(path) == false)
        {
            objectPool = new GameObjectPool();
            objectPool.prefab = prefab;
            if (prefab.layer == LayerMask.NameToLayer("UI") == false)
            {
                objectPool.root = root;
            }
            else
            {
                objectPool.root = uiRoot;
            }
            objectPools.Add(path, objectPool);
        }
        else
        {
            objectPool = objectPools[path];
        }
        if (objectPool != null)
        {
            return objectPool.GetGameObject();
        }
        return null;
    }


    public override void PushItem(GameObject objectEntity)
    {
        bool isFindPool = false;
        foreach (var item in objectPools)
        {
            if (item.Value.IsExist(objectEntity))
            {
                item.Value.PushItem(objectEntity);
                isFindPool = true;
                break;
            }
        }
        if (isFindPool==false)
        {
            Debug.LogWarning("对象池 过程中出错,找不到对应的对象池");
            GameObject.Destroy(objectEntity);
        }
    }

    public override void Init()
    {
         root = new GameObject("Pools").transform;
        uiRoot = new GameObject("UIPools").transform;
        GameObject.DontDestroyOnLoad(root.gameObject);
        GameObject.DontDestroyOnLoad(uiRoot.gameObject);
    }
}

游戏对象的对象池工厂

调用对象池工厂的PopItem来生成所需对象。代码如下:

GameObject entity = GameObjectFactory.Instance.PopItem(path);

当对象不用时可以调用对象池的PushItem来回收对象。代码如下:

GameObject entity=GetEntity();

GameObjectFactory.Instance.PushItem(entity);

具体案例可看文章:设计敌人血条模块

作者:Miracle
来源:麦瑞克博客
链接:https://www.unitymake.com/archives/unity/179
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
通用化的对象池设计
通用化的对象池 对象池顾名思义就是把对象池化。将一定数量的相同对象放入一个池中,当有需要用到时再从池中取出未被引用的对象,反之对象用完之后就推入……
<<上一篇
下一篇>>