先举一个射击游戏例子:

玩家不断的射击,不断的产生射击特效,弹痕,子弹,弹壳等各种物体,这些物体一段时间后自动销毁,但unity Destory后并没有释放其内存,不断堆积后内存爆满,GC频繁,游戏性能降低,所以我们需要降低GC的频率,因此就使用了缓存池来保留这些马上就会复用的资源,而不是直接销毁,GC频率降低,游戏体验提升。

不严谨的再举个栗子:

内存容量为50,每秒射击一次产生的各种物体要占用内存1单位,1s后其物体自动销毁或保存到缓存池里,我们计算220s内,每秒只射击一次的GC次数(只计算因为堆内存满后的GC)

  • 不使用缓存池:GC次数大概为4次
  • 使用缓存池 :GC次数为0次

好处就很显然了,一句话概括就是用内存换取游戏性能。


上面引用了缓存池的概念,那么缓存池是什么?

缓存池是什么

一个机制一个模块,管他娘的是什么🤡🤡🤡🤡。知道怎么用,为什么用后,自己就可以用自己的话去形容它了。

我的小偷理解就是:缓存池就是衣柜,想穿就往里面拿,里面没有就去买,买了直接穿,穿完再放回,衣柜还得有个容量,上到无穷大,衣柜炸了别找我,下到啥都不准放,那你还要柜子干啥,你觉得多大就是多大,不要我觉得要你觉得,衣柜满了就丢一些不要的衣服,想丢什么丢什么,最好先把要穿的衣服都准备好,别到时候要穿了还没有

大概就是这些。

搭建缓存池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//预加载(准备好要穿的衣服)
//取物体(取衣服)
//存物体(放衣服)
//清空
private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>();
字典就是衣柜
字典值就时衣柜里的抽屉,用来区分不同的衣服
字典键就时不同抽屉的标签,这里直接用存放的物体名字来区分
-------
容量是为了限制缓存池所占的内存大小
//字典容量
private int? poolCapacity=null;
//队列容量
private int? queueCapacity = null;

功能如上,具体用什么数据结构储存看自己的需求

  • 预加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>
/// 预加载(放好要穿的衣服)
/// 一般在场景加载时预加载
/// </summary>
/// <param name="name">物体名</param>
/// <param name="preNum">预加载数量</param>
public void PreLoad(string name,int preNum)
{
Queue<GameObject> gameObjects = new Queue<GameObject>();
for(int i=0;i<preNum;i++)
{
gameObjects.Enqueue(Resources.Load<GameObject>(name));
}
}
  • 取物体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /// <summary>
    /// 取衣服
    /// </summary>
    /// <param name="name">既是路径又是名字</param>
    /// <returns></returns>
    public GameObject GetObject(string name)
    {
    GameObject obj = null;
    if(objectPool.ContainsKey(name)&&objectPool[name].Count>0)
    {
    obj = objectPool[name].Dequeue();
    obj.SetActive(true);
    }
    else
    {
    obj = GameObject.Instantiate( Resources.Load<GameObject>(name));
    obj.name = name;
    }
    return obj;
    }
  • 存物体

    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
    /// <summary>
    /// 存衣服
    /// </summary>
    /// <param name="name">名字</param>
    /// <param name="obj">要存入的衣服</param>
    public void ReturnObject(string name,GameObject obj)
    {
    obj.SetActive(false);
    //衣柜是否有这个抽屉
    if (objectPool.ContainsKey(name))
    {
    //抽屉是否有空间若无删除
    if (objectPool[name].Drawer.Count < queueCapacity)
    objectPool[name].ReturnObject(obj);
    else
    GameObject.Destroy(obj);
    }
    //没有该抽屉
    //衣柜没有多余空间直接删除
    else if(objectPool.Count>=poolCapacity)
    {
    GameObject.Destroy(obj);
    }
    //衣柜还有空间创造新的抽屉储存
    else
    {
    Queue<GameObject> objQueue = new Queue<GameObject>();
    objQueue.Enqueue(obj);
    objectPool.Add(name, objQueue);
    }
    }
  • 清空

    1
    2
    3
    4
    5
    6
    7
    /// <summary>
    /// 清空
    /// </summary>
    public void Clear()
    {
    objectPool.Clear();
    }

