本文分享Unity中的资源管理-对象池技术(3)

在上两篇文章中, 我们一起学习了普通类的两种对象池实现, 今天接着介绍Unity中GameObject(主要是预制实例化对象)的对象池.

GameObjectPool的必要性

我们知道在游戏前端的开发中, 大部分情况下并不会像服务器开发一样会涉及到特别大量的对象的创建和销毁, 一般的容器最多也就几十上百个对象, 并不会有太大的性能压力, 所以普通类的对象池在客户端使用的并不多, 一般也就资源管理等少数场合会涉及到巨量普通类对象的使用, 而游戏前端更多利用对象池的场合在大量或者复杂游戏对象的使用上, 比如大量子弹, 敌人, 怪物, UI等.

因为游戏对象一般会涉及到显示相关的部分, 构建和销毁操作会比普通类对象更复杂, 占用的资源更多, 造成的代价更高. 为了避免不必要的资源浪费和提高游戏体验, 在大型游戏中, 使用游戏对象的对象池是比较普遍的方案.

在Unity中, 大部分资源通常情况下是不需要手动实例化的, 比如texture, atlas, shader, material, sound, animation等, 一般都是通过预制实例化来使用, 所以我们所谓GameObject的对象池, 更多指的是预制实例化对象的对象池.

对ObjectPool的优化

在第一篇文章中, 我们介绍了对象池的一种实现, 在进一步介绍游戏对象的对象池之前, 我们需要对该实现做一点优化以便更加通用.

主要的优化点在于:

  • 抽象出对象池的接口: IObjectPool
  • 规范化创建对象的委托和各种触发点的委托, 并添加可变长参数

对象池接口: IObjectPool

我们将产生和回收封装成为接口, 以便更加通用.

现在产生对象的方法可以接受可变长参数.

public interface IObjectPool<T> : IDisposable where T : class, new() {T Spawn(params object[] param);void Recycle(T obj);
}

各种委托

将委托封装并公开, 并可以接受可变长参数.

public class ObjectPoolDelegate<T>
{public delegate T NewObjFunc(params object[] param);public delegate void ObjectPoolAction(T obj, params object[] param);
}protected ObjectPoolDelegate<T>.ObjectPoolAction m_BeforeSpawnAction, m_AfterRecycleAction, m_AfterReleaseAction;
protected ObjectPoolDelegate<T>.NewObjFunc newObjFunc;

完整代码

其它的部分变化不大, 为了完整性, 这里还是贴出代码:

