对象池系统

对象池系统通过预先创建与复用对象来减少频繁的实例化与销毁开销,核心思想是“对象不用就隐藏并回收,下次再取出继续用”。
系统分为游戏对象池与泛型对象池两类:

  • 游戏对象池负责管理预制体的复用与上限控制,支持调试布局、预加载和最旧对象复用;
  • 泛型对象池用于逻辑类或数据类的重复利用,并在回收时执行重置方法。

所有池子由 PoolManager 统一管理,提供 Spawn/Despawn、自动创建池、预加载与跨场景清理等功能,使整个项目的对象创建更高效、更可控。


1. 总览

Fink Framework 的对象池系统由以下部分组成:

  • IPoolable:所有可池化的非 GameObject 类型需实现的接口
  • PoolStorage / BasePoolStorage:泛型对象池
  • PoolablePrefab:挂在 GameObject 预制体上的池化配置
  • GameObjectPool:单一 GameObject 池的具体实现
  • PoolManager:对象池全局管理器,统一分发与调度

此外,系统支持:

  • GameObject 池化(带复用上限、调试布局、动态扩容、预加载)
  • 泛型类池化(用于逻辑类、数据类)
  • 自动复用最旧对象
  • 统一管理、跨场景清理

2. 泛型对象池接口

2.1 功能概述

IPoolable 是所有需要进入 泛型对象池(非 GameObject 池) 的数据结构类、逻辑类必须实现的接口。

用途:

  • 通用对象重复利用
  • 自动调用 ResetInfo() 重置状态

2.2 接口定义

public interface IPoolable
{
    void ResetInfo();
}

3. 泛型对象池

3.1 设计目的

为非 GameObject 的数据类或逻辑类提供轻量级对象池。
采用 Queue 先进先出模型,简单高效。

3.2 结构

public abstract class BasePoolStorage { }

public class PoolStorage<T> : BasePoolStorage where T : class
{
    public Queue<T> poolObjs = new();
}

4. 游戏对象池化配置

4.1 功能说明

GameObject 预制体若要被池化,必须在预制体上添加此脚本(PoolablePrefab.cs)组件。

用途:

  • 指定该预制体在对象池中的最大同时存在数量(maxNum)

5. 全局对象池管理器

5.1 主要作用

PoolManager 是整个对象池系统的中枢,你永远不需要自己创建池,PoolManager 会自动帮你完成所有管理。

