UFL3-4——缓存池增加缓存对象种类

缓存池模块(数据结构类、逻辑类)优化 主要目的

我们目前实现的缓存池,主要是针对场景上的GameObject​对象的,我们只能对场景上的对象进行缓存池功能处理

但是在游戏开发中,还会存在大量的不需要挂载到场景上的实例化对象
比如一些数据结构类、逻辑类,它们并不依附于场景上的对象,而仅仅是被引用

举例:
一个自定义数据结构类,TestData t = new TestData();
当我们不使用它时,往往会将其置空,t = null;
下次又要使用时,再new​,t = new TestData();

那么对于一些频繁使用的数据结构类或逻辑类,这样做也会产生大量的垃圾
因此我们完全可以修改缓存池模块,让其也支持对不挂载的类对象也进行复用

说人话:
缓存池模块(数据结构类、逻辑类)优化 主要目的是让缓存池支持回收复用不继承MonoBehaviour​(不挂载GameObject​)的类对象

缓存池模块(数据结构类、逻辑类)优化 制作思路

关键点:

  1. 池子容器父类里式替换父类装子类

    1
    2
    3
    4
    /// <summary>
    /// 各个泛型容器类的基类,用于存储到字典内
    /// </summary>
    public abstract class PoolObjectBase { }
  2. 池子容器子类泛型类确定对象类型

    1
    2
    3
    4
    5
    6
    7
    8
    /// <summary>
    /// 用于存储 数据结构类 和 逻辑类 的 容器类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PoolObject<T> : PoolObjectBase where T : class
    {
    public Queue<T> poolObjs = new Queue<T>();
    }
  3. 对象父接口,用于实现重置数据方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /// <summary>
    /// 想要被复用的数据结构类、逻辑类、都必须要继承该接口
    /// </summary>
    public interface IPoolObject
    {
    /// <summary>
    /// 重置数据的方法
    /// </summary>
    void ResetInfo();
    }

注意:由于这些自定义数据或逻辑类中可能有对其他内容的引用,因此需要让其有一个重置数据的方法,在压入池子时重置数据

实现思路:

  1. 修改缓存池管理器 PoolManager

  2. 添加一个新池子容器,专门用来记录不继承 MonoBehaviour​(不挂载GameObject​)的类对象

    1
    2
    //用于存储数据结构类,逻辑类对象的池子的字典容器
    private Dictionary<string, PoolObjectBase> poolObjectDic = new Dictionary<string, PoolObjectBase>();
  3. 提供专门的方法供外部使用

    1. 从池子中获取对象的方法

      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
      /// <summary>
      /// 获取自定义的数据结构类 和 逻辑类对象 放入到池子中 (不继承Mono的)
      /// </summary>
      /// <typeparam name="T">数据类型</typeparam>
      /// <returns>从池子内获取或新实例化的对象</returns>
      public T GetObj<T>(string nameSpace = "") where T : class, IPoolObject, new()
      {
      //池子的名字就是根据类的类名和命名空间名决定的
      string poolName = nameSpace + "_" + typeof(T).Name;
      //有池子
      if (poolObjectDic.ContainsKey(poolName))
      {
      PoolObject<T> pool = poolObjectDic[poolName] as PoolObject<T>;
      //池子当中是否存在可复用的内容
      if (pool.poolObjs.Count > 0)
      {
      T obj = pool.poolObjs.Dequeue();
      return obj;
      }
      else
      {
      //实例化,该类型必须要有一个无参构造函数
      T obj = new T();
      return obj;
      }
      }
      //没有池子
      else
      {
      //实例化,该类型必须要有一个无参构造函数
      T obj = new T();
      return obj;
      }
      }
    2. 压入池子中的方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public void PushObj<T>(T obj, string nameSpace = "") where T : class, IPoolObject
      {
      if (obj == null)
      return;
      //池子的名字就是根据类的类名和命名空间名决定的
      string poolName = nameSpace + "_" + typeof(T).Name;
      PoolObject<T> pool;
      //有池子
      if (poolObjectDic.ContainsKey(poolName))
      {
      pool = poolObjectDic[poolName] as PoolObject<T>;
      }
      //无池子
      else
      {
      pool = new PoolObject<T>();
      poolObjectDic.Add(poolName, pool);
      }
      obj.ResetInfo();
      pool.poolObjs.Enqueue(obj);
      }
    3. 清空池子容器的方法

      直接修改原来的方法即可

      1
      2
      3
      4
      5
      6
      7
      8
      9
      /// <summary>
      /// 清除整个柜子当中的数据
      /// </summary>
      public void ClearPool()
      {
      poolDic.Clear();
      poolObjectDic.Clear();
      poolObj = null;
      }