public interface IObjectPool<T> : IDisposable where T : class, new() {T Spawn(params object[] param);void Recycle(T obj);
}public class ObjectPoolDelegate<T>
{public delegate T NewObjFunc(params object[] param);public delegate void ObjectPoolAction(T obj, params object[] param);
}public class ObjectPool<T> : IObjectPool<T>, IDisposable where T : class, new() {protected Stack<T> m_ObjPool;protected Dictionary<int, T> m_ObjDict;protected int m_MaxCount;protected int m_OnceReleaseCount;protected ObjectPoolDelegate<T>.ObjectPoolAction m_BeforeSpawnAction, m_AfterRecycleAction, m_AfterReleaseAction;protected ObjectPoolDelegate<T>.NewObjFunc m_NewObjFunc;public ObjectPool(int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10,ObjectPoolDelegate<T>.ObjectPoolAction beforeSpawnAction = null, ObjectPoolDelegate<T>.ObjectPoolAction afterRecycleAction = null, ObjectPoolDelegate<T>.ObjectPoolAction afterReleaseAction = null, ObjectPoolDelegate<T>.NewObjFunc newObjFunc = null) {m_ObjPool = new Stack<T>(initCapacity);m_ObjDict = new Dictionary<int, T>(initCapacity);m_MaxCount = maxCount;m_OnceReleaseCount = onceReleaseCount;m_BeforeSpawnAction = beforeSpawnAction;m_AfterRecycleAction = afterRecycleAction;m_AfterReleaseAction = afterReleaseAction;m_NewObjFunc = newObjFunc;}public void Dispose() {if (m_AfterReleaseAction != null) {var array = m_ObjDict.Select(s=>s.Value);foreach(var obj in array) {m_AfterReleaseAction(obj);}}m_ObjPool.Clear();m_ObjDict.Clear();m_BeforeSpawnAction = m_AfterRecycleAction = m_AfterReleaseAction = null;m_NewObjFunc = null;}/// <summary>/// 移除多余对象/// </summary>private void ReleaseOverflowObj() {Debug.Log("[ReleaseOverflowObj] 已达最大回收数量: " + m_ObjPool.Count);var removeCount = Math.Min(m_OnceReleaseCount, m_ObjPool.Count);while(--removeCount >= 0) {var obj = m_ObjPool.Pop();Release(obj);}Debug.Log("[ReleaseOverflowObj] 当前池中数量: " + m_ObjPool.Count);}public T CreateObj(params object[] param) {var obj = m_NewObjFunc != null ? m_NewObjFunc(param) : new T();m_ObjDict.Add(obj.GetHashCode(), obj);return obj;}public bool Release(T obj) {m_ObjDict.Remove(obj.GetHashCode());m_AfterReleaseAction?.Invoke(obj);return true;}public int Count() {return m_ObjPool.Count;}public T Spawn(params object[] param) {var obj = m_ObjPool.Count <= 0 ? CreateObj(param) : m_ObjPool.Pop();m_BeforeSpawnAction?.Invoke(obj, param);return obj;}public void Recycle(T obj) {if (m_ObjPool.Count >= m_MaxCount) {ReleaseOverflowObj();}if (m_ObjPool.Contains(obj)) return;m_ObjPool.Push(obj);m_AfterRecycleAction?.Invoke(obj);}public void Dispose() {if (m_AfterReleaseAction != null) {var array = m_ObjDict.Select(s=>s.Value);foreach(var obj in array) {m_AfterReleaseAction(obj);}}m_ObjPool.Clear();m_ObjDict.Clear();m_BeforeSpawnAction = m_AfterRecycleAction = m_AfterReleaseAction = null;m_NewObjFunc = null;}
}

GameObjectPool

游戏对象的对象池相比普通的对象池有它独有的特点:

  • 存放的对象类型为: GameObject

  • 需要提供创建对象的委托, 将创建对象的工作交给外部以兼容更多的情况

  • 需要提供对象的挂载点(Transform), 挂载点默认是不可见的(SetActive(false)), 将处于对象池内部的对象挂载在其之下时默认就不可见, 不需要再次调用SetActive(false)以提高性能

  • 实现对象池接口, 并持有一个普通的对象池, 而不是继承对象池

  • 在各个委托时做一些和游戏对象有关的基本操作, 如: 设置位置/旋转, 设置父节点, 销毁对象等

详细信息可以参考下面的代码和注释:

