对象池技术

16.1 为什么需要对象池

在游戏开发中,频繁创建和销毁对象会导致:

  • GC 压力:大量内存分配触发垃圾回收,造成卡顿
  • 性能开销:Instantiate / Destroy 是开销较大的操作

对象池通过预先创建一批对象实例,复用而不是销毁,显著提升性能。

适合对象池的场景

  • 子弹、弹幕
  • 粒子特效
  • 敌人波次
  • 掉落物
  • 临时 UI 提示

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

public class ObjectPool<T> where T : MonoBehaviour
{
private readonly T prefab;
private readonly Queue<T> pool = new Queue<T>();
private readonly Transform parent;

public ObjectPool(T prefab, int preloadCount, Transform parent = null)
{
this.prefab = prefab;
this.parent = parent;

for (int i = 0; i < preloadCount; i++)
{
T obj = CreateNew();
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}

private T CreateNew()
{
T obj = Object.Instantiate(prefab, parent);
obj.name = $"{prefab.name}_Pooled";
return obj;
}

public T Get()
{
if (pool.Count > 0)
{
T obj = pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}

T newObj = CreateNew();
newObj.gameObject.SetActive(true);
return newObj;
}

public void Return(T obj)
{
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}

16.3 实战:子弹池

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
public class Bullet : MonoBehaviour
{
[SerializeField] private float speed = 20f;
[SerializeField] private float lifetime = 3f;
private float timer;
private ObjectPool<Bullet> pool;

public void Initialize(ObjectPool<Bullet> owner)
{
pool = owner;
}

void OnEnable()
{
timer = 0f;
}

void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);

timer += Time.deltaTime;
if (timer >= lifetime)
{
ReturnToPool();
}
}

void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Enemy"))
{
other.GetComponent<Enemy>().TakeDamage(10);
ReturnToPool();
}
}

public void ReturnToPool()
{
pool?.Return(this);
}
}
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
public class Gun : MonoBehaviour
{
[SerializeField] private Bullet bulletPrefab;
[SerializeField] private Transform firePoint;
[SerializeField] private int preloadCount = 20;
private ObjectPool<Bullet> bulletPool;

void Start()
{
bulletPool = new ObjectPool<Bullet>(bulletPrefab, preloadCount);
}

void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}

void Shoot()
{
Bullet bullet = bulletPool.Get();
bullet.transform.SetPositionAndRotation(firePoint.position, firePoint.rotation);
bullet.Initialize(bulletPool);
}
}

16.4 使用 Unity 内置池(需要 2021.1+)

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
using UnityEngine.Pool;

public class ModernGun : MonoBehaviour
{
[SerializeField] private Bullet bulletPrefab;
private ObjectPool<Bullet> pool;

void Start()
{
pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab),
actionOnGet: (bullet) => bullet.gameObject.SetActive(true),
actionOnRelease: (bullet) => bullet.gameObject.SetActive(false),
actionOnDestroy: (bullet) => Destroy(bullet.gameObject),
collectionCheck: false,
defaultCapacity: 20,
maxSize: 50
);
}

void Shoot()
{
Bullet bullet = pool.Get();
bullet.transform.position = firePoint.position;
bullet.transform.rotation = firePoint.rotation;
}

void Update()
{
if (Input.GetKeyDown(KeyCode.R)) // 释放未使用的对象
{
pool.Trim();
}
}

void OnDestroy()
{
pool.Clear();
pool.Dispose();
}
}

// 在 Bullet 脚本中返回对象池
public class Bullet : MonoBehaviour
{
private IObjectPool<Bullet> pool;

public void SetPool(IObjectPool<Bullet> pool)
{
this.pool = pool;
}

void OnDisable()
{
// 确保返回池
pool?.Release(this);
}

void OnTriggerEnter(Collider other)
{
pool.Release(this);
}
}

16.5 性能对比

操作 耗时
Instantiate(首次) 高(资源加载)
pool.Get(池中有对象) 极低(仅 SetActive)
pool.Get(池已满) 中(Instantiate 新对象)
Destroy(立即回收) 中(内存释放)
pool.Return(池复用) 极低(仅 SetActive)

实测:1000 颗子弹场景,对象池方案比即时创建销毁快 10-20 倍,且无 GC 卡顿。


16.6 本章小结

  • 对象池通过复用对象减少 GC 和 Instantiate 开销
  • 核心操作:Get(获取)和 Return(归还)
  • 对象默认失活,取出时激活
  • Unity 2021+ 提供内置 ObjectPool<T>
  • 适合子弹、粒子、敌人等高频创建/销毁场景

练习题

  1. 实现一个简单的子弹池,发射 100 发子弹观察 GC 情况
  2. 对比 Instantiate/Destroy 和对象池方案在连续生成 500 个对象时的帧率差异
  3. 为对象池添加动态扩容和缩容功能(闲置对象超过 N 分钟自动释放)
  4. 使用 Unity 内置 ObjectPool<T> 重构一个现有项目中的对象创建逻辑
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Unity3D开发基础教程 系列
第 16/20 篇
分享: