UF_OLDL2——缓存池模块
缓存池
为何要用缓存池
节选自:Unity —— 缓存池简单理解 - 知乎 (zhihu.com)
来理解缓存池之前,我们先了解一下C#的内存回收机制:
每次实例化一个对象(在场景上创建一个对象),都会分配一个内存空间;
当这个对象被删除时,仅仅时断开了对这片空间的引用,此内存空间并没有被释放掉。再次创建对象时,会继续分配其他的内存空间,直到内存被全部被分配满。
当内存满了再回过头看有哪些是不用的"垃圾",再回收释放。
这样的一次释放,叫做 “一次GC”。
所谓垃圾,就是没有被任何变量、对象引用的内容。
通过是否被引用来确定哪些对象是"垃圾"。
正是因为每次GC需要经过大量的计算来判断是否需要回收,对CPU的消耗较大,
所以每次GC都可能会造成卡顿,GC次数一旦多了会严重影响玩家的使用体验,由此出现了缓存池。
在以前我们不使用缓存池时,例如子弹,我们一旦发射出去,打中什么了之后,就不再使用它,就会将其直接销毁
然而实际上,这些子弹对象仍然占用着内存空间,只是没有被引用
而这种对象的多次创建,内存空间被占满,这些没有被引用的对象内存空间将会触发GC而被回收释放,但是GC本身可能会造成卡顿!
随着游戏进行,如果不做应对,GC将会被频繁触发,进而引发卡顿,影响玩家体验
为了应对这种情况,我们就需要使用缓存池让类似于子弹这种一次性触发的对象可以在内存里被回收利用,减少GC
何为缓存池
打个比方,缓存池就像一个衣柜,把对象比作衣服,制作衣服比作实例化对象,销毁当作扔进垃圾桶,触发GC当作垃圾桶满了清理垃圾桶
刚开始时,这个衣柜没有任何衣服,
这时我们仍然需要制作衣服来获取需要的衣服,
是当这个衣服用完后,我们不再将其直接扔进垃圾桶,而是将其放入衣柜
当我们再需要这个衣服时,我们会先在衣柜里寻找有没有这个衣服
发现有,我们就直接穿衣柜里的衣服,而不是制作它
这样,我们解决了重复穿某件衣服时,需要重新制作衣服
以及垃圾桶被扔满了后要去清理垃圾的问题
当然,我们的衣服不可能只有一种类型(就像发射出去的子弹对象会有发射特效,子弹本体,击中特效)
所以,我们的衣柜需要一个个的抽屉,分门别类的放不同的衣服(对象按照不同的类,分门别类的集中缓存他们)
以上即缓存池的理论原理,接下来是代码实现
缓存池模块基础
知识点:字典,列表,GameObject和Resources的API
作用:当对象不需要时,直接传入缓存池,需要时在取出使用即可
使用方法:
当用完某对象时,使用PushObj()将其存储到缓存池并失活它,
而当需要使用某对象时,使用GetObj()方法获取并激活它
注意:传入的名字需要为该对象在Resource路径下的名字,才能保证实例化它,如果想要对象在被取出时做什么,请在OnEnable()内写逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| using System.Collections.Generic; using UnityEngine;
public class PoolManager : BaseManager<PoolManager> { public Dictionary<string, List<GameObject>> poolDic = new Dictionary<string, List<GameObject>>();
public GameObject GetObj(string name) { GameObject obj = null; if (poolDic.ContainsKey(name) && poolDic[name].Count > 0) { obj = poolDic[name][0]; poolDic[name].RemoveAt(0); } else { obj = GameObject.Instantiate(Resources.Load<GameObject>(name)); obj.name = name; } obj.SetActive(true); return obj; }
public void PushObj(string name, GameObject obj) { obj.SetActive(false); if (poolDic.ContainsKey(name)) { poolDic[name].Add(obj); } else { poolDic.Add(name, new List<GameObject>() { obj }); } } }
|
缓存池模块优化
上一个缓存池存在对象全部暴露在层级最外层的问题,这样会影响我们的Hierarchy窗口的观看,而且也不容易知道哪些对象是缓存池内的对象
以及,当切换场景时,场景上的对象都会被删除,这时缓存池存储对象就没有意义且会出错,应当清除存储池的所有对象
接下来的优化,会使我们的缓存池下使用若干个存储容器,分别存储不同类型的对象,
且在Hierarchy窗口下可以看到不同类型的对象依附在相应名字的空对象下
并且提供清空缓存池的方法
使用方法:
取出对象,传入该对象的Resources资源路径,即可获取到名字为传入的路径的对象
存入对象,填入该对象和对象的名字(直接填自己的名字即可),即可存储该对象
清空缓存池,直接Clear()即可,用于切换场景用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| using System.Collections.Generic; using UnityEngine;
public class PoolManager : BaseManager<PoolManager> { public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>(); private GameObject poolObj;
public GameObject GetObj(string name) { GameObject obj = null; if (poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0) { obj = poolDic[name].GetObj(); } else { obj = GameObject.Instantiate(Resources.Load<GameObject>(name)); obj.name = name; } return obj; }
public void PushObj(string name, GameObject obj) { if (poolObj == null) poolObj = new GameObject("Pool");
if (poolDic.ContainsKey(name)) { poolDic[name].PushObj(obj); } else { poolDic.Add(name, new PoolData(obj, poolObj)); } }
public void Clear() { poolDic.Clear(); poolObj = null; } }
public class PoolData { public GameObject fatherObj; public List<GameObject> poolList; public PoolData(GameObject obj, GameObject poolObj) { fatherObj = new GameObject(obj.name); fatherObj.transform.parent = poolObj.transform; poolList = new List<GameObject>(); PushObj(obj); }
public void PushObj(GameObject obj) { obj.SetActive(false); poolList.Add(obj); obj.transform.parent = fatherObj.transform; } public GameObject GetObj() { GameObject obj = null; obj = poolList[0]; poolList.RemoveAt(0); obj.transform.parent = null; obj.SetActive(true); return obj; } }
|
运用资源异步加载的缓存池模块优化
通过资源加载模块异步加载的方法,我们可以优化在缓存池不存在某资源时实例化资源,通过异步加载来提升性能
使用方法:
相比上面的缓存池模块,这次的优化重载了一个获取资源的方法,它使用加载资源模块异步加载资源
传入的参数变为:传入该对象的Resources资源路径,有参数的回调函数,
回调函数在加载完成后执行,执行时参数为读取到的游戏对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
| using System.Collections.Generic; using UnityEngine; using UnityEngine.Events;
public class PoolManager : BaseManager<PoolManager> { public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>(); private GameObject poolObj;
public GameObject GetObj(string name) { GameObject obj = null; if (poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0) { obj = poolDic[name].GetObj(); } else { obj = GameObject.Instantiate(Resources.Load<GameObject>(name)); obj.name = name; } return obj; }
public void GetObj(string name, UnityAction<GameObject> callback) { if (poolDic.ContainsKey(name) && poolDic[name].poolList.Count > 0) { callback(poolDic[name].GetObj()); } else { ResourcesManager.Instance().LoadAsync<GameObject>(name, (o) => { o.name = name; callback(o); }); } }
public void PushObj(string name, GameObject obj) { if (poolObj == null) poolObj = new GameObject("Pool");
if (poolDic.ContainsKey(name)) { poolDic[name].PushObj(obj); } else { poolDic.Add(name, new PoolData(obj, poolObj)); } }
public void Clear() { poolDic.Clear(); poolObj = null; } }
public class PoolData { public GameObject fatherObj; public List<GameObject> poolList; public PoolData(GameObject obj, GameObject poolObj) { fatherObj = new GameObject(obj.name); fatherObj.transform.parent = poolObj.transform; poolList = new List<GameObject>(); PushObj(obj); }
public void PushObj(GameObject obj) { obj.SetActive(false); poolList.Add(obj); obj.transform.parent = fatherObj.transform; } public GameObject GetObj() { GameObject obj = null; obj = poolList[0]; poolList.RemoveAt(0); obj.transform.parent = null; obj.SetActive(true); return obj; } }
|