using NewObjFunc = ObjectPoolDelegate<GameObject>.NewObjFunc;
using ObjectPoolAction = ObjectPoolDelegate<GameObject>.ObjectPoolAction;// 实现对象池接口, 使用聚合的方式实现, 降低依赖性
public class GameObjectPool : IObjectPool<GameObject> {// 持有一个普通的对象池private ObjectPool<GameObject> m_ObjectPool;// 挂载点private Transform m_PoolParent;// 创建对象的委托private NewObjFunc m_NewObjFunc;// 产生之前, 回收之后, 释放之后的委托private ObjectPoolAction m_BeforeSpawnAction, m_AfterRecycleAction, m_AfterReleaseAction;//---------------------------------------------// 公开委托属性供外部调用public ObjectPoolAction beforeSpawnAction {set => m_BeforeSpawnAction = value;}public ObjectPoolAction afterRecycleAction {set => m_AfterRecycleAction = value;}public ObjectPoolAction afterReleaseAction {set => m_AfterReleaseAction = value;}//---------------------------------------------// 只提供子类使用, 延迟对象池的实例化protected GameObjectPool() {}// 封装对象池实例化和初始化protected void InitPool(NewObjFunc newObjFunc, Transform poolParent, int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10) {Assert.IsNotNull(newObjFunc, "实例化对象函数不能为空!");Assert.IsNotNull(poolParent, "挂载节点不能为空!");m_PoolParent = poolParent;m_NewObjFunc = newObjFunc;// 不可见挂载点m_PoolParent.gameObject.SetActive(false);// 持有普通对象池, 并劫持各种委托m_ObjectPool = new ObjectPool<GameObject>(initCapacity, maxCount, onceReleaseCount, BeforeSpawn, AfterRecycle, AfterRelease, NewObj);}public GameObjectPool(NewObjFunc newObjFunc, Transform poolParent, int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10) {InitPool(newObjFunc, poolParent, initCapacity, maxCount, onceReleaseCount);}//---------------------------------------------// 对象池接口, 直接使用持有的对象池接口public GameObject Spawn(params object[] param) {var obj = m_ObjectPool.Spawn(param);return obj;}public void Recycle(GameObject obj) {m_ObjectPool.Recycle(obj);}// 清理委托和卸载对象池public virtual void Dispose() {m_ObjectPool?.Dispose();m_NewObjFunc = null;m_AfterReleaseAction = null;}//---------------------------------------------public GameObject CreateObj(params object[] param) {var obj = m_ObjectPool.CreateObj(param);return obj;}public bool Release(GameObject obj) {return m_ObjectPool.Release(obj);}// --------------------------------------------------------------------------------// 解析可变长参数, 依次为: 父节点, 位置, 旋转 protected void ParseParams(out Transform parent, out Vector3 pos, out Quaternion quaternion, params object[] param) {parent = m_PoolParent;pos = Vector3.zero;quaternion = Quaternion.identity;if (param != null) {if (param.Length > 0)parent = (Transform)param[0];if (param.Length > 1)pos = (Vector3)param[1];if (param.Length > 2)quaternion = (Quaternion)param[2];}}//---------------------------------------------// 对象池接口, 直接使用持有的对象池接口private GameObject NewObj(params object[] param) {ParseParams(out var parent, out var pos, out var quaternion, param);var obj = m_NewObjFunc(parent, pos, quaternion);return obj;}// 产生之前初始化protected virtual void BeforeSpawn(GameObject obj, params object[] param) {ParseParams(out var parent, out var pos, out var quaternion, param);var transform = obj.transform;transform.parent = parent;transform.position = pos;transform.rotation = quaternion;m_BeforeSpawnAction?.Invoke(obj, param);}// 回收后挂载不可见节点protected virtual void AfterRecycle(GameObject obj, params object[] param) {obj.transform.parent = m_PoolParent;m_AfterRecycleAction?.Invoke(obj, param);}// 销毁对象protected virtual void AfterRelease(GameObject obj, params object[] param) {Object.Destroy(obj);m_AfterReleaseAction?.Invoke(obj, param);}//---------------------------------------------
}

代码比较简单, 这里不再赘述.

PrefabObjectPool

有了游戏对象的对象池之后, 我们就可以利用它实现预制对象的对象池, 大部分情况下, 我们使用的是预制对象的对象池.

预制是一个十分特殊的存在, 它本身就只是一个可以持久化的游戏对象而已, 但是它可以挂载各种组件, 可以容纳各种其它游戏对象, 这可以极大的简化了我们对各种游戏对象的维护, 基本上大部分场合下, 我们只需要关注预制和其实例化出来的对象就可以了.

这里我们使用继承的方式来实现, 因为预制对象本身就是一种游戏对象, 稍微有所区别的只是构造对象的时候是从预制实例化而已.

