BUAA-Soft-2024-Summer/Soft-Summer-2024

Unity如何配置和使用一个对象池?

Closed this issue · 0 comments

考虑到这次游戏开发里有许多东方众,那么在开发中肯定少不了子弹的大量实例化和使用。

最简单的处理方法如下,每次想要使用的时候

var bullet = Instantiate(bulletPrefab);

也就是每次都根据预制件实例化一个新的子弹。

但!是!

频繁的新建和Destroy实际上是对系统资源的一种浪费,如果是少量的物体,可能无伤大雅。不过如果是像弹幕类型的全屏大量实例化,会给GC系统带来不小的压力(甚至有可能卡飞!)

其实我们发现很多子弹在使用之后没必要直接Destroy,可以收集起来之后继续使用!这种**就是对象池,幸运的是,Unity为我们内置了一个好用的对象池ObjectPool<>,因此我们只需要简单地使用!

下面是一个简单的示例:

public class PoolingTest : MonoBehaviour
{
    // 加上序列化字段的特性,让unity可以拖动赋值
    [SerializeField] private GameObject bulletPrefab;
    private ObjectPool<GameObject> objectPool;

    private void Start()
    {
        this.objectPool = new ObjectPool<GameObject>(
            () => Instantiate(bulletPrefab),
            obj => obj.SetActive(true),
            obj => {
                obj.SetActive(false);
                obj.transform.SetParent(this.transform);
            },
            obj => Destroy(obj),
            defaultCapacity: 20,
            maxSize: 10000
        );
    }

    private void Action() 
    {
        // 获取一个子弹实例
        var bullet = objectPool.Get();

        // 让子弹的MonoBehaviour开始运行 ^-^
        // ...

        // 当你不需要它的时候……
        objectPool.Release(bullet);
    }

    private void OnDestroy() 
    {
        objectPool.Dispose();    
    }
}

在这个示例中,我们新建了一个objectPool,里面传了好几个lambda表达式,他们是干什么的呢?

() => Instantiate(bulletPrefab)

告诉了对象池应该怎么获取新的实例,如果不够的话就找这个函数要!

obj => obj.SetActive(true)

告诉了对象池当取出对象的时候,应当将其设置为active

obj => {
    obj.SetActive(false);
    obj.transform.SetParent(this.transform);
},

告诉了对象池当收回对象的时候,将其置为inactive,并且把它变成自己的子对象。
为什么要变成自己的子对象呢?

因为用户可能在外部将对象放在了一个可能切换掉的场景里,如果场景被切换之后,这个游戏对象又被Release回去,而在unity看来,被摧毁的游戏对象和null没有区别(因此unity对象不可用?来判断空),会引发异常!qwq

所以尽量把它作为自己的子对象,让对象和对象池共存亡

obj => Destroy(obj),

告诉了当摧毁对象池时应该做什么,我们可以直接销毁所有的实例

而后面两个参数,聪明如你应该能看出来啦!它表示对象池的初始容量和最大容量

现在,我们就可以使用对象池来获取游戏对象和回收游戏对象,避免了大量不必要的内存申请和GC操作~ ^w^