Unity 对象池(缓存池)设计与实现
一、为什么需要对象池
在 Unity 项目中,频繁调用 Instantiate / Destroy 创建与销毁 GameObject,通常会引发以下问题:
- 产生大量 GC(垃圾回收),导致明显卡顿
- 运行时性能波动较大,尤其在 战斗系统 / UI 列表 / 特效系统 中更为突出
- 对象生命周期分散,难以统一管理与维护
对象池(Object Pool / 缓存池) 的核心思想可以概括为一句话:
对象不用就回收,需要时复用,而不是反复创建和销毁。
二、对象池模型
| 现实概念 | 程序抽象 | 说明 |
|---|---|---|
| 柜子 | PoolMgr |
统一管理所有对象类型 |
| 抽屉 | PoolData |
按对象名区分的子池 |
| 物品 | GameObject |
被缓存与复用的实例 |
三、核心类结构设计
1. PoolData(抽屉)—— 单一类型对象管理
关键成员
Stack<GameObject>:对象缓存容器rootObj:抽屉根节点(仅在开启布局时存在)
取对象流程
- 从栈中
Pop一个对象 - 激活对象(
SetActive(true)) - 断开与抽屉父节点的层级关系
回收对象流程
- 失活对象(
SetActive(false)) - 重新挂到抽屉根节点下
Push回栈中等待下次复用
2. PoolMgr(柜子)—— 全局对象池管理器
核心成员
Dictionary<string, PoolData> poolDic; // 抽屉集合
GameObject poolObj; // Pool 根节点(调试用)
static bool isOpenLayout; // 是否启用层级整理
四、代码实现(PoolMgr.cs)
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// 抽屉对象
/// </summary>
public class PoolData
{
//抽屉根对象 用来布局
private GameObject rootObj;
//用来存储抽屉中对象的容器
private Stack<GameObject> dataList = new Stack<GameObject>();
//获取容器中对象的数量
public int Count => dataList.Count;
//传入name作为抽屉对象物体名 传入poolObj作为抽屉对象的父对象
public PoolData(string name, GameObject poolObj)
{
if (PoolMgr.isOpenLayout)
{
rootObj = new GameObject(name);
rootObj.transform.parent = poolObj.transform;
}
}
/// <summary>
/// 取出容器中对象
/// </summary>
public GameObject GetObj()
{
//取出对象物体
GameObject obj = dataList.Pop();
//激活取出的对象
obj.SetActive(true);
if (PoolMgr.isOpenLayout)
{
//断开取出对象和抽屉对象的父子关系
obj.transform.parent = null;
}
return obj;
}
/// <summary>
/// 将对象放入抽屉中
/// </summary>
/// <param name="obj"></param>
public void PushObj(GameObject obj)
{
//失活放入的对象
obj.SetActive(false);
if (PoolMgr.isOpenLayout)
{
//建立放入对象和抽屉对象的父子关系
obj.transform.parent = rootObj.transform;
}
//将放入对象存入容器dataList中
dataList.Push(obj);
}
}
/// <summary>
/// 缓存池模块 管理器
/// </summary>
public class PoolMgr
{
private static PoolMgr instance;
public static PoolMgr Instance
{
get
{
if (instance == null)
{
instance = new PoolMgr();
}
return instance;
}
}
//柜子容器当中有抽屉的体现
private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
private GameObject poolObj;
//是否开启布局功能
public static bool isOpenLayout = false;
private PoolMgr() { }
/// <summary>
/// 拿东西的方法
/// </summary>
/// <param name="name">抽屉容器的名字</param>
/// <returns>从缓存池中取出的对象</returns>
public void GetObj(string name, UnityAction<GameObject> callBack)
{
GameObject obj;
//有抽屉 并且 抽屉里 有对象 才去直接拿(同步加载资源)
if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
{
//弹出栈中的对象 直接返回给外部使用
obj = poolDic[name].GetObj();
//取出缓存池对象操作 可用于对象初始化(避免激活失活无法运行Start初始化)
callBack?.Invoke(obj);
}
//否则 就应该去创造
else
{
//异步加载资源
ResourceRequest rq = Resources.LoadAsync<GameObject>(name);
rq.completed += (a) =>
{
obj = GameObject.Instantiate(rq.asset as GameObject);
obj.name = name;
//取出缓存池对象操作 可用于对象初始化(避免激活失活无法运行Start初始化)
callBack?.Invoke(obj);
};
}
}
/// <summary>
/// 往缓存池中放入对象
/// </summary>
/// <param name="name">抽屉(对象)的名字</param>
/// <param name="obj">希望放入的对象 </param>
public void PushObj(GameObject obj)
{
//放入缓存池对象相关操作
//callBack?.Invoke(obj);
if (poolObj == null && isOpenLayout)
{
poolObj = new GameObject("Pool");
}
//没有抽屉 创建抽屉
if (!poolDic.ContainsKey(obj.name))
{
poolDic.Add(obj.name, new PoolData(obj.name, poolObj));
}
//往抽屉当中放对象
poolDic[obj.name].PushObj(obj);
}
/// <summary>
/// 用于清除整个柜子当中的数据
/// 主要是 切场景时 使用
/// </summary>
public void ClearPool()
{
poolDic.Clear();
poolObj = null;
}
}
五、注意事项
GetObj使用 callBack参数,取出缓存池对象操作 可用于对象初始化(避免激活失活无法运行Start初始化)isOpenLayout仅用于 调试与层级可视化,正式版本可关闭- 切场景必须调用
ClearPool:- 否则字典仍持有旧场景对象引用
- 可能导致内存泄漏或空引用异常
六、使用示例
// 取出对象
PoolMgr.Instance.GetObj("Test/Cube", obj =>
{
obj.transform.position = Vector3.zero;
});
// 回收对象
PoolMgr.Instance.PushObj(obj);
// 切场景并清空缓存池
SceneManager.LoadScene("NewScene");
PoolMgr.Instance.ClearPool();

浙公网安备 33010602011771号