建议阅读缩放比例:150%

对象池是什么?

很明显,就是一个用来装对象的大池子,通常我们称拥有这种池子的人为海王(不是)

在游戏运行中,经常会出现大量重复的物体,我们需要不断的去生成和删除这些对象,但这么做会导致我们的内存空间产生碎片,使得存储空间不连续,进而导致触发 GC (垃圾回收机制),造成游戏卡顿。

为了避免这种情况,我们可以创建一个管理这些大量重复物体的管理中心,我们可以把这个管理中心看做是一个仓库,在仓库中一开始有已经有一些货物,但货物在仓库中,我们看不见,当我们需要用的时候,再从仓库中取出,使货物变得可见,等我们用完货物之后再还给仓库,再次变得不可见,而有的时候,仓库里的库存已经不能满足我们的需求了,这时我们需要仓库进货,进新的货物供我们使用,再返还时,仓库的库存可能太多了,塞不下,短时间内我们也不会用到这么多,仓库便会自己再处理掉货物。

这个仓库,就是所谓的对象池

很多时候,我们都需要是要对象池来对我们的游戏进行优化,以减少内存的负担,射击游戏中的子弹便是最常见的对象池使用案例,大量重复生成的子弹可以重复利用。

如何设计对象池?

容器的选择

为了创建对象池,我们首先需要一个字典,来让我们需要的对象和池子以键值对(key - value)的方式进行绑定,key 的类型取决于你的需要,value 的选择通常有两种方式,一个是使用队列(Queue),一个是使用列表(list),本文中使用队列方式。

Dictionary<GameObject, Queue<GameObject>>

从池中取出对象

ok,我们现在已经有了一个池子,接下来我们就可以获取对象了,使用队列的 Dequeue 方法,即便我们还没有放入对象,我们可以判定当库存为空时,就创建一个新的对象并返回。我这里因为获取对象需要初始化位置与方向信息,所以参数使用了 Vector3 和 Quaternion 。

public GameObject GetObject(GameObject prefab, Vector3 position, Quaternion rotation) {
    if (!pool.ContainsKey(prefab)) {
        pool.Add(prefab, new Queue<GameObject>());
    }

    if (pool[prefab].Count == 0) {
        GameObject instance = Object.Instantiate(prefab, position, rotation);
        return instance;
    }

    GameObject newObject = pool[prefab].Dequeue();
    newObject.SetActive(true);
    newObject.transform.position = position;
    newObject.transform.rotation = rotation;
    return newObject;
}

将对象放入池子

当我们需要放回对象时,使用队列的 Enqueue 方法,值得一提的是,最好设置一个仓库最大值,不然仓库可能会储存过多的对象

public void RecycleObject(GameObject prefab, GameObject gameObject) {
    if (pool[prefab].Count >= maxCount) {
        Object.Destroy(gameObject);
    } else {
        gameObject.SetActive(false);
        pool[prefab].Enqueue(gameObject);
    }
}

预加载对象

我们可以在池子中预加载好一定量的对象,以备之后使用

public class ObjectPool {
public int maxCount = 128;
public static ObjectPool instance = new ObjectPool();
public Dictionary<GameObject, Queue<GameObject>> pool = new Dictionary<GameObject, Queue<GameObject>>();

代码总体

// 从池中取出物体
public GameObject GetObject(GameObject prefab, Vector3 position, Quaternion rotation) {
    if (!pool.ContainsKey(prefab)) {
        pool.Add(prefab, new Queue<GameObject>());
    }

    if (pool[prefab].Count == 0) {
        GameObject instance = Object.Instantiate(prefab, position, rotation);
        return instance;
    }

    GameObject newObject = pool[prefab].Dequeue();
    newObject.SetActive(true);
    newObject.transform.position = position;
    newObject.transform.rotation = rotation;
    return newObject;
}

// 将物体放回池子
public void RecycleObject(GameObject prefab, GameObject gameObject) {
    if (pool[prefab].Count >= maxCount) {
        Object.Destroy(gameObject);
    } else {
        gameObject.SetActive(false);
        pool[prefab].Enqueue(gameObject);
    }
}

// 预加载物体
public void Init(GameObject prefab, int num) {
    if (!pool.ContainsKey(prefab)) {
        pool.Add(prefab, new Queue<GameObject>());
    }

    for (int i = 0; i < num; i++) {
        GameObject newObject = Object.Instantiate(prefab);
        newObject.SetActive(false);
        pool[prefab].Enqueue(newObject);
    }
}}