一个对象池的实现
对象池的学习
一 、首先对于缓存的GameObject 是否可以如池不以 是否激活来判定,而是添加一个带有bool值的组件来实现
/*key 用于让系统可重用多种 对象 * 他附加的物体则是 它本生所在的gameObject 无需在另外添加引用 */ public class Poolable : MonoBehaviour { public string key; public bool isPooled; }
如此做的缘由是因为使用这个物体是否被激活来判定入池的话会造成:
- 对象在分配给用户时并不处于激活状态,所以也没办法知道用户何时会激活这个物体。这就意味着对象池很可能错误地将同一个物体分配给多个用户。
- 因为对象池正在检查层次面板中的激活状态,禁用任何父物体都将导致已入池对象被标记为可重用—这可能会导致不可预期的后果。
- 必须检查整个层次结构来判断一个物体是否可用—要比在某些位置保存bool值慢得多。
二 、池管理器
/// <summary> /// 池管理器中 不同种预存物的数据类型 /// </summary> public class PoolData { public GameObject prefab;//实例化新对象的预制 public int maxCount; // 内存保存的最大对象数 public Queue<Poolable> pool;//存储可重用对象的队列 }
池管理器中 有一个字符串 key 映射 到 PoolData类的字典,它包含了以下信息:用于实例化新对象的预制,内存中保存的最大对象数量,以及用于存储可重用对象的队列。要使用它,你首先要调用AddEntry方法,在其中指定key与prefab的映射,并且告诉它预先创建多少(如果有)对象以及要存入内存的最大对象数量。在理想情况下,你会知道在游戏中平均需要多少对象。因为你可以在原始群体和最大计数中使用不同的值。你对池的是否扩充,扩充多少,有着完全控制权。
这个方法是静态的——这就意味着你并不需要一个实例引用来使用池管理器,你只需引用该类本身,静态方法比实例方法和属性要稍微快一点。但同时你也失去了一些灵活性比如继承和重载某些功能性函数。选择最适合你需求的模式。
即使我是用了静态方法,我仍然选择去创建一个单例实例,我使用这个GameObject有以下两个原因,有兴趣的可以关注一下:
- 组织结构:通过使池化物体成为池管理器的子物体,我能在编辑器的层次面板(Hierarchy)中折叠它们,这样开发真是棒极了!
- 保存物体:我的池管理器能保存场景改动,还可以保存其层次中的已入池对象。如果你不想要这功能,只需在销毁已添加实体的脚本同时销毁该实体即可。否则,如果你在多个场景中重用物体,或者在一个场景中来回反复地改变,那么这些场景的后续加载时间将会不一样长。
剩下的就是对 pooldata 的列表维护了
public class BulletPool : MonoBehaviour { #region 字段 、属性 static BulletPool instance; static BulletPool Instance { get { if (instance == null) CreateSharedInstance(); return instance; } } //string 不同的对象 指向 static Dictionary<string , PoolData> pools = new Dictionary<string , PoolData>(); #endregion #region Monobehaviour void Awake() { if (instance != null && instance != this) Destroy(this); else instance = this; } #endregion #region Public /// <summary> /// 设置这个类型物体的最大存储量 /// </summary> /// <param name="key"></param> /// <param name="maxCount"></param> public static void SetMaxCount(string key,int maxCount) { if (!pools.ContainsKey(key)) return; PoolData data = pools[key]; data.maxCount = maxCount; } /// <summary> /// 添加一种对象的 对象池存储 /// </summary> /// <param name="key">预制名</param> /// <param name="prefab">预制对象体</param> /// <param name="prepopulate">对象池欲存储的这个数量</param> /// <param name="maxCount">可存的最大数量</param> /// <returns>已经含有了这个物体的pool则返回</returns> public static bool AddEntry(string key,GameObject prefab,int prepopulate,int maxCount) { if (pools.ContainsKey(key)) return false; PoolData data = new PoolData(); data.prefab = prefab; data.maxCount = maxCount; data.pool = new Queue<Poolable>(prepopulate); pools.Add(key , data); //创建预存预设 for (int i = 0 ; i < prepopulate ; i++) Enqueue(CreateInstance(key,prefab)); return true; } /// <summary> /// 清除对象池 的一个对象种 /// </summary> /// <param name="key"></param> /// 如果不存在直接返回 public static void ClearEntry(string key) { if (!pools.ContainsKey(key)) return; PoolData data = pools[key]; while(data.pool.Count>0)//清除该种的 所有缓存对象 { Poolable obj = data.pool.Dequeue();//从对象池中清除第一个并返回 GameObject.Destroy(obj.gameObject);//清除Gameobject } pools.Remove(key); //移除 } /// <summary> /// 给对象池的该对象种 推入一个对象, 这个对象的种类分类 由字典管理 和 这个种类的队列 由PoolData的队列管理 /// </summary> /// <param name="sender"></param> public static void Enqueue(Poolable sender) { //这个对象种不存在 , 这个单个对象不可用 , if (sender == null || sender.isPooled || !pools.ContainsKey(sender.key)) return; PoolData data = pools[sender.key];//取得这个对象种 在对象池中的 队列数据结构 //超过这个对象种的最大存储量 if(data.pool.Count>=data.maxCount) { GameObject.Destroy(sender.gameObject); return; } data.pool.Enqueue(sender);//添加这个对象到 对象池 的 该种队列 sender.isPooled = true; sender.transform.SetParent(Instance.transform); sender.gameObject.SetActive(false); } /// <summary> /// 从对象池中的 该种对象 的队列中移除一个 对象, /// </summary> /// <param name="key">对象种的 key</param> /// <returns>返回</returns> public static Poolable Dequeue(string key) { if (!pools.ContainsKey(key)) // 没有这个对象种 return null; PoolData data = pools[key]; //该对象组的所有数据 if(data.pool.Count==0) { return CreateInstance(key , data.prefab); //该种 的预存已空 返回一个 } Poolable obj = data.pool.Dequeue(); //移除 obj.isPooled = false; return obj; } #endregion #region Private /// <summary> /// 创建对象池控制器obj 且绑定对象池脚本, /// </summary> static void CreateSharedInstance() { GameObject obj = new GameObject("BulletPool"); DontDestroyOnLoad(obj); instance = obj.AddComponent<BulletPool>(); } /// <summary> /// 创建一个对象的克隆 用来做缓存 /// </summary> /// <param name="key"></param> /// <param name="prefab"></param> /// <returns></returns> static Poolable CreateInstance(string key,GameObject prefab) { GameObject instance = Instantiate<GameObject>(prefab); Poolable p = instance.AddComponent<Poolable>(); p.key = key; return p; } #endregion }