该管理器主要负责:

  • 统一管理所有 GameObject 对象池(ObjectPool)
  • 管理所有 泛型对象池(PoolStorage
  • 自动创建指定类型的对象池(无需用户提前注册)
  • 提供统一的 Spawn() / Despawn() API
  • 支持 预加载(Preload),减少运行时实例化
  • 场景切换时支持 自动清空所有池
  • 支持根据预制体 PoolGameObject.maxNum 自动限制最大数量

5.2 游戏对象池 Spawn 机制

以下是 Spawn 的完整工作流程,让你更容易理解系统的运行方式:

5.2.1 第一次 Spawn:「自动创建池」 如果从未请求过某个对象,例如 "Enemy"

var enemy = PoolManager.Instance.Spawn("Enemy");

流程为:

  1. 从资源中加载 "Enemy" 预制体(使用 ResManager)
  2. 创建第一个实例
  3. 自动创建一个新的 ObjectPool
  4. 自动读取 PoolGameObject.maxNum
  5. 第一个对象加入“使用中列表”

你无需提前注册池,也无需手动创建容器。

5.2.2 后续 Spawn:「优先复用已有对象」

当池子已经存在时:

  • 若池中仍有缓存对象 → 直接取出
  • 若池中无缓存对象但未超上限 → 动态创建新的对象
  • 若已达最大上限 → 复用最旧的对象(对象池关键)

这样几乎所有情况下都不会进行销毁。

Spawn 返回的对象一定是 可立即使用的已激活对象

var enemy = PoolManager.Instance.Spawn("Enemy");
enemy.transform.position = spawnPoint;

5.3 泛型对象池 Spawn 机制

泛型池可用于池化任何「非 GameObject 类型」:

  • 数据类(如技能 info)
  • 逻辑类(如伤害计算器、路径节点、AI 状态数据)
  • 数组、结构体包装对象

泛型池规则:

  1. 若池不为空 → 出队复用
  2. 若池为空 → new T()
  3. 若池不存在 → 自动创建池后 new T()

5.4 游戏对象池回收机制

最简单的部分,只需传回对象本体:

PoolManager.Instance.Despawn(enemy);

回收时会自动:

  • SetActive(false)
  • 放回缓存栈
  • 从使用列表移除
  • 调试模式下重新归类到池根节点

5.5 泛型对象池回收机制

泛型对象会自动调用重置方法:

PoolManager.Instance.Despawn(info);

执行顺序:

  1. info.ResetInfo() —— 你可以在这里恢复默认状态
  2. 放入 Queue 中排队等待再次复用

泛型池适合经常创建大量数据类的系统,例如子弹伤害信息、战斗日志等。


5.6 预加载(Preload)

为了减少运行时卡顿,你可以在场景加载时提前准备对象:

PoolManager.Instance.Preload("Enemy", 50);

这会:

  • 首次创建对象池(若不存在)
  • 创建一定数量的对象加入池中
  • 自动处理最大上限
  • 统一在最后进行回收避免立即复用

适用于:

  • 怪物大量刷新的游戏
  • 子弹密集发射的射击游戏
  • UI 大量动态生成的界面(可配合 UI 池)

5.7 清空对象池(CleanPool)

切换场景时应当清理池内容,以免出现场景内幽灵对象挂掉的问题。

框架内置的场景系统在切换时会自动调用此方法,详情请见场景切换系统

代码示例:

PoolManager.Instance.CleanPool();

6. 对象池单独使用的简单示例

6.1 GameObject 的生成与回收

假设 Resources 下存在一个名为 "Enemy" 的预制体:

var enemy = PoolManager.Instance.Spawn("Enemy");
enemy.transform.position = new Vector3(0,1,0);

当对象不再使用时,只需要:

PoolManager.Instance.Despawn(enemy);

你无需手动 Destroy(),池系统会自动管理生命周期。


6.2 泛型对象的复用

假设 BulletInfo 是一个数据类,并实现了 IPoolable:

var bulletInfo = PoolManager.Instance.Spawn<BulletInfo>();

使用之后:

PoolManager.Instance.Despawn(bulletInfo);

该对象会:

  • 自动执行 ResetInfo()
  • 自动进入队列等待复用

6.3 一个更完整的示例

需要提前在Resources目录下放置一个名为"Bullet"的预制体文件。(可以只是一个简单的立方体)

public class EnemyShooter : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 生成子弹
            var bullet = PoolManager.Instance.Spawn("Bullet");
            bullet.transform.position = transform.position;
            bullet.GetComponent<Rigidbody>().velocity = transform.forward * 10f;
            
            // 3 秒后自动回收
            StartCoroutine(ReturnBullet(bullet));
        }
    }

    private IEnumerator ReturnBullet(GameObject bullet)
    {
        yield return new WaitForSeconds(3f);
        PoolManager.Instance.Despawn(bullet);
    }
}

这个例子展示了对象池最常见的实际用途:

  • 快速创建大量对象(子弹)
  • 无需销毁
  • 自动回收
  • 高效稳定

6.4 在战斗场景中预加载对象

private void Start()
{
    PoolManager.Instance.Preload("Bullet", 100);
    PoolManager.Instance.Preload("Enemy", 20);
}

这样可以极大减少战斗开始时的卡顿。


6.5 使用对象池的最佳实践

  1. 所有需要池化的预制体必须挂 PoolGameObject
  2. 尽量提前 Preload,减少运行时卡顿
  3. 不要使用 Destroy(),全部使用 Despawn()
  4. 对象被禁用后别依赖 OnDisable 做逻辑
  5. 逻辑类对象实现 ResetInfo() 时务必还原全部字段
  6. 避免对象频繁改变父子关系(debugMode 请发布前关闭)

这样你就能完整发挥对象池系统的性能优势。

7. 源文件引用

  • Framework/Pool/IPoolable.cs
  • Framework/Pool/GameObjectPool.cs
  • Framework/Pool/PoolablePrefab.cs
  • Framework/Pool/PoolManager.cs
  • Framework/Pool/PoolStorage.cs