效果如图

图1

但还不满意,如果需要缓存的对象很多,则这样在Hierarchy里就会显得非常的乱,那就别叫缓存池了,就叫做茅厕,poo pee fart混杂,所以需要将缓存池在Hierarchy具象出来,同时把抽屉抽象成一个类,设置父子对象,让缓存池在Hierarchy结构更加清晰。

效果如下

图2

完整代码

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 抽屉
/// </summary>
public class Drawers
{
private GameObject father;
private Queue<GameObject> drawer;
public Queue<GameObject> Drawer { get { return drawer; } }
public Drawers(GameObject obj,Transform wardrobe)
{
father = new GameObject(obj.name+"'s"+"屉子");
father.transform.SetParent(wardrobe);
obj.transform.SetParent(father.transform);
drawer = new Queue<GameObject>() ;
drawer.Enqueue(obj);
}
/// <summary>
/// 取衣服
/// </summary>
/// <returns></returns>
public GameObject GetObject()
{
drawer.Peek().SetActive(true);
return drawer.Dequeue();
}

/// <summary>
/// 存衣服
/// </summary>
/// <param name="obj">要存入的衣服</param>
public void ReturnObject(GameObject obj)
{
drawer.Enqueue(obj);
obj.transform.SetParent(father.transform);
}
}
/// <summary>
/// 衣柜
/// </summary>
public class Wardrobe : BasicManager<Wardrobe>
{
private Dictionary<string, Drawers> objectPool = new Dictionary<string,Drawers>();
public GameObject wardrobe;
//字典容量
private int? poolCapacity=10;
//队列容量
private int? queueCapacity = 50;

/// <summary>
/// 取衣服
/// </summary>
/// <param name="name">既是路径又是名字</param>
/// <returns></returns>
public GameObject GetObject(string name)
{
if(objectPool.ContainsKey(name)&&objectPool[name].Drawer.Count>0)
{
return objectPool[name].GetObject();
}
else
{
GameObject obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
obj.name = name;
return obj;
}
}

/// <summary>
/// 存衣服
/// </summary>
/// <param name="name">名字</param>
/// <param name="obj">要存入的衣服</param>
public void ReturnObject(string name,GameObject obj)
{
obj.SetActive(false);
if (wardrobe == null)
wardrobe = new GameObject("衣柜");
//衣柜是否有这个抽屉
if (objectPool.ContainsKey(name))
{
//抽屉是否有空间若无删除
if (objectPool[name].Drawer.Count < queueCapacity)
objectPool[name].ReturnObject(obj);
else
GameObject.Destroy(obj);
}
//没有该抽屉
//衣柜没有多余空间直接删除
else if(objectPool.Count>=poolCapacity)
{
GameObject.Destroy(obj);
}
//衣柜还有空间创造新的抽屉储存
else
{
Drawers draw = new Drawers(obj, wardrobe.transform);
objectPool.Add(name, draw);
}
}
/// <summary>
/// 预加载(放好要穿的衣服)
/// 场景加载时预加载最好
/// </summary>
/// <param name="name">物体名</param>
/// <param name="preNum">预加载数量</param>
public void PreLoad(string name,int preNum)
{
wardrobe = new GameObject("衣柜");
Queue<GameObject> gameObjects = new Queue<GameObject>();
for(int i=0;i<preNum;i++)
{
gameObjects.Enqueue(Resources.Load<GameObject>(name));
}
}

/// <summary>
/// 清空
/// </summary>
public void Clear()
{
objectPool.Clear();
GameObject.Destroy(wardrobe);
}
}