SmPool 类
一个对象池管理器,用于高效地管理游戏对象(如 Prefab)。在游戏开发中,经常需要频繁生成和销毁对象(例如子弹、敌人、特效等)。
如果每次都使用 Instantiate 和 Destroy 来创建和销毁对象,会对性能造成影响,尤其是在移动设备或其他性能有限的设备上。
对象池的概念是提前创建一批对象,当需要使用时将其从池中取出,使用完后再放回池中,而不是销毁它们。这样可以大大减少对象创建和销毁的开销。
主要功能
预加载对象:通过 Prepare() 方法,预先创建指定数量的对象,并存储在一个栈(mStock)中。
对象生成与回收:当需要使用对象时,通过 SpawnItem() 方法从池中取出一个对象并激活它;当不再使用时,通过 DespawnItem() 将对象放回池中或销毁。
自动清理:池支持自动清理功能(AutoCull),会定期检查池中的对象数量,并移除多余的对象以节省内存。
动态扩展:当池中的对象不够用时,池会动态扩展,即增加新的对象到池中。
对象重用:当池达到最大容量时,可以选择重用已生成的对象(ReUse 模式),避免频繁创建新对象。
类的组成
Prefab 和 PoolName:每个对象池都会管理一种 Prefab,并通过 PoolName 进行标识。
DontDestroy:设置为 true 时,该池的对象在场景切换时不会被销毁。
AllocationBlockSize:当池需要扩展时,每次增加的对象数量。
MaxPoolSize 和 MinPoolSize:池的最大和最小对象数量。池会根据需求动态调整对象数量。
AutoCull:是否自动清理池中的多余对象。
fnSpawn 和 fnDespawn:自定义生成和回收对象时的行为函数。
cs
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
/// <summary>
/// 管理Prefab池的类
/// </summary>
public class SmPool : MonoBehaviour
{
/// <summary>
/// 池的名称
/// </summary>
public string PoolName;
/// <summary>
/// 池中管理的Prefab,一个池只管理一个对应的预制体
/// </summary>
public GameObject Prefab;
/// <summary>
/// 在场景切换时保持持久化
/// </summary>
public bool DontDestroy = false;
/// <summary>
/// 游戏启动时自动准备。如果为false,需要手动调用Prepare()方法
/// </summary>
public bool PrepareAtStart = true;
/// <summary>
/// 池需要扩展或收缩时的分配的大小
/// </summary>
public int AllocationBlockSize = 1;//自行调试
/// <summary>
/// 池的最小大小
/// </summary>
public int MinPoolSize = 1;//自行调试
/// <summary>
/// 池的最大大小(池的最大大小应该是池内池外的和)
/// </summary>
public int MaxPoolSize = 1;//自行调试
/// <summary>
/// 超出最大池大小时的行为
/// </summary>
public PoolExceededMode OnMaxPoolSize = PoolExceededMode.Ignore;
/// <summary>
/// 自动清除 items直到池大小达到MaxPoolSize
/// </summary>
public bool AutoCull = true;
/// <summary>
/// 自动清除的时间间隔,单位为秒
/// </summary>
public float CullingSpeed = 1.0f;//自行调试
/// <summary>
/// 是否记录所有操作日志
/// </summary>
public bool DebugLog = false;
/// <summary>
/// 清除间隔时间,单位为秒
/// </summary>
public float CullingInterval = 2f;//自行调试
/// <summary>
/// 对象从池中拿出时回调
/// </summary>
public System.Action<GameObject> fnSpawn;
/// <summary>
/// 对象放回池时回调.
/// 预加载的时候也会被调用
/// </summary>
public System.Action<GameObject> fnDespawn;
/// <summary>
/// 存放未使用对象的栈(池内)
/// </summary>
Stack<GameObject> mStock = new Stack<GameObject>();
/// <summary>
/// 存放已生成的对象列表(池外)
/// </summary>
public List<GameObject> mSpawned = new List<GameObject>();
/// <summary>
/// 上次清除的时间
/// </summary>
float mLastCullingTime;
/// <summary>
/// 当前池中对象数
/// </summary>
public int InStock
{
get { return mStock.Count; }
}
/// <summary>
/// 当前生成对象数
/// </summary>
public int Spawned
{
get { return mSpawned.Count; }
}
#region ### Unity 回调 ###
/// <summary>
/// 初始化池,需要手动传入预制体/某对象的引用
/// 会根据PrepareAtStart属性预加载池,默认是预加载
/// </summary>
/// <param name="pf">预制体</param>
/// <param name="min">最小尺寸</param>
/// <param name="max">最大尺寸</param>
public void Init(GameObject pf, int min, int max)
{
DoInit(pf, min, max);
}
/// <summary>
/// 初始化池,不传入引用,而是传入一个预制体路径.
/// 注意:是Resources文件夹下的路径
/// 会根据PrepareAtStart属性预加载池,默认是预加载
/// </summary>
/// <param name="sgPf">预制体路径</param>
/// <param name="min">最小尺寸</param>
/// <param name="max">最大尺寸</param>
public void Init(string sgPf, int min, int max)
{
GameObject pf = Resources.Load<GameObject>(sgPf); // 加载资源
DoInit(pf, min, max);
}
/// <summary>
/// 创建一个池,这会在场景中生成一个对象,但没初始化.
/// </summary>
/// <param name="sg">池属性名</param>
/// <param name="parent">池对象的父物体</param>
/// <param name="cullingInterval"></param>
/// <returns>返回池</returns>
public static SmPool CreatePool(string sg, Transform parent, float cullingInterval = 2f)
{
GameObject go = new GameObject(sg); // 创建池对象
Transform tm = go.transform;
tm.SetParent(parent); // 设置父对象
tm.localPosition = Vector3.zero; // 初始化位置
tm.localScale = Vector3.one; // 初始化缩放
SmPool pm = go.AddComponent<SmPool>();
pm.PoolName = sg;
pm.CullingInterval = cullingInterval;
return pm;
}
/// <summary>
/// 实际初始化池
/// </summary>
/// <param name="go">池管理那个预制体</param>
/// <param name="min">池的最小尺寸</param>
/// <param name="max">池的最大尺寸</param>
void DoInit(GameObject go, int min, int max)
{
this.Prefab = go; // 设定管理的Prefab
this.MinPoolSize = min; // 最小池大小
this.MaxPoolSize = max; // 最大池大小
//
if (PoolName.Length == 0)
Debug.LogWarning("SmartPool: 缺少池名,属于'" + gameObject.name + "'!");
if (DontDestroy)
DontDestroyOnLoad(gameObject); // 设置为在场景切换中不销毁对象
if (Prefab == null)
{
Debug.LogError("SmartPool: 池'" + PoolName + "'缺少Prefab!");
}
if (PrepareAtStart)
Prepare(); // 如果自动准备,则调用Prepare()
}
void OnEnable()
{
StartCoroutine(__LateTick()); // 开始协程进行定期清除
}
/// <summary>
/// 协程定时自动清除池中对象
/// </summary>
/// <returns></returns>
IEnumerator __LateTick()
{
WaitForSeconds wfs = new WaitForSeconds(CullingInterval); // 定义清除间隔时间
for (;;) //无限循环
{
if (AutoCull && Time.time - mLastCullingTime > CullingSpeed)
{
mLastCullingTime = Time.time; // 更新上次清除时间
Cull(true); // 执行清除
}
yield return wfs;
}
}
void Reset()
{
// 重置所有参数
PoolName = "";
Prefab = null;
DontDestroy = false;
AllocationBlockSize = 1;
MinPoolSize = 1;
MaxPoolSize = 1;
OnMaxPoolSize = PoolExceededMode.Ignore;
DebugLog = false;
AutoCull = true;
CullingSpeed = 1f;
mLastCullingTime = 0;
}
#endregion
#region ### 池的操作方法 ###
/// <summary>
/// 删除池中的所有实例,销毁对象并清空引用
/// </summary>
void Clear()
{
if (DebugLog)
Debug.Log("SmartPool (" + PoolName + "): 清除所有实例 " + Prefab.name);
foreach (GameObject go in mSpawned)
Destroy(go); // 销毁所有生成的对象
mSpawned.Clear(); // 清空已生成列表
foreach (GameObject go in mStock)
Destroy(go); // 销毁库存中的对象
mStock.Clear(); // 清空库存栈
}
/// <summary>
/// 收缩库存至符合MaxPoolSize
/// </summary>
public void Cull()
{
Cull(false);
}
/// <summary>
/// 收缩库存至符合MaxPoolSize
/// </summary>
/// <param name="smartCull">如果为true,则最大移除AllocationBlockSize个对象</param>
public void Cull(bool smartCull)
{
int toCull = (smartCull)
? Mathf.Min(AllocationBlockSize, mStock.Count - MaxPoolSize)
: mStock.Count - MaxPoolSize;
while (toCull-- > 0)
{
GameObject item = mStock.Pop(); // 从库存栈中移除
Destroy(item); // 销毁对象
}
}
/// <summary>
/// 回收对象到池中
/// 如果没有放回回调那么失活,否则不失活
/// 注意:这个对象如果不是这个池生成的会被销毁
/// </summary>
/// <param name="item"></param>
public void DespawnItem(GameObject item)
{
if (!item) return;
if (IsSpawned(item))
{
mSpawned.Remove(item); // 从已生成列表移除
mStock.Push(item); // 加入库存栈
if (fnDespawn == null)
{
item.SetActive(false); // 如果没有自定义回收行为,禁用对象
}
else
fnDespawn(item); // 调用自定义回收行为
}
else
{
GameObject.Destroy(item); // 如果不在已生成列表中,直接销毁对象
}
}
/// <summary>
/// 回收池中所有对象
/// </summary>
public void DespawnAllItems()
{
while (mSpawned.Count > 0)
DespawnItem(mSpawned[0]); // 回收所有对象
}
/// <summary>
/// 销毁已生成的对象而不是回收它
/// </summary>
/// <param name="item">需要销毁的对象</param>
public void KillItem(GameObject item)
{
if (!item)
{
return;
}
mSpawned.Remove(item); // 从已生成列表移除
Destroy(item); // 销毁对象
}
/// <summary>
/// 判断对象是否由该池管理
/// </summary>
/// <param name="item">对象</param>
/// <returns>如果对象由该池管理,返回true</returns>
public bool IsManagedObject(GameObject item)
{
if (!item)
{
return false;
}
if (mSpawned.Contains(item) || mStock.Contains(item))
return true;
else
return false;
}
/// <summary>
/// 判断对象是否由该池生成
/// </summary>
/// <param name="item">对象</param>
/// <returns>如果对象由该池生成,返回true</returns>
public bool IsSpawned(GameObject item)
{
if (!item)
{
return false;
}
return (mSpawned.Contains(item)); // 判断对象是否在已生成列表中
}
/// <summary>
/// 生成实例并将其添加(放回)池
/// 如果放回池中的回调存在那么不失活,否则失活
/// </summary>
/// <param name="no">生成的数量</param>
void Populate(int no)
{
GameObject go;
while (no > 0)
{
go = Instantiate<GameObject>(Prefab); // 实例化Prefab
go.transform.SetParent(transform); // 设置父对象
go.name = Prefab.name; // 设置名称
mStock.Push(go); // 加入库存栈
no--;
if (fnDespawn == null)
go.SetActive(false); // 如果没有自定义行为,禁用对象
else
fnDespawn(go); // 调用自定义行为
}
}
/// <summary>
/// 预加载池.首先清空池并重新填充池到最小大小
/// </summary>
public void Prepare()
{
Clear(); // 清空池
mStock = new Stack<GameObject>(MinPoolSize); // 初始化库存栈
Populate(MinPoolSize); // 填充池
}
/// <summary>
/// 生成实例并激活它,添加到已生成列表中
/// </summary>
/// <returns>生成的实例</returns>
public GameObject SpawnItem()
{
GameObject item = null;
// 如果库存没有对象,则生成更多
if (InStock == 0)
{
//如果没达到池的数量上限或池本身忽略上限,那么会填充池
if (Spawned < MaxPoolSize || OnMaxPoolSize == PoolExceededMode.Ignore)
Populate(AllocationBlockSize); // 填充池
}
// 如果有对象在库存中,则取出
if (InStock > 0)
{
item = mStock.Pop();
}
else if (OnMaxPoolSize == PoolExceededMode.ReUse)
{
item = mSpawned[0]; // 重用已生成对象
mSpawned.RemoveAt(0);
}
if (item != null)
{
mSpawned.Add(item); // 添加到已生成列表中
if (fnSpawn == null)//todo 这里如果放入池的时候失活,拿出的时候不一定会激活
{
item.SetActive(true); // 激活对象
item.transform.localPosition = Vector3.zero; // 重置位置
}
else
fnSpawn(item); // 调用自定义生成行为
}
return item;
}
#endregion
}
/// <summary>
/// 定义超出MaxPoolSize时的反应
/// </summary>
[System.Serializable]
public enum PoolExceededMode : int
{
/// <summary>
/// 忽略MaxPoolSize
/// </summary>
Ignore = 0,
/// <summary>
/// 当超出MaxPoolSize时停止生成
/// </summary>
StopSpawning = 1,
/// <summary>
/// 当超出MaxPoolSize时重用已生成对象
/// </summary>
ReUse = 2
}