public class PrefabObjectPool : GameObjectPool {private GameObject m_Prefab;public PrefabObjectPool(GameObject prefab, Transform poolParent, int initCapacity = 5, int maxCount = 500, int onceReleaseCount = 10) {Assert.IsNotNull(prefab, "预制不能为空!");Assert.IsNotNull(poolParent, "挂载节点不能为空!");m_Prefab = prefab;InitPool(NewObj, poolParent, initCapacity, maxCount, onceReleaseCount);}// --------------------------------------------------------------------------------private GameObject NewObj(params object[] param) {ParseParams(out var parent, out var pos, out var quaternion, param);var obj = Object.Instantiate(m_Prefab, pos, quaternion, parent);return obj;}
}

注意在GameObjectPool中, 我们提供了一个默认构造函数, 并将持有的普通的对象池的实例化和初始化封装为初始化方法, 所以在实现PrefabObjectPool就变的很简单了.

PrefabObjectPool接受一个预制对象, 并重写创建对象方法, 在新建对象时从预制实例化.

有了以上的内容, 我们就可以正常使用了.

使用示例

public class ObjectPoolTest : MonoBehaviour {public Button spawnButton;public Button recycleButton;private GameObject m_PrefabPool;private GameObjectPool m_Pool;private Dictionary<int, GameObject> m_Objects = new Dictionary<int, GameObject>();private void Awake() {// ObjectPoolTest2.Test();var parentTrans = new GameObject("parentTrans");var prefabPool = new GameObject("PrefabPool");m_PrefabPool = prefabPool;var asset = AssetDatabase.LoadAssetAtPath<GameObject>("obj.prefab");var objectPool = new PrefabObjectPool(asset, prefabPool.transform, 10, 10, 2);// var objectPool = new GameObjectPool(s => {//     var obj = Instantiate(asset);//     return obj;// }, prefabPool.transform, 10, 10, 2);m_Pool = objectPool;objectPool.afterReleaseAction = OnObjectRelease;if (spawnButton != null) {var count = 0;spawnButton.onClick.AddListener(() => {var obj = m_Pool.Spawn(parentTrans.transform);m_Objects.Add(obj.GetHashCode(), obj);obj.name = "Obj: " + count++;});}if (recycleButton != null) {recycleButton.onClick.AddListener(() => {var obj = m_Objects.FirstOrDefault();if (obj.Value != null) {m_Pool.Recycle(obj.Value);m_Objects.Remove(obj.Key);}});}}private void OnObjectRelease(GameObject obj, params object[] param) {m_Objects.Remove(obj.GetHashCode());}private void OnDestroy() {if (m_PrefabPool != null) {Destroy(m_PrefabPool);m_PrefabPool = null;}m_Pool.Dispose();}
}

逻辑很简单, 点击Spawn生成对象, 并使用, 点击Recycle回收对象.


总结

今天介绍了游戏对象的对象池, 后续还可以实现对象池的管理器, 用来管理多个对象池, 在最后整套的资源管理方案部分会再次介绍.

原谅作者今天贴了这么多代码, 其实对象池的基本理论在前面两篇文章已经介绍的很详细了, 本篇文章更多的是在基建之上的应用而已.

对象池的核心概念很简单, 要用好却不容易, 我们在资源管理中也会大量用到对象池技术, 慢慢大家就会熟悉了.

下一篇文章会介绍另一个常用的技术: 引用计数. 相信很多同学都接触过, 作者会尝试将其基本理论梳理清楚, 并给出其应用, 希望感兴趣的同学继续关注.

好了, 今天的内容就这么多, 希望对大家有所帮助.

Unity中的资源管理-对象池技术(3)相关推荐

  1. Unity对象池技术

    Unity对象池技术 https://blog.csdn.net/LemonXQ/article/details/77148886 顾名思义,对象池是存放对象的缓冲区.用户可以从缓冲区中放入/取出对象 ...

  2. Unity中的资源管理-引用计数

