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);
}
}
}
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
逻辑:加载资源,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;
}
}
}