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 会将它停用。
挑战
中等从 Unity 的对象池中发射烟花。
- 在
Launcher的Awake中,创建一个ObjectPool<Spark>:使用Instantiate(sparkPrefab)创建对象,取出时启用,释放时禁用,放回时销毁对象,并设置maxSize: 5。 - 在
Update中,使用pool.Get()获取每个火花,而不是使用Instantiate。 - 在
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 在线编辑器