    本文分享Unity中的资源管理-引用计数 在前面的文章中, 我们一起学习了对象池的基本原理和几种实现, 今天和大家继续聊聊另一个资源管理中比较重要的技术: 引用计数. GC的基础知识 GC(Garba ...

  3. 面试官问:对象池技术了解吗?apache common pool2呢?

    欢迎关注方志朋的博客,回复"666"获面试宝典 对象池顾名思义就是存放对象的池,与我们常听到的线程池.数据库连接池.http连接池等一样,都是典型的池化设计思想. 对象池的优点就是 ...

  4. java对象工厂池_[转载]Java对象池技术的原理及其实现

    作者:汪永好 出处:计算机与信息技术 责任编辑: 方舟 [ 2006-11-25 07:00 ] 摘 要 :本文在分析对象池技术基本原理的基础上,给出了对象池技术的两种实现方式.还指出了使用对象池技术 ...

  5. java 对象池 博客_Java对象池技术的原理及其实现的小结

    一起学习 Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除.因此,对象的生命周期长度可用如下的表达式表示:T = T1 T2 T3.其中T1表示对象的创建时间,T2表示对象的 ...

  6. java 对象池 实现_Java对象池技术的原理及其实现

    看到一片有关于java 对象基础知识,故转载一下,同时学习一下. 摘 要 本文在分析对象池技术基本原理的基础上,给出了对象池技术的两种实现方式.还指出了使用对象池技术时所应注意的问题. 关键词 对象池 ...

  7. Unity中的资源管理-几种常见的序列化方式

    本文分享Unity中的资源管理-几种常见的序列化方式 在网游客户端的开发中, 大部分数据只需要从服务器获取数据之后存放在内存中, 但是仍然有一些数据需要做序列化, 并持久化存放在客户端本地. 比如用户 ...

  8. Unity程序优化之对象池

    对于做程序的朋友来说优化是一个避免不了的话题,对于程序的优化可以从两个方面来入手,一个是CPU方面的优化,一个是GPU的优化.对于CPU与GPU的作用简单理解的话你可以暂时当做CUP是处理计算的.GP ...

  9. Unity中的资源管理-AssetBundle(1)

    本文分享Unity中的资源管理-AssetBundle(1) 在上一篇文章中, 我们简单介绍了Unity中的资源和基本的使用, 今天我们详细介绍下使用AssetBundle来管理资源. AssetBu ...

最新文章

  1. ​基于BCI的现代神经反馈有助于认知增强
  2. ADO.NET Entity Data Model入门实例
  3. oracle中的查询语句(关于出库入库信息表,明细表,把捆包箱表,单位信息表的集中查询)...
  4. 移动平台MOBA发热与帧率优化
  5. GDCM:从ELSCINT1读取Wave Information标签的测试程序
  6. Excel隐藏的数据处理技巧
  7. Generator 函数的异步应用
  8. mvcc原理_Mysql MVCC实现原理
  9. Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析
  10. 数据科学家:我的工作就是发现别人未知的
  11. 国军标GJB150三防试验(霉菌+盐雾+湿热)
  12. 用友U8 ERP系统材料出库单打印格式设置-表格格式调整
  13. mac更新完后读取不出移动硬盘的问题解决
  14. ppt背景图片php,求ppt背景,ppt背景图片,漂亮一些的。
  15. 软件需求分类与需求获取
  16. html ico在线制作,10款免费图标生成器网页设计师
  17. python怎么群发邮件_小工具:使用python群发邮件
  18. SSH免密失败并报错:no mutual signature algorithm
  19. 离散数学——Euler 图与 Hamilton 图
  20. 任务分配的穷举法、匈牙利法、分支定界法

热门文章

  1. 命令行 调节 mac m1 鼠标 灵敏度
  2. 餐饮到底怎么做才能提升利润?
  3. 随机种子、torch.backends.cudnn.benchmark\.deterministic
  4. CSS的动画案例——西游记动画
  5. 深圳朋友查找自己社保明细
  6. 流量明星翻车的“直播卖房”,为什么众盟做成了?
  7. VL02N 交货单过账科目、成本中心、获利段替代
  8. 长尾理论不是颠覆性的概念
  9. 基于Springboot+vue的化妆品美妆销售商城网站 elementui
  10. 类似滴滴打车模式搬家预约抢单系统开发