使用示例

声明一个由缓存池管理的类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestData : IPoolObject
{
public int i;
public string str;
public object obj;

public void ResetInfo()
{
i = 0;
str = "";
obj = null;
}
}

对缓存池特定位置打断点

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main : MonoBehaviour
{
private TestData data;

void Update()
{
if (Input.GetKeyDown(KeyCode.G))
{
data = PoolManager.Instance.GetObj<TestData>();
data.i = 11;
data.obj = new object();
data.str = "123";
}

if (Input.GetKeyDown(KeyCode.P))
{
PoolManager.Instance.PushObj<TestData>(data);
data = null;
}
}
}

通过缓存池获取对象,再通过将对象压入到缓存池内,通过断点调试,观察代码执行顺序,以及缓存池内对象数量变化

具体代码

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolData
{
//抽屉根对象,用来就行布局管理的对象
private GameObject poolObj;
//用来存储抽屉中的对象,记录没有使用的对象
private Stack<GameObject> dataStack = new Stack<GameObject>();
//抽屉上限,场景上同时存在的对象的上限个数
private int maxNum;
//记录使用中的对象的列表
private List<GameObject> usedList = new List<GameObject>();
//获取容器中是否有对象
public int Count => dataStack.Count;

public int UsedCount => usedList.Count;

/// <summary>
/// 与使用中的对象数量与最大容量进行比较,小于返回true,需要实例化
/// </summary>
public bool NeedCreate => usedList.Count < maxNum;

public PoolData(GameObject poolRootObj, string poolName, GameObject usedObj)
{
//开启功能时,才会动态创建父子关系
if (PoolManager.isOpenLayout)
{
//创建抽屉父对象,和柜子父对象建立父子关系
poolObj = new GameObject(poolName);
poolObj.transform.SetParent(poolRootObj.transform);
}
//创建抽屉时,外部肯定是会动态创建一个对象的,我们应该将其记录到使用中的对象容器中
PushUsedList(usedObj);
PoolObj obj = usedObj.GetComponent<PoolObj>();
if (obj == null)
{
Debug.LogError("请为缓存池功能的预设体对象挂载PoolObj脚本,用于设置数量上限");
return;
}
//记录上限数量值
this.maxNum = obj.maxNum;
}

/// <summary>
/// 从抽屉中弹出数据对象
/// </summary>
/// <returns>弹出的数据对象</returns>
public GameObject Pop()
{
//从抽屉内取出并激活对象
GameObject obj;
if (Count > 0)
{
obj = dataStack.Pop();
usedList.Add(obj); //将取出的对象压入到使用中的列表内
}
else
{
obj = usedList[0]; //取0索引的对象,它就是使用时间最长的对象
usedList.RemoveAt(0); //并将其从使用中的列表中移除出去
usedList.Add(obj); //由于该对象还需要使用,所以将其移到列表的最尾部使用
}

obj.SetActive(true);
//开启布局功能时,才需要断开父子关系
if (PoolManager.isOpenLayout)
obj.transform.SetParent(null);
return obj;
}

/// <summary>
/// 将数据压入到抽屉内
/// </summary>
/// <param name="obj">压入到抽屉内</param>
public void Push(GameObject obj)
{
//失活后压入到抽屉内
obj.SetActive(false);
//开启了布局功能,才需要将要压入的对象设置父子关系
if (PoolManager.isOpenLayout)
obj.transform.SetParent(poolObj.transform);
dataStack.Push(obj);
//这个对象不再使用了,因此需要从记录容器中移除出去
usedList.Remove(obj);
}

/// <summary>
/// 将对象添加到使用中的列表
/// </summary>
/// <param name="obj">要添加到使用中列表的对象</param>
public void PushUsedList(GameObject obj)
{
usedList.Add(obj);
}
}

/// <summary>
/// 各个泛型容器类的基类,用于存储到字典内
/// </summary>
public abstract class PoolObjectBase { }

