在做UI的下拉列表和游戏中物体的实例化时,我们经常会用到对象池。因为对象池会把生成的物体回收起来供下次使用,节省很大的性能。
项目中通常会有多个不同类型的预制体需要被大量复制,所以我们首先要创建一个子池来产生不同类型的预制体,然后在创建一个大池子来存储管理所有被实例化的不同类型的预制体。首先来创建一个接口,用于需要在被创建和回收的时候需要初始化数据的功能函数:
public interface IPool
{
/// <summary>
/// 被创建时调用
/// </summary>
void Created();
/// <summary>
/// 生成时调用
/// </summary>
void Spawn();
/// <summary>
/// 回收时调用
/// </summary>
void UnSpawn();
}
然后来完成我们的子池脚本:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SubPool
{
//所有实例化出来的物体管理器
private List<GameObject> objects = new List<GameObject>();
//废弃不用的物体管理器
private List<GameObject> discardObjects=new List<GameObject>();
//对象池里物体和对应组件的关系 物体在被生成或者回收需要重置脚本数据的 脚本都要继承自IPool接口
Dictionary<GameObject,IPool> poolObjectDic=new Dictionary<GameObject, IPool>();
private GameObject prefab;
private GameObject Pool;//将不用的东西放到Pool池子里
public string Name { get { return prefab.name; } }
public SubPool( GameObject go, GameObject Pool)
{
this.prefab = go;
this.Pool = Pool;
}
//取出物体
public GameObject Spawn(Transform parent)
{
GameObject go = null;
IPool ipool = null;
if (discardObjects.Count > 0)
{
go = discardObjects[0];
discardObjects.Remove(go);
}
if (go == null)
{
go = GameObject.Instantiate(prefab);
objects.Add(go);
ipool = go.GetComponent<IPool>();
if (ipool!=null)
{
ipool.Created();
poolObjectDic.Add(go, ipool);
}
}
else
{
ipool = go.GetComponent<IPool>();
}
ipool?.Spawn();
go.transform.SetParent(parent);
go.SetActive(true);
return go;
}
//回收物体
public void UnSpawn(GameObject go)
{
if (objects.Contains(go))
{
go.SetActive(false);
discardObjects.Add(go);
go.transform.SetParent(Pool.transform);
IPool ipool = null;
if(poolObjectDic.TryGetValue(go,out ipool))
ipool.UnSpawn();
}
}
//回收所有物体
public void UnSpawnAll()
{
discardObjects.Clear();
for (int i = 0; i < objects.Count; i++)
{
if(objects[i]!=null)
UnSpawn(objects[i]);
}
}
public bool Contain(GameObject go)
{
return objects.Contains(go);
}
}
然后在创建一个大池子来管理这些被实例化的物体:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
public static ObjectPool Instance = null;
private GameObject Pool;
void Awake()
{
Instance = this; Pool = new GameObject("Pools");
}
/// <summary>
/// 资源目录
/// </summary>
public string ResourceDir = "Pool";
Dictionary<string, SubPool> pools = new Dictionary<string, SubPool>();
//取出物体
public GameObject Spawn(string name, Transform trans)
{
SubPool subPool = null;
if (!pools.ContainsKey(name))
CreatNewsubPool(name, trans);
subPool = pools[name];
return subPool.Spawn(trans);
}
//回收物体
public void UnSpawn(GameObject go)
{
SubPool pool = null;
foreach (var p in pools.Values)
{
if (p.Contain(go))
{
pool = p;
break;
}
}
pool.UnSpawn(go);
}
// 回收所有物体
public void UnSpawnAll()
{
foreach (var p in pools.Values)
{
p.UnSpawnAll();
}
}
//回收预制体名字为name的物体
public void UnspawnAll(string name)
{
if (pools.ContainsKey(name))
{
pools[name].UnSpawnAll();
}
}
//新建一个池子
private void CreatNewsubPool(string name, Transform parent)
{
string path = ResourceDir + "/" + name;
GameObject go = (Resources.Load<GameObject>(path));
SubPool pool = new SubPool(go, Pool);
pools.Add(name, pool);
}
}
ObjectPool是单利模式,我们获取的时候直接全局获取来控制。在使用对象池取物体时,我们调用Spawn函数并传入预制体的名字以及对象池里的物体要放到的父物体下,通常我们在调用之前一般会将之前的隐藏,比如刷新列表重新载入数据。代码如下:
//先全部回收
for (int i = 0; i<ParentTransform.childCount; i++)
{
ObjectPool.Instance.UnSpawn(ParentTransform.GetChild(i).gameObject);
}
//然后在取出
Debug.Log(roomDatas.Count);
for (int i = 0; i<roomDatas.Count; i++)
{
GameObject cloneGo = ObjectPool.Instance.Spawn("Item", ParentTransform);
cloneGo.transform.SetAsLastSibling();//开发刷新列表的时候我们会发现从池子里取出物体的顺序不对 我们要设置按照数据顺序显示
//cloneGo.GetComponent<Item>().Init(i, roomDatas[i],this);
}
以上是我在项目中遇到的对象池使用问题。
如果本博客对你有帮助,记得点关注哦!
2019.7.3更:
其实最初的模板是照着siki学院的一个老师讲的模板写的,后来在项目应用中发现局限性越来越大以及功能性越来越小。所以就在不断的迭代更改。
新增的:
1.可以回收指定名字的对象池内所有物体。
2.Gameobject也可以隐藏但不被回收。
2020.12.1更:
新增在创建 回收时调用事件。可用于自身数据的初始化。
测试代码如下:
public class TestItem : MonoBehaviour,IPool
{
public void Created()
{
Debug.Log("Created");
}
public void Spawn()
{
Debug.Log("Spawn");
}
public void UnSpawn()
{
Debug.Log("UnSpawn");
}
}
在创建和回收时打印结果如下: