Menu
CoddyTech

Unity 的 ObjectPool

本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 48 课,共 64 课。

Unity 自带对象池,位于 UnityEngine.Pool 中的 ObjectPool<T>。你可以用四个 lambda 指定每个步骤要执行的操作,对象池会管理备用对象和数量。这个烟囱每 0.2 秒喷出一团烟雾,每团烟雾会在一秒后归还:

using UnityEngine;
using UnityEngine.Pool;

public class Chimney : MonoBehaviour
{
    public Puff puffPrefab;
    private ObjectPool<Puff> pool;
    private float timer;

    void Awake()
    {
        pool = new ObjectPool<Puff>(
            createFunc: () => Instantiate(puffPrefab),
            actionOnGet: puff => puff.gameObject.SetActive(true),
            actionOnRelease: puff => puff.gameObject.SetActive(false),
            actionOnDestroy: puff => Destroy(puff.gameObject),
            collectionCheck: true,
            defaultCapacity: 10,
            maxSize: 20);
    }

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= 0.2f)
        {
            timer = 0f;
            Puff puff = pool.Get();
            puff.transform.position = transform.position + Vector3.up;
            puff.Launch(pool);
        }
    }
}
// Puff.cs
private IObjectPool<Puff> pool;
private float age;

public void Launch(IObjectPool<Puff> owner)
{
    pool = owner;
    age = 0f;
}

void Update()
{
    transform.Translate(0f, Time.deltaTime, 0f);
    age += Time.deltaTime;
    if (age >= 1f) pool.Release(this);
}

当 Get 发现对象池为空时,会运行 createFunc;每次调用 Get 都会运行 actionOnGet,每次调用 Release 都会运行 actionOnRelease。如果归还烟雾时,已有 maxSize 个备用对象在池中等待,对象池就不会保留它,而是由 actionOnDestroy 将它销毁。defaultCapacity 只是对象池列表的初始容量,因此不会预先创建对象。CountAll 统计对象池创建且仍持有的烟雾数量,CountActive 统计游戏中正在使用的烟雾数量,CountInactive 则统计备用烟雾数量。Get 会先启用烟雾,然后烟囱才设置它的位置。在这里这样做没有问题,因为 Puff 没有会读取其位置的 OnEnable。烟雾将对象池保存为 IObjectPool<Puff>,这是所有 Unity 对象池都实现的接口;每次使用时,Launch 都会重置它的存活时间。

启用 collectionCheck 后,如果归还已经在对象池中的烟雾,就会抛出 InvalidOperationException:Trying to release an object that has already been released to the pool.这项检查只在 Editor 中运行,因为它会在每次 Release 时搜索备用对象;在构建版本中,烟雾会在对象池中重复出现两次,调用两次 Get 都会返回同一个对象。常见原因是对象归还后仍在运行,并再次归还自己,因此 actionOnRelease 会将它停用。

challenge icon

挑战

中等

从 Unity 的对象池中发射烟花。

  1. 在 Launcher 的 Awake 中,创建一个 ObjectPool<Spark>:使用 Instantiate(sparkPrefab) 创建对象,取出时启用,释放时禁用,放回时销毁对象,并设置 maxSize: 5。
  2. 在 Update 中,使用 pool.Get() 获取每个火花,而不是使用 Instantiate。
  3. 在 Return 中,调用 pool.Release(spark),而不是调用 Destroy。

按“开始”并发射几轮烟花。然后点击“检查”。

亲手试试

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;

public class Launcher : MonoBehaviour
{
    public Spark sparkPrefab;
    public int sparks = 8;

    void Awake()
    {
        // 创建对象池:创建、获取、释放和销毁火花,最多保留 5 个备用火花
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            for (int i = 0; i < sparks; i++)
            {
                Spark spark = Instantiate(sparkPrefab);
                spark.transform.position = transform.position + Vector3.up;
                spark.Launch(Direction(i), this);
            }
        }
    }

    // 生命周期已结束的火花
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // 向外、向上,均匀分布在发射器周围
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}

Unity 游戏架构中的所有课

自己练习: Unity 在线编辑器