/// <summary>
/// 用于存储 数据结构类 和 逻辑类 的 容器类
/// </summary>
/// <typeparam name="T"></typeparam>
public class PoolObject<T> : PoolObjectBase where T : class
{
public Queue<T> poolObjs = new Queue<T>();
}

/// <summary>
/// 想要被复用的数据结构类、逻辑类、都必须要继承该接口
/// </summary>
public interface IPoolObject
{
/// <summary>
/// 重置数据的方法
/// </summary>
void ResetInfo();
}

/// <summary>
/// 缓存池模块 管理器
/// </summary>
public class PoolManager : BaseManager<PoolManager>
{
//柜子容器当中有抽屉的体现
private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
//用于存储数据结构类,逻辑类对象的池子的字典容器
private Dictionary<string, PoolObjectBase> poolObjectDic = new Dictionary<string, PoolObjectBase>();

//池子根对象
private GameObject poolObj;

//是否开启Hierarchy窗口自动布局功能
public static bool isOpenLayout = true;

private PoolManager() { }

/// <summary>
/// 拿东西的方法
/// </summary>
/// <param name="name">抽屉容器的名字</param>
/// <returns>从缓存池中取出的对象</returns>
public GameObject GetObj(string name)
{
//如果根物体为空且自动排布开启,就创建
if (poolObj == null && isOpenLayout)
poolObj = new GameObject("Pool");

GameObject obj;
//无抽屉,或者抽屉无对象且使用中的对象也未超上限,需要新实例化一个对象
if (!poolDic.ContainsKey(name) ||
(poolDic[name].Count == 0 && poolDic[name].NeedCreate))
{
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
obj.name = name;
//无抽屉时,创建一个抽屉并将新对象传入到构造函数内
if (!poolDic.ContainsKey(name))
poolDic.Add(name, new PoolData(poolObj, name, obj));
//有抽屉时,将新创建的对象传入到使用中的列表内
else
poolDic[name].PushUsedList(obj);
}
//当抽屉有对象或者使用中的对象超上限了,就直接从抽屉内取出来使用
else // if (poolDic[name].Count > 0 || poolDic[name].UsedCount >= maxNum)
{
obj = poolDic[name].Pop();
}
return obj;
}

/// <summary>
/// 获取自定义的数据结构类 和 逻辑类对象 放入到池子中 (不继承Mono的)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <returns>从池子内获取或新实例化的对象</returns>
public T GetObj<T>(string nameSpace = "") where T : class, IPoolObject, new()
{
//池子的名字就是根据类的类名和命名空间名决定的
string poolName = nameSpace + "_" + typeof(T).Name;
//有池子
if (poolObjectDic.ContainsKey(poolName))
{
PoolObject<T> pool = poolObjectDic[poolName] as PoolObject<T>;
//池子当中是否存在可复用的内容
if (pool.poolObjs.Count > 0)
{
T obj = pool.poolObjs.Dequeue();
return obj;
}
else
{
//实例化,该类型必须要有一个无参构造函数
T obj = new T();
return obj;
}
}
//没有池子
else
{
//实例化,该类型必须要有一个无参构造函数
T obj = new T();
return obj;
}
}

/// <summary>
/// 往缓存池中放入对象
/// </summary>
/// <param name="name">抽屉(对象池)的名字</param>
/// <param name="obj">要放入的对象</param>
public void PushObj(GameObject obj)
{
////如果根物体为空且自动排布开启,就创建
//if (poolObj == null && isOpenLayout)
// poolObj = new GameObject("Pool");

poolDic[obj.name].Push(obj);
}

public void PushObj<T>(T obj, string nameSpace = "") where T : class, IPoolObject
{
if (obj == null)
return;
//池子的名字就是根据类的类名和命名空间名决定的
string poolName = nameSpace + "_" + typeof(T).Name;
PoolObject<T> pool;
//有池子
if (poolObjectDic.ContainsKey(poolName))
{
pool = poolObjectDic[poolName] as PoolObject<T>;
}
//无池子
else
{
pool = new PoolObject<T>();
poolObjectDic.Add(poolName, pool);
}
obj.ResetInfo();
pool.poolObjs.Enqueue(obj);
}

/// <summary>
/// 清除整个柜子当中的数据
/// </summary>
public void ClearPool()
{
poolDic.Clear();
poolObjectDic.Clear();
poolObj = null;
}
}