一:前言

在游戏的制作过程中,我们可能会碰到这样的情况,开枪的时候射出子弹,每个子弹即一个对象,正常情况,我们的处理方式可能会是每开一枪就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;
}