13. 对象池

13-1. 对象池

  • 作用:减少对象创建和销毁的次数,让对象可以重复使用
  • 对象特点:使用频率高、使用次数多
  • 对象类型:AssetBundle需要Unload、GameObject(UI、Entity、Sound)需要Destroy

传统对象池:

  • 原理:先创建一定数量的对象、使用时从池子取,用完了还回去
  • 特点:相同对象、多次使用

我们的对象池:

  • 特点:对象可以是多种类型、短时间内可重复使用、过期自动销毁
  • 设计原理:池内不创建对象,对象在外部创建,使用完放入对象池,再次使用从对象池取

创建Framework/ObjectPool/PoolObject

using UnityEngine;

//专门用来存放ObjectPool里放置的不同对象的数据类
public class PoolObject
{
    //具体对象
    public Object Object;
    
    //对象名字
    public string Name;
    
    //最后一次使用时间,,,针对不同类型的对象有不同的有效时长,超过有效时长就销毁
    public System.DateTime LastUseTime;

    //生成实例,放入池子时,会记录一下上一次的使用时间
    public PoolObject(string name, Object obj)
    {
        Name = name;
        Object = obj;
        LastUseTime = System.DateTime.Now;
    }
}

对于AssetBundle和GameObject的存和取都不相同。AssetBundle不需要设置非激活。
创建Framework/ObjectPool/PoolBase

using System.Collections.Generic;
using UnityEngine;

public class PoolBase : MonoBehaviour
{
    //自动释放时间/s
    protected float m_ReleaseTime;
    
    //上次释放资源的时间/毫微妙 1(秒) = 10000000(毫微秒)
    protected long m_LastReleaseTime = 0;
    
    //真正的对象池
    protected List<PoolObject> m_Objects;

    public void Start()
    {
        m_LastReleaseTime = System.DateTime.Now.Ticks;
    }
    
    //初始化对象池
    public void Init(float time)
    {
        m_ReleaseTime = time;
        m_Objects = new List<PoolObject>();
    }

    //取出对象
    public virtual Object Spawn(string name)
    {
        foreach (PoolObject po in m_Objects)
        {
            if (po.Name == name)
            {
                //放在池子里的一定是还没有用到的,用到的就拿出来
                m_Objects.Remove(po);
                return po.Object;
            }
        }
        return null;
    }
    
    //回收对象
    public virtual void UnSpawn(string name, Object obj)
    {
        PoolObject po = new PoolObject(name, obj);
        m_Objects.Add(po);
    }
    
    //释放,父类什么都不做
    public virtual void Release()
    {
        
    }

    private void Update()
    {
		//父类需要按给定时间执行一次Release,但是到底释放没有,需要根据继承父类的子类来确定,其内部的时间是否到了销毁时间
        if (System.DateTime.Now.Ticks - m_LastReleaseTime >= m_ReleaseTime * 10000000)
        {
            //刷新时间
            m_LastReleaseTime = System.DateTime.Now.Ticks;
            Release();
        }
    }
}
public class GameObjectPool : PoolBase
{
    //去物体:在池子中查找,如果有物体就激活,没有返回null
    public override Object Spawn(string name)
    {
        Object obj = base.Spawn(name);
        if (obj == null)
            return null;
        
        GameObject go = obj as GameObject;
        go.SetActive(true);
        return obj;
    }

    //存物体:把物体设置非激活,然后放进池子回收
    public override void UnSpawn(string name, Object obj)
    {
        GameObject go = obj as GameObject;
        go.SetActive(false);
        go.transform.SetParent(this.transform, false);
        base.UnSpawn(name, obj);
    }

    public override void Release()
    {
        base.Release();
        foreach (PoolObject item in m_Objects)
        {
            if (System.DateTime.Now.Ticks - item.LastUseTime.Ticks >= m_ReleaseTime * 10000000)
            {
                Debug.Log("GameObjectPool release time:" + System.DateTime.Now);
                Destroy(item.Object);
                m_Objects.Remove(item);
                //因为删除了list一项之后,不能用迭代器了,需要递归一下跳出
                Release();
                return;
            }
        }
    }
}
public class AssetPool : PoolBase
{
    public override UnityEngine.Object Spawn(string name)
    {
        return base.Spawn(name);
    }

    public override void UnSpawn(string name, UnityEngine.Object obj)
    {
        base.UnSpawn(name, obj);
    }

    public override void Release()
    {
        base.Release();
        foreach (PoolObject item in m_Objects)
        {
            if (System.DateTime.Now.Ticks - item.LastUseTime.Ticks >= m_ReleaseTime * 10000000)
            {
                Debug.Log("AssetPool release time:" + System.DateTime.Now);
                Manager.Resource.UnloadBundle(name);
                m_Objects.Remove(item);
                Release();
                return;
            }
        }
    }
}
public void UnloadBundle(string name)
{
    
}

