[个人框架 C_Framework] C_Pool快速对象池
2018-11-05 本文已影响0人
hh5460
对象池 作为游戏开发过程中最常用的手段之一,在C_Framework框架中也必然会有,那么什么是池呢,池实际上可以理解为一个容器,需要的时候就从里面拿,用完了的时候就放回去,再需要的时候再拿出来,不停的复用来做到用池中有限的物体模拟出无限的物体,而不是使用的时候Instantiate,不用的时候Destory,因为这样做的性能效率很低...
一个简单的对象池写起来很容易,那么我们写一个稍微进阶一点的对象池,我们需要的功能有:
1.一个基本对象池
2.可以手动拖拽预制到面板设置属性创建对象池
3.可以代码创建对象池
4.不拖拽也不用代码,直接指定Resources 或者 AssetBunlde 路径,通过它们直接创建对象池
5.支持预加载,限制最大个数,保留个数,定时回收
6.支持安全模式,即当我限制了池中A物体最多只能有2个的时候A1 A2,如果前两个A1A2还没有用完,就请求第三个物体A3,在非安全模式下会正常复用直接把没用完的A1拿过来,但是在安全模式下,会抛出异常...
效果Demo:
我们先创建一个sphere,添加一个Rigidbody组件,再为其挂载一个脚本,让它每次OnEnable后2秒关闭自己active(具体的业务逻辑自己结合项目即可,这里是为了演示效果,模拟子弹2秒消失),并重命名为bullet并保存到project层级下作为预制,然后我们将C_Pool脚本挂载在Main Camera下,具体挂在哪无所谓,然后看到C_Pool面板下有Pools集合属性,我们将其设为1并添加绑定刚刚的bullet预制,然后设置其属性
Target Name : 对象的名称,当要从对象池中取出对象时,用这个名字来取
Target : 具体绑定的需要复用的对象,当这里不绑定时就需要指定下面两个参数
Resources Directory : Resourecs 路径,当上面Target属性没有绑定时,而这个属性不为空时,就从对应的Resources路径加载
Asset Bundle Pageage Name : 同上,在PersistentPath路径下自动加载包中物体
Preload Nums : 预加载,用之前就预先加载几个
Stay Nums : 保持个数,即至少要保持有几个可用的,回收也回收不掉
Limit Nums : 限制个数,即不可以超过这个数量
Is Use GC : 是否开启定时回收
Interval Time GC : 每隔多少秒回收一次
One Shot GC Num : 每次回收几个
Is Safe Mode : 是否开启安全模式,参考上方第6条
然后我们再创建一个测试脚本,不停的从池中获取bullet然后发射即可,看下方gif演示效果,具体看下对象池设置参数和使用过程中对象池的变化情况,这里我们预加载2个bullet,保持始终有2个,限制最多有8个,每2秒回收一次,每次回收2个
C_Pool
代码(SingleFramework不明白的请参考我之前的文章SingleFramework单例篇)
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class C_Pool : SingleFramework<C_Pool>
{
public bool isNeedGlobalGC = false;
public float InvokeRepGCTime = 300f; //每隔300s后自动GC回收一次
public PoolItem this[string indexString]
{
get
{
return GetPoolItem(indexString);
}
}
//界面属性绑定
public PoolItemInfo[] Pools;
Dictionary<string, PoolItem> dicPoolItems = new Dictionary<string, PoolItem>();
[HideInInspector]
public GameObject gameObjectPools;
//GC回收
void PoolGC()
{
Resources.UnloadUnusedAssets();
GC.Collect();
}
public override void Init()
{
gameObjectPools = new GameObject("Pools");
//有面板预加载的项
if (Pools.Length > 0)
{
for (int i = 0; i < Pools.Length; i++)
{
Add2Pool(Pools[i]); //初始化池信息
}
}
//是否需要开启全局GC
if (isNeedGlobalGC)
{
InvokeRepeating("PoolGC", InvokeRepGCTime, InvokeRepGCTime);
}
}
/// <summary>
/// !!!每次当该脚本在Editor环境下Inspector的属性变化时会调用
/// </summary>
void OnValidate()
{
if (Pools != null)
{
for (int i = 0; i < Pools.Length; i++)
{
if (Pools[i].Target != null && string.IsNullOrEmpty(Pools[i].TargetName))
{
Pools[i].TargetName = Pools[i].Target.name;
}
}
}
}
//将当前对象返回到池中
internal static void ReturnPool(GameObject target)
{
target.gameObject.SetActive(false);
target.transform.SetParent(Instance.gameObjectPools.transform);
}
public void Add2Pool(PoolItemInfo poolItemInfo)
{
if (string.IsNullOrEmpty(poolItemInfo.TargetName))
{
throw new Exception("Pool池中不允许存在TargetName为空的项...");
}
if (poolItemInfo.Target == null && string.IsNullOrEmpty(poolItemInfo.ResourcesDirectory) && string.IsNullOrEmpty(poolItemInfo.AssetBundlePackageName)) //未绑定
{
throw new Exception(poolItemInfo.TargetName + "未绑定任何创建方式...");
}
if (dicPoolItems.ContainsKey(poolItemInfo.TargetName))
{
throw new System.Exception("CPool中元素" + poolItemInfo.Target.name + "池中已经存在了!");
}
PoolItem poolItem = new PoolItem();
poolItem.poolItemInfo = poolItemInfo;
poolItem.Preload(); //处理预加载
dicPoolItems.Add(poolItemInfo.TargetName, poolItem);
}
public PoolItem GetPoolItem(string prefabKey)
{
if (dicPoolItems.ContainsKey(prefabKey))
{
return dicPoolItems[prefabKey];
}
else
{
throw new System.Exception("池中不存在" + prefabKey + "物体");
}
}
public bool ContainsPool(string PoolName)
{
if (dicPoolItems.ContainsKey(PoolName))
{
return true;
}
return false;
}
/// <summary>
/// 启用对应Pool的携程
/// </summary>
/// <param name = "poolItem" > 对应Pool项 </ param >
public void StartPoolItemCoroutine(PoolItem poolItem)
{
if (poolItem.poolItemInfo.IntervalTimeGC != 0 && poolItem.poolItemInfo.OneShotGCNum != 0)
{
poolItem.coroutine = StartCoroutine(PoolItemCoroutine(poolItem));
poolItem.IsGCCoroutining = true;
}
}
/// <summary>
/// 停止对应Pool的协成
/// </summary>
/// <param name = "poolItem" ></ param >
public void StopPoolItemCoroutine(PoolItem poolItem)
{
StopCoroutine(poolItem.coroutine);
poolItem.IsGCCoroutining = false;
}
/// <summary>
/// PoolItem携程执行函数
/// </summary>
/// <param name = "poolItem" ></ param >
/// < returns ></ returns >
IEnumerator PoolItemCoroutine(PoolItem poolItem)
{
yield return new WaitForFixedUpdate(); //这里停留一帧给库预加载
while (poolItem.LstOffs.Count > 0 && poolItem.LstOffs.Count > poolItem.poolItemInfo.StayNums) //Pool中有
{
yield return poolItem.poolItemInfo.ws_IntervalTimeGC;
int GCNum = 0;
for (int i = 0; i < poolItem.LstOffs.Count; i++)
{
if (poolItem.LstOffs[i].gameObject.activeSelf == false)
{
Destroy(poolItem.LstOffs[i].gameObject);
poolItem.LstOffs.RemoveAt(i--);
GCNum++;
if (GCNum == poolItem.poolItemInfo.OneShotGCNum || poolItem.LstOffs.Count == poolItem.poolItemInfo.StayNums)
{
break;
}
}
}
}
StopPoolItemCoroutine(poolItem);
}
}
public class PoolItem
{
public PoolItemInfo poolItemInfo; //Pool所包含的信息项
public Coroutine coroutine; //当前PoolItem对象所关联的coroutine
bool isGCCoroutining = false; //当前PoolItem池是否在GC中
public bool IsGCCoroutining
{
get
{
return isGCCoroutining;
}
set
{
if (value != isGCCoroutining)
{
isGCCoroutining = value;
}
}
}
public List<GameObject> LstOffs = new List<GameObject>();
//出库
public GameObject DePool() //出cool
{
//目标GameObject
GameObject target = null;
//当前池中的个数已经大于等于限制个数了,则不再Instantiate该物体,开始复用
//(LstOffs.Count > 0 && !LstOffs[0].gameObject.activeSelf)表示的是当最初有未使用的时候就可以开始复用了
if (poolItemInfo.LimitNums != 0 && LstOffs.Count >= poolItemInfo.LimitNums || (LstOffs.Count > 0 && !LstOffs[0].gameObject.activeSelf))
{
//先去搜索关闭着的物体,如果找不到则强制第一个物体
int index = -1;
for (int i = 0; i < LstOffs.Count; i++)
{
if (!LstOffs[i].activeSelf)
{
index = i;
break;
}
}
//检测是否启用了安全模式
if (index == -1)
{
if (poolItemInfo.IsSafeMode)
{
throw new Exception("当前的对象" + poolItemInfo.TargetName + "在池中没有可用物体了,你可以取消安全模式强制复用!!!");
}
else
{
index = 0;
}
}
//取出当前模型后从队列位置移除然后重新追加到队尾
target = LstOffs[index];
LstOffs.RemoveAt(index);
LstOffs.Add(target);
}
else
{
//如果当前对象为空,则从内存中获取当前对象
if (poolItemInfo.Target == null)
{
LoadTargetInMemory();
}
//构造当前对象
target = InstantiateTarget();
}
target.SetActive(true);
return target;
}
//预加载
public void Preload()
{
if (poolItemInfo.Target == null)
{
LoadTargetInMemory();
}
for (int i = 0; i < poolItemInfo.PreloadNums; i++)
{
InstantiateTarget().SetActive(false);
}
}
//获得镜像在内存中
void LoadTargetInMemory()
{
//去Resources目录加载
if (!string.IsNullOrEmpty(poolItemInfo.ResourcesDirectory))
{
try
{
poolItemInfo.Target = Resources.Load<GameObject>(Path.Combine(poolItemInfo.ResourcesDirectory, poolItemInfo.TargetName));
}
catch
{
throw new System.Exception(poolItemInfo.TargetName + "在Resources下不存在");
}
}
//去PersistentDataPath下加载物体
if (!string.IsNullOrEmpty(poolItemInfo.AssetBundlePackageName))
{
try
{
AssetBundle assetbundle = AssetBundle.LoadFromFile(ConstantVariable.PersistentDataPathFile + ConstantVariable.buildPathDir + poolItemInfo.AssetBundlePackageName);
poolItemInfo.Target = assetbundle.LoadAsset<GameObject>(poolItemInfo.TargetName.ToLower());
assetbundle.Unload(false);
}
catch
{
throw new System.Exception(poolItemInfo.TargetName + "在AssetBundle下不存在");
}
}
}
//实例化物体出来
GameObject InstantiateTarget()
{
GameObject target = GameObject.Instantiate<GameObject>(poolItemInfo.Target);
target.name = poolItemInfo.TargetName;
target.transform.SetParent(C_Pool.Instance.gameObjectPools.transform);
LstOffs.Add(target);
if (poolItemInfo.IsUseGC && IsGCCoroutining == false)
{
C_Pool.Instance.StartPoolItemCoroutine(this);
}
return target;
}
}
[Serializable]
public class PoolItemInfo
{
public string TargetName; //目标名
//以下三个任选一个赋值
public GameObject Target; //目标物体
//Resources路径
public string ResourcesDirectory; //注意注意,不允许放在Resources根目录下,必须新建文件夹,即不允许将该字段设置为空 当要使用此加载方式的时候
//AssetBundle包名,由于一个包中会包含多个物体,所以这里需要单独指定包名
public string AssetBundlePackageName;
public int PreloadNums = 1; //预加载个数
public int StayNums = 1; //保留个数
public int LimitNums = 0; //限制个数 0表示无限
public int OneShotGCNum = 1; //每次回收多少個
public bool IsSafeMode = false; //是否安全模式
//安全模式时如果没有可用物体则报错
//非安全模式,如果没有可用的物体则强制使用当前第一个物体,取出使用并将其置于队尾
public bool IsUseGC = false; //是否开启GC回收
public float IntervalTimeGC = 60f; //每隔多长时间加入到回收池
WaitForSeconds _ws_IntervalTimeGC;
public WaitForSeconds ws_IntervalTimeGC //每个粒子等待回收时间对象
{
get
{
if (_ws_IntervalTimeGC == null)
{
_ws_IntervalTimeGC = new WaitForSeconds(IntervalTimeGC);
}
return _ws_IntervalTimeGC;
}
}
}
接下来我们再来看看如何直接通过Resources加载,这样就不用再拖拽到Target了
Resources加载