一:前言
在游戏的制作过程中,我们可能会碰到这样的情况,开枪的时候射出子弹,每个子弹即一个对象,正常情况,我们的处理方式可能会是每开一枪就Instantiate一个新的子弹,当子弹到达极限距离或者碰到物体后再Destroy销毁它。假设有射出1000发子弹,我们就会执行1000次这样的操作,反复创建销毁是一个内存反复分配与释放的过程,很容易产生内存碎片。在Unity中Instantiate和Destroy操作,不仅影响性能还容易产生内存碎片,在Unity中创建或是销毁对象需要付出昂贵的代价的。而用对象池可以解决性能开销问题
内存碎片:
内存碎片的意思是内存被分成一个一个的小块而不是整个大块,所有内存小块的大小可能很大但并不能使用,比如你想分配16byte的内存,此时如果有 20byte的空间就可以分配成功,但是如果这20byte是内存碎片,为两个10byte就会分配失败。所以,如果存在大量内存碎片,理论上有足够的可用内存,也会分配失败
很多游戏公司的游戏都会进行浸泡测试,让一个游戏跑好几天,查看是否崩溃来检测内存泄露等等,因为内存碎片产生毁灭性的结果是一个缓慢的过程
二:什么是对象池?
池可以理解为我们现实生活中的游泳池,里面装满了水,而在计算机世界里称为一定数量水的集合。所以池的概念和集合很相似。例如内存池就是一定数量的已经分配好的内存的集合。线程池就是一定数量的已经创建好的线程的集合,那么,对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合
对象池的原理就是预先分配一大块内存,生成满需要经常用的对象,然后直到不使用再全部释放
三:对象池的优点
对象池就是复用池中对象,没有分配内存和创建堆中对象的开销,进而减少垃圾收集器的负担, 避免内存抖动
四:对象池的应用场景
在制作过程中当经常有同一个Prefab要用到多次,需要反复实例化(Instantiate)和销毁(Desroy)。例如射击游戏中的子弹,跑酷游戏中的障碍物,路径等.....
五:对象池的使用
在游戏加载时把一批Prefab实例化好放在对象池中,游戏中用的时候拿出来(SetActive=true),不用的时候放回去(SetActive=false),避免反复实例化和销毁。可以理解为对象池就是一个租借处,需要的时候借出去,用完了再还回来
六:对象池的使用流程
——————————————————————ReusableObject脚本
using UnityEngine;
/// <summary>
/// 对象池中的每个物体都需要重写取出和放回的方法
/// </summary>
public abstract class ReusableObject : MonoBehaviour
{
/// <summary>
/// 取出时
/// </summary>
public abstract void OnSpawn();
/// <summary>
/// 回收时
/// </summary>
public abstract void OnUnSpawn();
}
——————————————————————SubPool脚本
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 每个子池子
/// </summary>
public class SubPool
{
//预制体
private GameObject prefab;
//父物体
private Transform parent;
//当前子池子中的所有物体
private List<GameObject> goList = new List<GameObject>();
/// <summary>
/// 初始化当前子池子
/// </summary>
/// <param name="prefab">预制体</param>
/// <param name="parent">父物体</param>
public SubPool(GameObject prefab, Transform parent)
{
this.prefab = prefab;
this.parent = parent;
}
#region public方法
/// <summary>
/// 取出物体
/// </summary>
public GameObject Spawn()
{
GameObject go = null;
go = GetGoFromList();
if (go == null)
{
go = GameObject.Instantiate(prefab, parent);
goList.Add(go);
}
go.GetComponent<ReusableObject>().OnSpawn();//取出物体时执行的方法
go.SetActive(true);
return go;
}
/// <summary>
/// 回收物体
/// </summary>
/// <param name="go">要回收的物体</param>
public void UnSpawn(GameObject go)
{
go.GetComponent<ReusableObject>().OnUnSpawn();//回收物体时执行的方法
go.SetActive(false);
}
/// <summary>
/// 回收所有物体
/// </summary>
public void UnSpawnAll()
{
foreach (var temp in goList)
{
if (temp.activeSelf)
{
UnSpawn(temp);
}
}
}
/// <summary>
/// 当前子池子中是否包含此游戏物体
/// </summary>
/// <param name="go">判断的游戏物体</param>
public bool Contains(GameObject go)
{
return goList.Contains(go);
}
#endregion
#region private方法
/// <summary>
/// 从goList中得到一个物体
/// </summary>
/// <returns></returns>
private GameObject GetGoFromList()
{
GameObject go = null;
foreach (var temp in goList)
{
if (temp.activeSelf == false)
{
go = temp;
}
}
return go;
}
#endregion
}
——————————————————————ObjectPool脚本
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 对象池总管理器
/// </summary>
public class ObjectPool : MonoBehaviour
{
//单例
public static ObjectPool Instance { get; set; }
//每个对象池物体信息的字典
private Dictionary<string, Transform> poolInfoDict = new Dictionary<string, Transform>();
//每个子池子的字典
private Dictionary<string, SubPool> subPoolDict = new Dictionary<string, SubPool>();
[Header("资源目录")]
public string resDir;
[Header("手动添加所有对象池物体")]
[Space(25)]
public ObjectInfo[] objects;
private void Awake()
{
Instance = this;
//初始化每个对象池物体信息的字典
foreach (var o in objects)
{
if (!poolInfoDict.ContainsKey(o.resName))
{
poolInfoDict.Add(o.resName, o.parent);
}
}
}
#region main
/// <summary>
/// 取出物体
/// </summary>
/// <param name="resName">资源名称</param>
public GameObject Spawn(string resName)
{
GameObject go = null;
if (!subPoolDict.ContainsKey(resName))
{
if (!CreatePool(resName))
{
Debug.LogWarning("创建池子失败:" + resName);
return go;
}
}
SubPool pool = subPoolDict[resName];
go = pool.Spawn();//取出物体
return go;
}
/// <summary>
/// 回收物体
/// </summary>
/// <param name="go">要回收的物体</param>
public void UnSpawn(GameObject go)
{
foreach (var pool in subPoolDict.Values)
{
if (pool.Contains(go))
{
pool.UnSpawn(go);
break;
}
}
}
/// <summary>
/// 回收所有物体
/// </summary>
public void UnSpawnAll()
{
foreach (var pool in subPoolDict.Values)
{
pool.UnSpawnAll();
}
}
#endregion
#region private方法
/// <summary>
/// 创建池子
/// </summary>
/// <param name="resName">资源名称</param>
private bool CreatePool(string resName)
{
if (!poolInfoDict.ContainsKey(resName))
{
Debug.LogWarning("不存在此子池子:" + resName + ",请在面板中手动添加资源名称");
return false;
}
string path = resDir + resName;
GameObject prefab = Resources.Load<GameObject>(path);
if (prefab == null)
{
Debug.LogWarning("路径不正确:" + path);
return false;
}
SubPool pool = new SubPool(prefab, poolInfoDict[resName]);
subPoolDict.Add(resName, pool);
return true;
}
#endregion
}
/// <summary>
/// 每个对象池中的物体信息
/// </summary>
[System.Serializable]
public class ObjectInfo
{
//资源名称
public string resName;
//父物体
public Transform parent;
}