创建Framework-Manager-PoolManager

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

//运行时,在pool下创建一个poolName的子物体,挂载脚本,AssetBundle挂载AssetPool,GameObject挂载GameObjectPool脚本
//挂载的脚本就是对象池,先Init初始化对象池,然后将这个对象池添加到PoolManager中进行管理。
//通过PoolManager的m_Pools管理里面的对象池,分别取出和放回对象
public class PoolManager : MonoBehaviour
{
    //对象池父节点
    private Transform m_PoolParent;

    //对象池字典
    private Dictionary<string, PoolBase> m_Pools = new Dictionary<string, PoolBase>();

    private void Awake()
    {
        m_PoolParent = this.transform.parent.Find("Pool");
    }
    
    //创建对象池,,T必须是继承PoolBase的类型
    private void CreatePool<T>(string poolName, float releaseTime) where T : PoolBase
    {
        if (!m_Pools.TryGetValue(poolName, out PoolBase pool))
        {
            GameObject go = new GameObject(poolName);
            go.transform.SetParent(m_PoolParent);
            pool = go.AddComponent<T>();
            pool.Init(releaseTime);
            m_Pools.Add(poolName, pool);
        }
    }
    
    //创建物体对象池
    public void CreateGameObjectPool(string poolName, float releaseTime)
    {
        CreatePool<GameObjectPool>(poolName, releaseTime);
    }
    
    //创建资源对象池
    public void CreateAssetPool(string poolName, float releaseTime)
    {
        CreatePool<AssetPool>(poolName, releaseTime);
    }

	//取出对象
    public Object Spawn(string poolName, string assetName)
    {
        if (m_Pools.TryGetValue(poolName, out PoolBase pool))
        {
            return pool.Spawn(assetName);
        }
        return null;
    }
    
    //回收对象
    public void UnSpawn(string poolName, string assetName, Object asset)
    {
        if (m_Pools.TryGetValue(poolName, out PoolBase pool))
        {
            pool.UnSpawn(assetName, asset);
        }
    }
}

Unity对象池出现竞争怎么办 unity对象池的原理_lua

private static PoolManager _pool;
public static PoolManager Pool
{
    get { return _pool; }
}

public void Awake()
{
    _resource = this.gameObject.AddComponent<ResourceManager>();
    _lua = this.gameObject.AddComponent<LuaManager>();
    _ui = this.gameObject.AddComponent<UIManager>();
    _entity = this.gameObject.AddComponent<EntityManager>();
    _scene = this.gameObject.AddComponent<MySceneManager>();
    _sound = this.gameObject.AddComponent<SoundManager>();
    _event = this.gameObject.AddComponent<EventManager>();
    _pool = this.gameObject.AddComponent<PoolManager>();
}

13-2. 对象池测试

在Lua的脚本中编写逻辑。调用Manager.UI.OpenUI(“”),OpenUI函数时UIManager中的打开UI功能,函数内部去对象池查找,有就Spawn没有就加载Bundle后LoadUI;调用self:Close()关闭UI,这个self是UILogic,因为OpenUI函数传进去一个ui.TestUI用于Init,并连接到执行了Init这个功能的脚本UILogic(继承了LuaBehaviour),从而在TestUI脚本中用self就是UILogic的实例。

public class UILogic : LuaBehaviour
{
    public string AssetName;
    Action m_LuaOnOpen;
    Action m_LuaOnClose;
    public override void Init(string luaName)
    {
        base.Init(luaName);
        m_ScriptEnv.Get("OnOpen", out m_LuaOnOpen);
        m_ScriptEnv.Get("OnClose", out m_LuaOnClose);
    }
    
    public void OnOpen()
    {
        m_LuaOnOpen?.Invoke();
    }

    public void OnClose()
    {
        m_LuaOnClose?.Invoke();
        //回收对象,把这个带有UILogic的物体直接放进对象池。
        Manager.Pool.UnSpawn("UI", AssetName, this.gameObject);
    }
    protected override void Clear()
    {
        //OnClose();
        base.Clear();
        m_LuaOnOpen = null;
        m_LuaOnClose = null;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour
{
    缓存UI,,后期会用对象池管理,,,不需要了
    //Dictionary<string, GameObject> m_UI = new Dictionary<string, GameObject>();

    //UI分组
    Dictionary<string, Transform> m_UIGroups = new Dictionary<string, Transform>();

    private Transform m_UIParent;

    /// <summary>
    /// 传入一个ui名字和lua名字,以及ui要放到的组中,自动给ui预制体绑定C#脚本,自动执行lua脚本
    /// </summary>
    /// <param name="uiName">ui名字</param>
    /// <param name="luaName">lua名字</param>
    public void OpenUI(string uiName, string group, string luaName)
    {
        GameObject ui = null;
        //加载ui成功后,设置父节点
        Transform parent = GetUIGroup(group);

        //去对象池查找,如果有ui就直接加载,没有再去Load
        string uiPath = PathUtil.GetUIPath(uiName);
        Object uiObj = Manager.Pool.Spawn("UI",uiPath);
        if (uiObj != null)
        {
            ui = uiObj as GameObject;
			ui.transform.SetParent(parent, false);
            UILogic uiLogic = ui.GetComponent<UILogic>();
            uiLogic.OnOpen();
            return;
        }

        Manager.Resource.LoadUI(uiName, (UnityEngine.Object obj) =>
        {
            ui = Instantiate(obj) as GameObject;
			ui.transform.SetParent(parent, false);
			//m_UI.Add(uiName, ui);
            if (ui.name.Contains("TestUI"))
            {
                UISelect uiSelect = ui.transform.GetChild(0).GetChild(1).gameObject.AddComponent<UISelect>();
                uiSelect.Init("ui.SelectMenuUI");
                uiSelect.OnOpen();

                OptionsSelect optionsSelect = ui.transform.GetChild(1).gameObject.AddComponent<OptionsSelect>();
                optionsSelect.Init("ui.SelectOptions");
                optionsSelect.OnOpen();
            }

            //给UI预制体绑定UILogic的C#脚本
            UILogic uiLogic = ui.AddComponent<UILogic>();
            uiLogic.AssetName = uiPath;
            //初始化这个lua脚本(Awake)
            uiLogic.Init(luaName);
            //UI的Start
            uiLogic.OnOpen();
        });
    }
}
public class GameStart : MonoBehaviour
{
    public GameMode GameMode;

    // Start is called before the first frame update
    void Start()
    {
        //开始时订阅事件
        Manager.Event.Subscribe(10000, OnLuaInit);
        
        AppConst.GameMode = this.GameMode;
        DontDestroyOnLoad(this);

        Manager.Resource.ParseVersionFile();
        Manager.Lua.Init();
    }

    void OnLuaInit(object args)
    {
        //初始化完成之后(lua都加载完),在执行回调
        Manager.Lua.StartLua("main"); //输入的文件名
        //输入的是函数名
        XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
        func.Call();
        
        Manager.Pool.CreateGameObjectPool("UI", 10);
        Manager.Pool.CreateGameObjectPool("Monster", 120);
        Manager.Pool.CreateGameObjectPool("Effect", 120);
        Manager.Pool.CreateAssetPool("AssetBundle", 300);
    }

    public void OnApplicationQuit()
    {
        Manager.Event.UnSubscribe(10000, OnLuaInit);
    }
}
btn_pooltest = self.transform:Find("amiibo"):GetComponent("Button");
btn_close = self.transform:Find("DLC Purchased"):GetComponent("Button");
btn_pooltest:OnClickSet(
        function()
            Manager.UI:OpenUI("TestUI", "UI", "ui.TestUI");
        end
)

btn_close:OnClickSet(
        function()
            self:Close();
        end
)

13-3. AssetBundle卸载

需要给Bundle资源添加引用计数,当没有使用的时候,就卸载bundle

Unity对象池出现竞争怎么办 unity对象池的原理_unity_02

Unity对象池出现竞争怎么办 unity对象池的原理_Unity对象池出现竞争怎么办_03


逻辑:加载资源,ResourceManager.LoadBundleAsync加引用计数,当GameObjectPool把不用的物体release时需要调用ResourceManager.MinusBundleCount减去该物体资源的引用计数,当引用计数<=0时,放到对象池release,其中调用Manager.Resource.UnloadBundle卸载资源。

public class ResourceManager : MonoBehaviour
{
    //定义一个类,用来解析版本信息
    internal class BundleInfo
    {
        public string AssetName;
        public string BundleName;
        public List<string> Dependeces;
    }

    internal class BundleData
    {
        public AssetBundle Bundle;
        
        //引用计数
        public int Count;

        public BundleData(AssetBundle ab)
        {
            Bundle = ab;
            Count = 1;
        }
    }
    
    //存放bundle信息的集合
    private Dictionary<string, BundleInfo> m_BundleInfos = new Dictionary<string, BundleInfo>();
    //存放加载过的Bundle
    //private Dictionary<string, AssetBundle> m_AssetBundles = new Dictionary<string, AssetBundle>();
    private Dictionary<string, BundleData> m_AssetBundles = new Dictionary<string, BundleData>();

    IEnumerator LoadBundleAsync(string assetName,Action<UObject> action = null)
    {
        string bundleName = m_BundleInfos[assetName].BundleName;
        //这个是小写的bundle.ab的路径名
        string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
        List<string> dependences = m_BundleInfos[assetName].Dependeces;
        //判断是否已经加载过bundle了
        BundleData bundle = GetBundle(bundleName);
        if (bundle == null)
        {
            //如果没有取到就要去对象池取
            UObject obj = Manager.Pool.Spawn("AssetBundle", bundleName);
            if (obj != null)
            {
                AssetBundle ab = obj as AssetBundle;
                bundle = new BundleData(ab);
            }
            else//如果对象池没取到,就去加载bundle
            {
                //创建异步加载bundle申请
                AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
                yield return request;
                bundle = new BundleData(request.assetBundle);
            }
            m_AssetBundles.Add(bundleName, bundle);
        }

		//先加载需要的Bundle,然后检测依赖资源,如果依赖资源也卸载掉了,就加载依赖资源
        if (dependences != null && dependences.Count > 0)
        {
            //递归加载依赖bundle,因为依赖的资源目录名就是bundle资源名
            for (int i = 0; i < dependences.Count; i++)
            {
                yield return LoadBundleAsync(dependences[i]);
            }
        }

        if (assetName.EndsWith(".unity"))
        {
            action?.Invoke(null);
            yield break;
        }

		//如果action == null,说明是依赖资源,依赖资源不需要加载asset,也不需要执行回调
        if (action == null)
        {
            yield break;
        }

        //从bundle申请加载指定路径名的文件,例如prefab
        AssetBundleRequest bundleRequest = bundle.Bundle.LoadAssetAsync(assetName);
        yield return bundleRequest;

        //如果回调和request都不为空,语法糖
        action?.Invoke(bundleRequest?.asset);
    }

    //从已经加载的Bundle字典中查询是否已经有了
    BundleData GetBundle(string name)
    {
        BundleData bundle = null;
        if (m_AssetBundles.TryGetValue(name, out bundle))
        {
            //拿到了BundleData说明要使用了
            bundle.Count++;
            return bundle;
        }
        return null;
    }

    public void UnloadBundle(string name)
    {
        
    }

    //减去一个bundle的引用计数
    private void MinusOneBundleCount(string bundleName)
    {
        if (m_AssetBundles.TryGetValue(bundleName, out BundleData bundle))
        {
            if (bundle.Count > 0)
            {
                bundle.Count--;
                Debug.Log("bundle引用计数:" + bundleName + " count:" + bundle.Count);
            }

            if (bundle.Count <= 0)
            {
                 Debug.Log("放入bundle对象池:" + bundleName);
                 Manager.Pool.UnSpawn("AssetBundle", bundleName, bundle.Bundle);
                 m_AssetBundles.Remove(bundleName);
            }
        }
    }
    
    //减去Bundle和依赖资源的引用计数
    public void MinusBundleCount(string assetName)
    {
        //先减掉自身的bundle引用计数
        string bundleName = m_BundleInfos[assetName].BundleName;
        MinusOneBundleCount(bundleName);
        
        //依赖资源,,在查找依赖资源,减掉依赖资源的引用计数
        List<string> dependences = m_BundleInfos[assetName].Dependeces;
        if (dependences != null)
        {
            foreach (string dependence in dependences)
            {
                string name = m_BundleInfos[dependence].BundleName;
                MinusOneBundleCount(name);
            }
        }
    }

	//卸载assetbundle资源
    public void UnloadBundle(UObject obj)
    {
        AssetBundle ab = obj as AssetBundle;
        ab.Unload(true);
    }
}
public override void Release()
{
    base.Release();
    foreach (PoolObject item in m_Objects)
    {
        if (System.DateTime.Now.Ticks - item.LastUseTime.Ticks >= m_ReleaseTime * 10000000)
        {
            Debug.Log("GameObjectPool release time:" + System.DateTime.Now);
            Destroy(item.Object);
            Manager.Resource.MinusBundleCount(item.Name);
            m_Objects.Remove(item);
            //因为删除了list一项之后,不能用迭代器了,需要递归一下跳出
            Release();
            return;
        }
    }
}
public override void Release()
{
    base.Release();
    foreach (PoolObject item in m_Objects)
    {
        if (System.DateTime.Now.Ticks - item.LastUseTime.Ticks >= m_ReleaseTime * 10000000)
        {
            Debug.Log("AssetPool release time:" + System.DateTime.Now + "unload ab:" + item.Name);
            Manager.Resource.UnloadBundle(item.Object);
            m_Objects.Remove(item);
            Release();
            return;
        }
    }
}