4.对象池模块

  1. 4.对象池模块
    1. 4.1 知识点
      1. 对象池模块的作用
      2. 对象池的基本原理
      3. 对象池的注意事项
      4. GameObject对象池基础实现
        1. GameObject对象池实现
        2. GameObject对象池管理器实现
        3. 总对象池管理器实现
        4. 初步测试
        5. 注意
      5. GameObject对象池窗口布局优化
        1. 之前存在什么问题
        2. 如何解决窗口布局问题
        3. GameObjPoolMgr支持布局管理
        4. GameObjPool支持布局管理
        5. 窗口布局优化测试
      6. GameObject对象池对象上限优化
        1. 之前存在什么问题
        2. 如何解决对象上限问题
        3. 对象上限优化测试
      7. GameObject对象池数据结构选择
        1. 为什么需要特殊的数据结构
        2. 推荐的数据结构组合
        3. 数据结构选择的原因
        4. 抢占策略
      8. Object对象池基础实现
        1. 基础对象池实现
        2. 普通类对象池管理器实现
        3. 更新总对象池管理器
        4. 初步测试代码
      9. Object对象池数据重置优化
        1. 之前存在什么问题
        2. 如何解决数据重置问题
        3. 自定义对象池实现
        4. 对象池管理器优化
        5. 使用示例
        6. 拓展
    2. 4.2 知识点代码
      1. PoolMgr.cs(对象池管理器)
      2. GameObjPool.cs(GameObject对象池基类)
      3. ObjPool.cs(普通类对象池基类)
      4. GameObjPoolMgr.cs(GameObject对象池管理器)
      5. ObjPoolMgr.cs(普通类对象池管理器)
      6. DefaultGameObjPool.cs(默认GameObject对象池实现)
      7. DefaultObjPool.cs(默认普通类对象池实现)
      8. TestData.cs(测试数据结构类)
      9. TestDataPool.cs(自定义TestData对象池)
      10. DelayRemove.cs(延迟销毁脚本)
      11. GameObjectPoolUsageTest.cs(GameObject对象池使用测试)
      12. ObjectPoolUsageTest.cs(普通类对象池使用测试)
      13. ComprehensivePoolTest.cs(综合功能测试)

4.对象池模块


4.1 知识点

对象池模块的作用

在游戏开发中,我们经常需要频繁地创建和销毁对象,比如子弹、特效、敌人等。频繁的创建和销毁会导致以下问题:

  1. 内存分配开销:每次创建对象都需要分配内存
  2. 垃圾回收压力:频繁销毁对象会产生大量垃圾,增加GC压力
  3. 性能影响:特别是在移动设备上,频繁的内存分配和回收会严重影响性能

对象池模块的主要作用是通过重复利用已经创建的对象,避免频繁的创建和销毁,从而减少系统的内存分配和垃圾回收带来的开销


对象池的基本原理

对象池的基本原理可以用一个形象的比喻来理解:

  • 柜子:管理所有对象池的容器(Dictionary)
  • 抽屉:存储特定类型对象的容器(List、Stack、Queue等)
  • 东西:需要复用的对象

工作流程:

  1. 用时去拿:需要对象时,先从抽屉中取(没有就创造,存在就获取)
  2. 不用就还:使用完毕后,将对象分门别类地放入对应的抽屉中
  3. 统一管理:通过柜子统一管理所有抽屉



对象池的注意事项

  1. 对于Unity来说,对象池可以分成GameObject对象池和普通C#Object对象池。也可以自行分类,比如特效对象池,模型对象池等,看业务需求定。
  2. 对象池入池一般要有清洗方法,出池可以有初始化方法。
  3. 每个对象池的key可以是Type,也可能是Prefab路径,看情况定。
  4. 对象池注册可以手动注册,也可以走配置表或者反射扫描程序集。

GameObject对象池基础实现

对于GameObject对象池,我们需要实现基本的获取和归还功能。但是,在实际使用中,我们需要一个管理器来统一管理多个对象池。

GameObject对象池实现

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池(初步版本 - 基础功能实现)
/// </summary>
public class GameObjPool
{
    #region 字段

    /// <summary>
    /// 存储未使用的对象栈
    /// </summary>
    protected Stack<GameObject> _unuseStack = new Stack<GameObject>();

    /// <summary>
    /// 对象池名称
    /// </summary>
    protected string _poolName;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    /// <param name="poolName">对象池名称</param>
    protected GameObjPool(string poolName)
    {
        _poolName = poolName;
    }

    #endregion

    #region 抽象方法

    /// <summary>
    /// 加载GameObject预制体
    /// </summary>
    /// <returns>加载的GameObject预制体</returns>
    protected abstract GameObject LoadPrefab();

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中取出对象
    /// </summary>
    /// <returns>取出的GameObject对象</returns>
    public GameObject GetObj()
    {
        GameObject obj;

        if (_unuseStack.Count > 0)
        {
            // 从未使用的栈中取出对象
            obj = _unuseStack.Pop();
        }
        else
        {
            // 创建新对象
            obj = CreateNewObject();
        }

        // 激活对象
        obj.SetActive(true);
        return obj;
    }

    /// <summary>
    /// 将对象归还到对象池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 失活对象
        obj.SetActive(false);
        // 压入未使用栈
        _unuseStack.Push(obj);
    }

    /// <summary>
    /// 创建新对象
    /// </summary>
    /// <returns>创建的GameObject对象</returns>
    public GameObject CreateNewObject()
    {
        GameObject prefab = LoadPrefab();
        GameObject obj = Object.Instantiate(prefab);
        obj.name = _poolName;
        return obj;
    }

    #endregion
}

GameObject对象池管理器实现

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池管理器(初步版本)
/// 使用字典管理多个GameObjPool实例
/// </summary>
public class GameObjPoolMgr
{
    #region 字段

    /// <summary>
    /// GameObject对象池字典
    /// 键为对象名称,值为对应的GameObjPool
    /// </summary>
    private Dictionary<string, GameObjPool> _gameObjPoolDict = new Dictionary<string, GameObjPool>();

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
        // 如果没有对应的对象池,创建默认对象池
        if (!_gameObjPoolDict.ContainsKey(name))
        {
            _gameObjPoolDict[name] = new DefaultGameObjPool(name);
        }

        return _gameObjPoolDict[name].GetObj();
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 往对象池中归还对象
        _gameObjPoolDict[obj.name].ReturnObj(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _gameObjPoolDict.Clear();
    }

    #endregion
}

总对象池管理器实现

using UnityEngine;

/// <summary>
/// 对象池管理器(初步版本)
/// 统一管理GameObject对象池
/// </summary>
public class PoolMgr : Singleton<PoolMgr>
{
    #region 字段

    /// <summary>
    /// GameObject对象池管理器
    /// </summary>
    private GameObjPoolMgr gameObjPoolMgr;

    #endregion

    #region 构造函数

    private PoolMgr()
    {
        gameObjPoolMgr = new GameObjPoolMgr();
    }

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetGameObj(string name)
    {
        return gameObjPoolMgr.GetObj(name);
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnGameObj(GameObject obj)
    {
        gameObjPoolMgr.ReturnObj(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        gameObjPoolMgr.ClearPool();
    }

    #endregion
}

初步测试

// DefaultGameObjPool.cs
using UnityEngine;

public class DefaultGameObjPool : GameObjPool
{
    public DefaultGameObjPool(string poolName) : base(poolName)
    {
    }

    protected override GameObject LoadPrefab()
    {
        return Resources.Load<GameObject>(_poolName);
    }
}

// GameObjectPoolTest.cs
using UnityEngine;

public class GameObjectPoolTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("GameObject对象池测试:开始");
        
        // 通过PoolMgr获取对象
        GameObject obj1 = PoolMgr.Instance.GetGameObj("Cube");
        Debug.Log("获取对象:" + obj1.name);
        // 输出:获取对象:Cube

        // 归还对象
        PoolMgr.Instance.ReturnGameObj(obj1);
        Debug.Log("归还对象:" + obj1.name);
        // 输出:归还对象:Cube

        // 再次获取(应该复用之前的对象)
        GameObject obj2 = PoolMgr.Instance.GetGameObj("Cube");
        Debug.Log("再次获取对象:" + obj2.name);
        // 输出:再次获取对象:Cube
        Debug.Log("是否为同一对象:" + (obj1 == obj2));
        // 输出:是否为同一对象:True
    }
}

注意

LoadPrefab这个是继承GameObjPool可重写的。现在DefaultGameObjPool是使用Resource。实际上可以实现其他资源加载的GameObjPool,比如AB包,Addresable,YooAsset。同时也可以基于不同对象使用不同加载然后做成一张配置表,key可以就是一个字符串名字。

GameObject对象池窗口布局优化

之前存在什么问题

当项目变大,抽屉多了,对象多了,游戏中成百上千个对象在开发测试时不方便从Hierarchy窗口中查看对象获取信息。因此我们希望能优化一下Hierarchy窗口中的布局,将对象和抽屉的关系可视化。

问题分析:

  1. 对象混乱:大量失活的对象在Hierarchy中难以管理
  2. 关系不清:无法直观看到对象属于哪个对象池
  3. 调试困难:开发时难以快速定位和调试对象池问题

如何解决窗口布局问题

解决方案:

  1. 柜子管理自己的柜子根物体:创建统一的根对象管理所有对象池
  2. 抽屉管理自己的抽屉根物体:每个对象池有自己的根对象
  3. 失活时建立父子关系,激活时断开父子关系:通过父子关系实现可视化布局

注意: isOpenLayout可以在Unity编辑器环境才开启,这样打包出去后避免布局影响性能!

GameObjPoolMgr支持布局管理

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池管理器(支持布局管理)
/// 使用字典管理多个GameObjPool实例
/// </summary>
public class GameObjPoolMgr
{
    #region 字段

    /// <summary>
    /// GameObject对象池字典
    /// 键为对象名称,值为对应的GameObjPool
    /// </summary>
    private Dictionary<string, GameObjPool> _gameObjPoolDict = new Dictionary<string, GameObjPool>();

    /// <summary>
    /// 对象池根对象
    /// </summary>
    private GameObject _mgrRoot;

    /// <summary>
    /// 是否开启布局功能
    /// </summary>
    public static bool isOpenLayout = true;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public GameObjPoolMgr()
    {
        if (!isOpenLayout)
        {
            return;
        }

        _mgrRoot = new GameObject("GameObjPoolRoot");
        Object.DontDestroyOnLoad(_mgrRoot);
    }

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
        // 如果没有对应的对象池,创建默认对象池
        if (!_gameObjPoolDict.ContainsKey(name))
        {
            _gameObjPoolDict[name] = new DefaultGameObjPool(name, _mgrRoot);
        }

        return _gameObjPoolDict[name].GetObj();
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 往对象池中归还对象
        _gameObjPoolDict[obj.name].ReturnObj(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _gameObjPoolDict.Clear();
        if (_mgrRoot != null)
        {
            Object.Destroy(_mgrRoot);
            _mgrRoot = null;
        }
    }

    #endregion
}

GameObjPool支持布局管理

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池(支持布局管理)
/// </summary>
public abstract class GameObjPool
{
    #region 字段

    /// <summary>
    /// 存储未使用的对象栈
    /// </summary>
    protected Stack<GameObject> _unuseStack = new Stack<GameObject>();

    /// <summary>
    /// 抽屉根对象,用于进行布局管理的对象
    /// </summary>
    protected GameObject _rootObj;

    /// <summary>
    /// 对象池名称
    /// </summary>
    protected string _poolName;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    /// <param name="poolName">对象池名称</param>
    /// <param name="root">缓存池父对象</param>
    protected GameObjPool(string poolName, GameObject root)
    {
        _poolName = poolName;
        _rootObj = root;
    }

    #endregion

    #region 抽象方法

    /// <summary>
    /// 加载GameObject预制体
    /// </summary>
    /// <returns>加载的GameObject预制体</returns>
    protected abstract GameObject LoadPrefab();

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中取出对象
    /// </summary>
    /// <returns>取出的GameObject对象</returns>
    public GameObject GetObj()
    {
        GameObject obj;

        if (_unuseStack.Count > 0)
        {
            // 从未使用的栈中取出对象
            obj = _unuseStack.Pop();
        }
        else
        {
            // 创建新对象
            obj = CreateNewObject();
        }

        // 激活对象
        obj.SetActive(true);
        // 断开父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(null);
        }

        return obj;
    }

    /// <summary>
    /// 将对象归还到对象池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 失活对象
        obj.SetActive(false);
        // 放入对应抽屉的根物体中,建立父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(_rootObj.transform);
        }

        // 压入未使用栈
        _unuseStack.Push(obj);
    }

    /// <summary>
    /// 创建新对象
    /// </summary>
    /// <returns>创建的GameObject对象</returns>
    public GameObject CreateNewObject()
    {
        GameObject prefab = LoadPrefab();
        GameObject obj = Object.Instantiate(prefab);
        obj.name = _poolName;
        return obj;
    }

    #endregion
}

窗口布局优化测试

using UnityEngine;

public class LayoutOptimizationTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("窗口布局优化测试:开始");
        
        // 通过PoolMgr获取对象
        GameObject obj1 = PoolMgr.Instance.GetGameObj("Cube");
        Debug.Log("获取对象:" + obj1.name);
        // 输出:获取对象:Cube
        Debug.Log("对象父对象:" + (obj1.transform.parent == null ? "无" : obj1.transform.parent.name));
        // 输出:对象父对象:无

        // 归还对象
        PoolMgr.Instance.ReturnGameObj(obj1);
        Debug.Log("归还对象:" + obj1.name);
        // 输出:归还对象:Cube
        Debug.Log("对象父对象:" + (obj1.transform.parent == null ? "无" : obj1.transform.parent.name));
        // 输出:对象父对象:CubePoolRoot
    }
}

开启后类似这样的布局

GameObject对象池对象上限优化

之前存在什么问题

目前我们制作的对象池模块,理论上来说,当动态创建的对象长时间不放回抽屉,每次从对象池中动态获取对象时,会不停的新建对象,那么也就是对象的数量是没有上限的,场景上的某种对象可以存在n个。

问题分析:

  1. 无上限创建:对象数量没有限制,可能导致内存溢出
  2. 资源浪费:不重要的资源没必要让其无限加量
  3. 性能影响:大量对象会影响渲染性能和内存使用

如何解决对象上限问题

解决方案:

  1. 控制对象数量上限:设置对象池的最大容量
  2. LRU淘汰策略:当池满时,抢占”使用最久”的资源
  3. 记录使用状态:维护正在使用中的对象列表
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池(解决上限问题)
/// </summary>
public class GameObjPool
{
    #region 字段

    /// <summary>
    /// 存储未使用的对象栈
    /// </summary>
    protected Stack<GameObject> _unuseStack = new Stack<GameObject>();

    /// <summary>
    /// 记录正在使用中的对象列表(用于LRU淘汰)
    /// </summary>
    protected LinkedList<GameObject> _usedList = new LinkedList<GameObject>();

    /// <summary>
    /// 对象到链表节点的映射(用于O(1)删除)
    /// </summary>
    protected Dictionary<GameObject, LinkedListNode<GameObject>> _gameObj2NodeDict = new Dictionary<GameObject, LinkedListNode<GameObject>>();

    /// <summary>
    /// 对象池上限,场景上同时存在的对象的最大个数
    /// </summary>
    protected readonly int MaxNum;

    /// <summary>
    /// 抽屉根对象,用于进行布局管理的对象
    /// </summary>
    protected GameObject _rootObj;

    /// <summary>
    /// 对象池名称
    /// </summary>
    protected string _poolName;

    #endregion

    #region 属性

    /// <summary>
    /// 获取容器中未使用对象的数量
    /// </summary>
    public int UnuseCount => _unuseStack.Count;

    /// <summary>
    /// 获取正在使用中的对象数量
    /// </summary>
    public int UsedCount => _usedList.Count;

    /// <summary>
    /// 判断是否需要创建新对象
    /// </summary>
    public bool NeedCreate => UnuseCount == 0 && _usedList.Count < MaxNum;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    /// <param name="poolName">对象池名称</param>
    /// <param name="root">缓存池父对象</param>
    /// <param name="maxNum">最大对象数量,默认100</param>
    protected GameObjPool(string poolName, GameObject root, int maxNum = 100)
    {
        _poolName = poolName;
        _rootObj = root;
        MaxNum = maxNum;
    }

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中取出对象
    /// </summary>
    /// <returns>取出的GameObject对象</returns>
    public GameObject GetObj()
    {
        GameObject obj;

        if (UnuseCount > 0)
        {
            // 从未使用的栈中取出对象(LIFO)
            obj = _unuseStack.Pop();
        }
        else if (_usedList.Count < MaxNum)
        {
            // 创建新对象
            obj = CreateNewObject();
        }
        else
        {
            // 池满,抢占最早使用的对象(FIFO)
            obj = _usedList.First.Value;
            _usedList.RemoveFirst();
            _gameObj2NodeDict.Remove(obj);
        }

        // 添加到使用列表的尾部(最近使用)
        LinkedListNode<GameObject> node = _usedList.AddLast(obj);
        _gameObj2NodeDict[obj] = node;

        // 激活对象
        obj.SetActive(true);
        // 断开父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(null);
        }

        return obj;
    }

    /// <summary>
    /// 将对象归还到对象池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 失活对象
        obj.SetActive(false);
        // 放入对应抽屉的根物体中,建立父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(_rootObj.transform);
        }

        // 从使用列表中移除
        if (_gameObj2NodeDict.TryGetValue(obj, out LinkedListNode<GameObject> node))
        {
            _usedList.Remove(node);
            _gameObj2NodeDict.Remove(obj);
        }

        // 压入未使用栈(LIFO)
        _unuseStack.Push(obj);
    }

    #endregion
}

对象上限优化测试

using UnityEngine;

public class ObjectLimitTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("对象上限优化测试:开始");
        
        // 通过PoolMgr获取对象(注意:这里演示的是基础功能,实际的上限控制需要在GameObjPoolMgr中实现)
        GameObject obj1 = PoolMgr.Instance.GetGameObj("Cube");
        GameObject obj2 = PoolMgr.Instance.GetGameObj("Cube");
        GameObject obj3 = PoolMgr.Instance.GetGameObj("Cube");
        
        Debug.Log($"创建3个对象:{obj1.name},{obj2.name},{obj3.name}");
        // 输出:创建3个对象:Cube,Cube,Cube

        // 归还对象
        PoolMgr.Instance.ReturnGameObj(obj1);
        PoolMgr.Instance.ReturnGameObj(obj2);
        PoolMgr.Instance.ReturnGameObj(obj3);
        
        Debug.Log("归还所有对象");
        // 输出:归还所有对象

        // 再次获取(应该复用之前的对象)
        GameObject obj4 = PoolMgr.Instance.GetGameObj("Cube");
        Debug.Log($"再次获取对象:{obj4.name}");
        // 输出:再次获取对象:Cube
        Debug.Log($"是否为同一对象:{obj4 == obj1}");
        // 输出:是否为同一对象:True
    }
}

GameObject对象池数据结构选择

为什么需要特殊的数据结构

当对象池达到上限时,我们需要实现”超限就抢占最早被借出的活跃对象”的功能。这要求我们能够:

  1. 快速找到最早借出的对象:需要维护借出顺序
  2. 快速删除任意活跃对象:归还时可能不是最早借出的
  3. 高效管理空闲对象:频繁的获取和归还操作

推荐的数据结构组合

核心设计:三个容器协同工作

  1. 空闲池Stack<GameObject>(LIFO)

    • 最后归还的对象最先被复用
    • 符合Unity内置ObjectPool的设计理念
    • 操作复杂度:O(1)
  2. 活跃表LinkedList<GameObject>(按借出顺序排队)

    • 队头 = 最早借出的对象(最先被抢占)
    • 队尾 = 最新借出的对象
    • 支持O(1)的插入和删除操作
  3. 索引表Dictionary<GameObject, LinkedListNode<GameObject>>

    • 将GameObject映射到其在链表中的节点
    • 实现O(1)的节点定位和删除
    • 这是实现LRU/FIFO淘汰的经典组合

数据结构选择的原因

为什么不推荐用 Queue 做“空闲池

  • 结论
    空闲对象用 Stack(LIFO)更合适Queue(FIFO)不是不行,但在 Unity 的 GameObject 池里通常没有优势,反而会带来“冷对象优先复用”的开销。

  • 语义

    • StackLIFO,总是优先复用“刚放回”的对象(更“热”)。
    • QueueFIFO,优先复用“最早放回”的对象(更“冷”)。
  • 实现与生态

    • Unity 官方 ObjectPool<T> 就是基于栈的实现,说明官方默认的“空闲区”语义是 LIFO。
    • 业界通用的 Apache Commons Pool 也默认 LIFO(可改为 FIFO),空闲时借用会返回“最后归还”的实例。
  • 局部性与开销

    • LIFO 往往复用刚刚用过、数据/分配路径更“热”的实例;社区经验也倾向于 Stack 更有局部性、更快。

为什么选择LinkedList + Dictionary?

  • LinkedList支持O(1)的节点插入和删除
  • Dictionary提供O(1)的节点定位
  • 这是实现”抢占最早/最久未用”的标准数据结构组合

抢占策略

FIFO抢占(按借出顺序)

  • 只在借出时将对象添加到链表尾部
  • 超限时从链表头部抢占最早借出的对象
  • 适合大多数游戏场景

LRU抢占(按最近使用)

  • 对象被使用时将其节点移动到链表尾部
  • 超限时抢占链表头部的对象(最久未用)
  • 适合需要”热度”管理的场景

Object对象池基础实现

对于不继承MonoBehaviour的普通类对象池,我们需要实现泛型对象池。同样,我们也需要管理器来统一管理多个对象池。

基础对象池实现

using System.Collections.Generic;

/// <summary>
/// 普通类对象池(初步版本 - 基础功能实现)
/// </summary>
/// <typeparam name="T">要存储的对象类型</typeparam>
public class ObjPool<T> where T : class, new()
{
    #region 字段

    /// <summary>
    /// 对象池队列
    /// </summary>
    protected Queue<T> _objQueue = new Queue<T>();

    #endregion

    #region 属性

    /// <summary>
    /// 获取池中可用对象的数量
    /// </summary>
    public int Count => _objQueue.Count;

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中获取对象
    /// </summary>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj()
    {
        // 池子当中是否有可以复用的内容
        // 从队列中取出对象进行复用 否则创建新对象
        T obj = _objQueue.Count > 0 ? _objQueue.Dequeue() : new T();
        return obj;
    }

    /// <summary>
    /// 将对象归还到池子中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(T obj)
    {
        // 如果想要归还null对象,是不被允许的
        if (obj == null)
        {
            return;
        }

        // 放入池子中
        _objQueue.Enqueue(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _objQueue.Clear();
    }

    #endregion
}

普通类对象池管理器实现

using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 普通类对象池管理器(初步版本)
/// 使用字典管理多个ObjPool实例
/// </summary>
public class ObjPoolMgr
{
    #region 字段

    /// <summary>
    /// 数据结构类对象池字典
    /// 用于存储不继承MonoBehaviour的对象
    /// </summary>
    private Dictionary<Type, object> _objPoolDict = new Dictionary<Type, object>();

    #endregion

    #region 普通类对象池管理

    /// <summary>
    /// 获取自定义的数据结构类和逻辑类对象
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj<T>() where T : class, new()
    {
        Type type = typeof(T);

        // 有池子
        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                return pool.GetObj();
            }

            Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            return null;
        }
        else
        {
            // 没有池子,创建新池子
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            return pool.GetObj();
        }
    }

    /// <summary>
    /// 将自定义数据结构类和逻辑类对象归还到池子中
    /// </summary>
    /// <typeparam name="T">对应类型</typeparam>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj<T>(T obj) where T : class, new()
    {
        // 如果想要归还null对象,是不被允许的
        if (obj == null)
        {
            return;
        }

        Type type = typeof(T);

        // 有池子
        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                pool.ReturnObj(obj);
            }
            else
            {
                Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            }
        }
        else
        {
            // 没有池子,创建新池子
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            pool.ReturnObj(obj);
        }
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _objPoolDict.Clear();
    }

    #endregion
}

更新总对象池管理器

using UnityEngine;

/// <summary>
/// 对象池管理器(支持普通类对象池)
/// 统一管理GameObject对象池和普通类对象池
/// </summary>
public class PoolMgr : Singleton<PoolMgr>
{
    #region 字段

    /// <summary>
    /// GameObject对象池管理器
    /// </summary>
    private GameObjPoolMgr gameObjPoolMgr;

    /// <summary>
    /// 普通类对象池管理器
    /// </summary>
    private ObjPoolMgr objPoolMgr;

    #endregion

    #region 构造函数

    private PoolMgr()
    {
        gameObjPoolMgr = new GameObjPoolMgr();
        objPoolMgr = new ObjPoolMgr();
    }

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetGameObj(string name)
    {
        return gameObjPoolMgr.GetObj(name);
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnGameObj(GameObject obj)
    {
        gameObjPoolMgr.ReturnObj(obj);
    }

    #endregion

    #region 普通类对象池管理

    /// <summary>
    /// 获取自定义的数据结构类和逻辑类对象
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj<T>() where T : class, new()
    {
        return objPoolMgr.GetObj<T>();
    }

    /// <summary>
    /// 将自定义数据结构类和逻辑类对象归还到池子中
    /// </summary>
    /// <typeparam name="T">对应类型</typeparam>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj<T>(T obj) where T : class, new()
    {
        objPoolMgr.ReturnObj<T>(obj);
    }

    #endregion

    #region 统一管理

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        gameObjPoolMgr.ClearPool();
        objPoolMgr.ClearPool();
    }

    #endregion
}

初步测试代码

// TestData.cs
using UnityEngine;

public class TestData
{
    public int i = 0;
    public string str = "";
    public object o = null;

    public TestData()
    {
        Debug.Log("TestData构造函数被调用");
    }
}

// DefaultObjPool.cs
public class DefaultObjPool<T> : ObjPool<T> where T : class, new()
{
    public DefaultObjPool() : base()
    {
    }
}

// ObjectPoolTest.cs
using UnityEngine;

public class ObjectPoolTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("普通类对象池测试:开始");
        
        // 通过PoolMgr获取对象
        TestData data1 = PoolMgr.Instance.GetObj<TestData>();
        data1.i = 100;
        data1.str = "测试数据";
        Debug.Log($"获取对象,i={data1.i},str={data1.str}");
        // 输出:TestData构造函数被调用
        // 输出:获取对象,i=100,str=测试数据

        // 归还对象
        PoolMgr.Instance.ReturnObj(data1);
        Debug.Log("归还对象");

        // 再次获取(应该复用之前的对象)
        TestData data2 = PoolMgr.Instance.GetObj<TestData>();
        Debug.Log($"再次获取对象,i={data2.i},str={data2.str}");
        // 输出:再次获取对象,i=100,str=测试数据(数据没有被重置)
        Debug.Log($"是否为同一对象:{data1 == data2}");
        // 输出:是否为同一对象:True
    }
}

Object对象池数据重置优化

之前存在什么问题

在基础实现中,我们发现普通类对象池存在一个问题:当对象被归还到池中后,再次获取时,对象的数据没有被重置,仍然保留着之前使用时的数据。

问题分析:

  1. 数据残留:对象归还后数据没有被清理
  2. 状态混乱:再次使用时可能产生意外的行为
  3. 安全隐患:敏感数据可能被泄露

如何解决数据重置问题

解决方案:

  1. 添加重置机制:在归还对象时重置数据
  2. 提供虚方法:允许子类自定义重置逻辑
  3. 实现默认重置:为常见数据类型提供默认重置
using System.Collections.Generic;

/// <summary>
/// 普通类对象池(支持数据重置)
/// </summary>
/// <typeparam name="T">要存储的对象类型</typeparam>
public abstract class ObjPool<T> where T : class, new()
{
    #region 字段

    /// <summary>
    /// 对象池队列
    /// </summary>
    protected Queue<T> _objQueue = new Queue<T>();

    #endregion

    #region 属性

    /// <summary>
    /// 获取池中可用对象的数量
    /// </summary>
    public int Count => _objQueue.Count;

    #endregion

    #region 虚方法

    /// <summary>
    /// 从对象池中取出对象时的初始化
    /// 子类可以重写此方法来执行自定义初始化
    /// </summary>
    /// <param name="obj">取出的对象</param>
    protected virtual void OnGetObject(T obj)
    {
        // 默认实现为空,子类可以重写
    }

    /// <summary>
    /// 将对象放回对象池时的清理
    /// 子类可以重写此方法来执行自定义清理
    /// </summary>
    /// <param name="obj">放回的对象</param>
    protected virtual void OnReturnObject(T obj)
    {
        // 默认实现为空,子类可以重写
        // 如果需要重置对象,请在子类中重写此方法
    }

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中获取对象
    /// </summary>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj()
    {
        // 池子当中是否有可以复用的内容
        // 从队列中取出对象进行复用 否则创建新对象
        T obj = _objQueue.Count > 0 ? _objQueue.Dequeue() : new T();

        // 执行初始化
        OnGetObject(obj);

        return obj;
    }

    /// <summary>
    /// 将对象归还到池子中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(T obj)
    {
        // 如果想要归还null对象,是不被允许的
        if (obj == null)
        {
            return;
        }

        // 执行清理
        OnReturnObject(obj);

        // 放入池子中
        _objQueue.Enqueue(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _objQueue.Clear();
    }

    #endregion
}

自定义对象池实现

using UnityEngine;

/// <summary>
/// 玩家数据结构类
/// 用于演示自定义对象池的使用
/// </summary>
public class PlayerData
{
    public int playerId = 0;
    public string playerName = "";
    public int level = 1;
    public int experience = 0;

    public PlayerData()
    {
        Debug.Log("PlayerData构造函数被调用");
    }
}
using UnityEngine;

/// <summary>
/// TestData对象池
/// 自定义TestData的重置逻辑
/// </summary>
public class TestDataPool : ObjPool<TestData>
{
    /// <summary>
    /// 将对象放回对象池时的清理
    /// </summary>
    /// <param name="obj">放回的TestData对象</param>
    protected override void OnReturnObject(TestData obj)
    {
        // 重置TestData的数据
        obj.i = 0;
        obj.str = "";
        obj.o = null;
    }
}

/// <summary>
/// PlayerData对象池
/// 自定义PlayerData的重置逻辑
/// </summary>
public class PlayerDataPool : ObjPool<PlayerData>
{
    protected override void OnReturnObject(PlayerData obj)
    {
        // 重置PlayerData的数据
        obj.playerId = 0;
        obj.playerName = "";
        obj.level = 1;
        obj.experience = 0;
    }
}

对象池管理器优化

using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 普通类对象池管理器(支持自定义对象池)
/// 使用字典管理多个ObjPool实例
/// </summary>
public class ObjPoolMgr
{
    #region 字段

    /// <summary>
    /// 数据结构类对象池字典
    /// 用于存储不继承MonoBehaviour的对象
    /// </summary>
    private Dictionary<Type, object> _objPoolDict = new Dictionary<Type, object>();

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public ObjPoolMgr()
    {
    }

    #endregion

    #region 对象池注册

    /// <summary>
    /// 注册自定义对象池
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    /// <param name="pool">自定义对象池实例</param>
    public void RegisterPool<T>(ObjPool<T> pool) where T : class, new()
    {
        Type type = typeof(T);
        _objPoolDict[type] = pool;
    }

    #endregion

    #region 普通类对象池管理

    /// <summary>
    /// 获取自定义的数据结构类和逻辑类对象
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj<T>() where T : class, new()
    {
        Type type = typeof(T);

        // 有池子
        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                return pool.GetObj();
            }

            Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            return null;
        }
        else
        {
            // 没有池子,创建默认对象池
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            return pool.GetObj();
        }
    }

    /// <summary>
    /// 将自定义数据结构类和逻辑类对象归还到池子中
    /// </summary>
    /// <typeparam name="T">对应类型</typeparam>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj<T>(T obj) where T : class, new()
    {
        if (obj == null)
        {
            return;
        }

        Type type = typeof(T);

        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                pool.ReturnObj(obj);
            }
            else
            {
                Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            }
        }
        else
        {
            // 没有池子,创建默认对象池
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            pool.ReturnObj(obj);
        }
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _objPoolDict.Clear();
    }

    #endregion
}

使用示例

using UnityEngine;

public class DataResetTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("数据重置测试:开始");
        
        // 手动注册自定义对象池
        PoolMgr.Instance.RegisterObjPool(new TestDataPool());
        PoolMgr.Instance.RegisterObjPool(new PlayerDataPool());
        
        // 测试TestData对象池
        TestData data1 = PoolMgr.Instance.GetObj<TestData>();
        data1.i = 100;
        data1.str = "测试数据";
        Debug.Log($"获取TestData,i={data1.i},str={data1.str}");
        // 输出:获取TestData,i=100,str=测试数据

        PoolMgr.Instance.ReturnObj(data1);
        Debug.Log("归还TestData");

        TestData data2 = PoolMgr.Instance.GetObj<TestData>();
        Debug.Log($"再次获取TestData,i={data2.i},str={data2.str}");
        // 输出:再次获取TestData,i=0,str=(数据被重置)
        Debug.Log($"是否为同一对象:{data1 == data2}");
        // 输出:是否为同一对象:True

        // 测试PlayerData对象池
        PlayerData player1 = PoolMgr.Instance.GetObj<PlayerData>();
        player1.playerId = 12345;
        player1.playerName = "测试玩家";
        Debug.Log($"获取PlayerData,ID={player1.playerId},Name={player1.playerName}");
        // 输出:获取PlayerData,ID=12345,Name=测试玩家

        PoolMgr.Instance.ReturnObj(player1);
        Debug.Log("归还PlayerData");

        PlayerData player2 = PoolMgr.Instance.GetObj<PlayerData>();
        Debug.Log($"再次获取PlayerData,ID={player2.playerId},Name={player2.playerName}");
        // 输出:再次获取PlayerData,ID=0,Name=(数据被重置)
    }
}

拓展

如果有需要,可以传入程序集扫描继承了对象池基类的自定义对象池然后自定义注册,避免每次新做一个对象池手动传入。GetObj,OnGetObject方法可以搞个可选参数,方便传入不同参数进行不同的初始化。


4.2 知识点代码

PoolMgr.cs(对象池管理器)

using UnityEngine;

/// <summary>
/// 对象池管理器
/// 统一管理GameObject对象池和普通类对象池
/// 提供与原有API兼容的接口
/// </summary>
public class PoolMgr : Singleton<PoolMgr>
{
    #region 字段

    /// <summary>
    /// GameObject对象池管理器
    /// </summary>
    private GameObjPoolMgr gameObjPoolMgr;

    /// <summary>
    /// 普通类对象池管理器
    /// </summary>
    private ObjPoolMgr objPoolMgr;

    /// <summary>
    /// 是否开启布局功能
    /// </summary>
    public static bool isOpenLayout = true;

    #endregion

    #region 构造函数

    private PoolMgr()
    {
        gameObjPoolMgr = new GameObjPoolMgr();
        objPoolMgr = new ObjPoolMgr();
    }

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetGameObj(string name)
    {
        return gameObjPoolMgr.GetObj(name);
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnGameObj(GameObject obj)
    {
        gameObjPoolMgr.ReturnObj(obj);
    }

    #endregion

    #region 普通类对象池管理

    /// <summary>
    /// 获取自定义的数据结构类和逻辑类对象
    /// 不继承MonoBehaviour的对象池实现
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj<T>() where T : class, new()
    {
        return objPoolMgr.GetObj<T>();
    }

    /// <summary>
    /// 将自定义数据结构类和逻辑类对象归还到池子中
    /// </summary>
    /// <typeparam name="T">对应类型</typeparam>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj<T>(T obj) where T : class, new()
    {
        objPoolMgr.ReturnObj<T>(obj);
    }

    /// <summary>
    /// 注册自定义对象池
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    /// <param name="pool">自定义对象池实例</param>
    public void RegisterObjPool<T>(ObjPool<T> pool) where T : class, new()
    {
        objPoolMgr.RegisterPool<T>(pool);
    }

    #endregion

    #region 统一管理

    /// <summary>
    /// 清除整个对象池中的数据
    /// 主要用于场景切换时清理资源
    /// </summary>
    public void ClearPool()
    {
        gameObjPoolMgr.ClearPool();
        objPoolMgr.ClearPool();
    }

    #endregion
}

GameObjPool.cs(GameObject对象池基类)

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池抽象基类
/// 提供可继承的对象池实现,支持自定义加载方式和数量上限
/// 使用栈(LIFO)管理对象,符合Unity官方ObjectPool设计
/// </summary>
public abstract class GameObjPool
{
    #region 字段

    /// <summary>
    /// 存储未使用的对象栈
    /// </summary>
    protected Stack<GameObject> _unuseStack = new Stack<GameObject>();

    /// <summary>
    /// 记录正在使用中的对象列表(用于LRU淘汰)
    /// </summary>
    protected LinkedList<GameObject> _usedList = new LinkedList<GameObject>();

    /// <summary>
    /// 对象到链表节点的映射(用于O(1)删除)
    /// </summary>
    protected Dictionary<GameObject, LinkedListNode<GameObject>> _gameObj2NodeDict = new Dictionary<GameObject, LinkedListNode<GameObject>>();

    /// <summary>
    /// 对象池上限,场景上同时存在的对象的最大个数
    /// </summary>
    protected readonly int MaxNum;

    /// <summary>
    /// 抽屉根对象,用于进行布局管理的对象
    /// </summary>
    protected GameObject _rootObj;

    /// <summary>
    /// 对象池名称
    /// </summary>
    protected string _poolName;

    #endregion

    #region 属性

    /// <summary>
    /// 获取容器中未使用对象的数量
    /// </summary>
    public int UnuseCount => _unuseStack.Count;

    /// <summary>
    /// 获取正在使用中的对象数量
    /// </summary>
    public int UsedCount => _usedList.Count;

    /// <summary>
    /// 判断是否需要创建新对象
    /// 当使用中的对象数量小于最大容量时返回true
    /// </summary>
    public bool NeedCreate => UnuseCount == 0 && _usedList.Count < MaxNum;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    /// <param name="poolName">对象池名称</param>
    /// <param name="root">缓存池父对象</param>
    /// <param name="maxNum">最大对象数量,默认100</param>
    /// <param name="warmCount">预热数量,默认0</param>
    protected GameObjPool(string poolName, GameObject root, int maxNum = 100, int warmCount = 0)
    {
        _poolName = poolName;
        _rootObj = root;
        MaxNum = maxNum;

        // 预热对象
        for (int i = 0; i < Mathf.Min(warmCount, MaxNum); i++)
        {
            GameObject obj = CreateNewObject();
            obj.SetActive(false);
            if (GameObjPoolMgr.isOpenLayout)
            {
                obj.transform.SetParent(_rootObj.transform);
            }
            _unuseStack.Push(obj);
        }
    }

    #endregion

    #region 抽象方法

    /// <summary>
    /// 加载GameObject预制体
    /// 子类可以重写此方法来自定义加载方式
    /// </summary>
    /// <returns>加载的GameObject预制体</returns>
    protected abstract GameObject LoadPrefab();

    #endregion

    #region 虚方法

    /// <summary>
    /// 从对象池中取出对象时的初始化
    /// 子类可以重写此方法来执行自定义初始化
    /// </summary>
    /// <param name="obj">取出的GameObject对象</param>
    protected virtual void OnGetObject(GameObject obj)
    {
        // 默认实现为空,子类可以重写
    }

    /// <summary>
    /// 将对象放回对象池时的清理
    /// 子类可以重写此方法来执行自定义清理
    /// </summary>
    /// <param name="obj">放回的GameObject对象</param>
    protected virtual void OnReturnObject(GameObject obj)
    {
        // 默认实现为空,子类可以重写
    }

    /// <summary>
    /// 对象被抢占时的回调
    /// 当池满时,会抢占最早使用的对象
    /// </summary>
    /// <param name="obj">被抢占的对象</param>
    protected virtual void OnPreemptObject(GameObject obj)
    {
        // 默认实现为空,子类可以重写
    }

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中取出对象
    /// </summary>
    /// <returns>取出的GameObject对象</returns>
    public GameObject GetObj()
    {
        GameObject obj;

        if (UnuseCount > 0)
        {
            // 从未使用的栈中取出对象(LIFO)
            obj = _unuseStack.Pop();
        }
        else if (_usedList.Count < MaxNum)
        {
            // 创建新对象
            obj = CreateNewObject();
        }
        else
        {
            // 池满,抢占最早使用的对象(FIFO)
            obj = _usedList.First.Value;
            _usedList.RemoveFirst();
            _gameObj2NodeDict.Remove(obj);
            OnPreemptObject(obj);
        }

        // 添加到使用列表的尾部(最近使用)
        LinkedListNode<GameObject> node = _usedList.AddLast(obj);
        _gameObj2NodeDict[obj] = node;

        // 激活对象
        obj.SetActive(true);
        // 断开父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(null);
        }

        // 执行初始化
        OnGetObject(obj);

        return obj;
    }

    /// <summary>
    /// 将对象归还到对象池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 执行清理
        OnReturnObject(obj);

        // 失活对象
        obj.SetActive(false);
        // 放入对应抽屉的根物体中,建立父子关系
        if (GameObjPoolMgr.isOpenLayout)
        {
            obj.transform.SetParent(_rootObj.transform);
        }

        // 从使用列表中移除
        if (_gameObj2NodeDict.TryGetValue(obj, out LinkedListNode<GameObject> node))
        {
            _usedList.Remove(node);
            _gameObj2NodeDict.Remove(obj);
        }

        // 压入未使用栈(LIFO)
        _unuseStack.Push(obj);
    }

    /// <summary>
    /// 创建新对象并添加到使用列表
    /// </summary>
    /// <returns>创建的GameObject对象</returns>
    public GameObject CreateNewObject()
    {
        GameObject prefab = LoadPrefab();
        GameObject obj = Object.Instantiate(prefab);
        obj.name = _poolName;
        return obj;
    }

    #endregion
}

ObjPool.cs(普通类对象池基类)

using System.Collections.Generic;

/// <summary>
/// 普通类对象池抽象基类
/// 提供可继承的对象池实现,支持自定义创建和重置逻辑
/// </summary>
/// <typeparam name="T">要存储的对象类型</typeparam>
public abstract class ObjPool<T> where T : class, new()
{
    #region 字段

    /// <summary>
    /// 对象池队列
    /// </summary>
    protected Queue<T> _objQueue = new Queue<T>();

    #endregion

    #region 属性

    /// <summary>
    /// 获取池中可用对象的数量
    /// </summary>
    public int Count => _objQueue.Count;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    protected ObjPool()
    {
    }

    #endregion

    #region 抽象方法

    /// <summary>
    /// 创建新对象
    /// 子类可以重写此方法来自定义创建逻辑
    /// </summary>
    /// <returns>创建的对象</returns>
    protected virtual T CreateObject()
    {
        return new T();
    }

    #endregion

    #region 虚方法

    /// <summary>
    /// 从对象池中取出对象时的初始化
    /// 子类可以重写此方法来执行自定义初始化
    /// </summary>
    /// <param name="obj">取出的对象</param>
    protected virtual void OnGetObject(T obj)
    {
        // 默认实现为空,子类可以重写
    }

    /// <summary>
    /// 将对象放回对象池时的清理
    /// 子类可以重写此方法来执行自定义清理
    /// </summary>
    /// <param name="obj">放回的对象</param>
    protected virtual void OnReturnObject(T obj)
    {
        // 默认实现为空,子类可以重写
        // 如果需要重置对象,请在子类中重写此方法
    }

    #endregion

    #region 对象管理方法

    /// <summary>
    /// 从对象池中获取对象
    /// </summary>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj()
    {
        // 池子当中是否有可以复用的内容
        // 从队列中取出对象进行复用 否则创建新对象
        T obj = _objQueue.Count > 0 ? _objQueue.Dequeue() : CreateObject();

        // 执行初始化
        OnGetObject(obj);

        return obj;
    }

    /// <summary>
    /// 将对象归还到池子中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(T obj)
    {
        // 如果想要归还null对象,是不被允许的
        if (obj == null)
        {
            return;
        }

        // 执行清理
        OnReturnObject(obj);

        // 放入池子中
        _objQueue.Enqueue(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// 主要用于场景切换时清理资源
    /// </summary>
    public void ClearPool()
    {
        _objQueue.Clear();
    }

    #endregion
}

GameObjPoolMgr.cs(GameObject对象池管理器)

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject对象池管理器
/// 使用字典管理多个GameObjPool实例
/// 不再负责加载逻辑,由各个Pool自己决定
/// </summary>
public class GameObjPoolMgr
{
    #region 字段

    /// <summary>
    /// GameObject对象池字典
    /// 键为对象名称,值为对应的GameObjPool
    /// </summary>
    private Dictionary<string, GameObjPool> _gameObjPoolDict = new Dictionary<string, GameObjPool>();

    /// <summary>
    /// 对象池根对象
    /// </summary>
    private GameObject _mgrRoot;

    /// <summary>
    /// 是否开启布局功能
    /// </summary>
    public static bool isOpenLayout = true;

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public GameObjPoolMgr()
    {
        if (!isOpenLayout)
        {
            return;
        }

        _mgrRoot = new GameObject("GameObjPoolRoot");
        Object.DontDestroyOnLoad(_mgrRoot);
    }

    #endregion

    #region GameObject对象池管理

    /// <summary>
    /// 从对象池中获取GameObject对象
    /// </summary>
    /// <param name="name">对象名称</param>
    /// <returns>从缓存池中取出的对象</returns>
    public GameObject GetObj(string name)
    {
        // 如果没有对应的对象池,创建默认对象池
        if (!_gameObjPoolDict.ContainsKey(name))
        {
            _gameObjPoolDict[name] = new DefaultGameObjPool(name, _mgrRoot);
        }

        return _gameObjPoolDict[name].GetObj();
    }

    /// <summary>
    /// 将GameObject对象归还到缓存池中
    /// </summary>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj(GameObject obj)
    {
        // 往对象池中归还对象
        _gameObjPoolDict[obj.name].ReturnObj(obj);
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// 主要用于场景切换时清理资源
    /// </summary>
    public void ClearPool()
    {
        _gameObjPoolDict.Clear();
        if (_mgrRoot != null)
        {
            Object.Destroy(_mgrRoot);
            _mgrRoot = null;
        }
    }

    #endregion
}

ObjPoolMgr.cs(普通类对象池管理器)

using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 普通类对象池管理器(支持自定义对象池)
/// 使用字典管理多个ObjPool实例
/// </summary>
public class ObjPoolMgr
{
    #region 字段

    /// <summary>
    /// 数据结构类对象池字典
    /// 用于存储不继承MonoBehaviour的对象
    /// </summary>
    private Dictionary<Type, object> _objPoolDict = new Dictionary<Type, object>();

    #endregion

    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public ObjPoolMgr()
    {
    }

    #endregion

    #region 对象池注册

    /// <summary>
    /// 注册自定义对象池
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    /// <param name="pool">自定义对象池实例</param>
    public void RegisterPool<T>(ObjPool<T> pool) where T : class, new()
    {
        Type type = typeof(T);
        _objPoolDict[type] = pool;
    }

    #endregion

    #region 普通类对象池管理

    /// <summary>
    /// 获取自定义的数据结构类和逻辑类对象
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <returns>从对象池中取出的对象</returns>
    public T GetObj<T>() where T : class, new()
    {
        Type type = typeof(T);

        // 有池子
        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                return pool.GetObj();
            }

            Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            return null;
        }
        else
        {
            // 没有池子,创建默认对象池
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            return pool.GetObj();
        }
    }

    /// <summary>
    /// 将自定义数据结构类和逻辑类对象归还到池子中
    /// </summary>
    /// <typeparam name="T">对应类型</typeparam>
    /// <param name="obj">要归还的对象</param>
    public void ReturnObj<T>(T obj) where T : class, new()
    {
        if (obj == null)
        {
            return;
        }

        Type type = typeof(T);

        if (_objPoolDict.TryGetValue(type, out var value))
        {
            if (value is ObjPool<T> pool)
            {
                pool.ReturnObj(obj);
            }
            else
            {
                Debug.LogError($"{typeof(T)} 存在对象池但是转换失败!");
            }
        }
        else
        {
            // 没有池子,创建默认对象池
            DefaultObjPool<T> pool = new DefaultObjPool<T>();
            _objPoolDict.Add(type, pool);
            pool.ReturnObj(obj);
        }
    }

    /// <summary>
    /// 清除整个对象池中的数据
    /// </summary>
    public void ClearPool()
    {
        _objPoolDict.Clear();
    }

    #endregion
}

DefaultGameObjPool.cs(默认GameObject对象池实现)

using UnityEngine;

/// <summary>
/// 默认GameObject对象池实现
/// 使用Resources.Load加载资源,使用默认最大数量上限
/// </summary>
public class DefaultGameObjPool : GameObjPool
{
    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    /// <param name="poolName">对象池名称</param>
    /// <param name="root">缓存池父对象</param>
    /// <param name="maxNum">最大对象数量,默认100</param>
    /// <param name="warmCount">预热数量,默认0</param>
    public DefaultGameObjPool(string poolName, GameObject root, int maxNum = 100, int warmCount = 0) : base(poolName, root, maxNum, warmCount)
    {
    }

    #endregion

    #region 重写方法

    /// <summary>
    /// 使用Resources.Load加载预制体
    /// </summary>
    /// <returns>加载的GameObject预制体</returns>
    protected override GameObject LoadPrefab()
    {
        return Resources.Load<GameObject>(_poolName);
    }

    #endregion
}

DefaultObjPool.cs(默认普通类对象池实现)

/// <summary>
/// 默认普通类对象池实现
/// 使用无参构造函数创建对象
/// </summary>
/// <typeparam name="T">要存储的对象类型</typeparam>
public class DefaultObjPool<T> : ObjPool<T> where T : class, new()
{
    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public DefaultObjPool() : base()
    {
    }

    #endregion
}

TestData.cs(测试数据结构类)

using UnityEngine;

/// <summary>
/// 测试数据结构类
/// 用于演示普通类对象池的使用
/// </summary>
public class TestData
{
    public int i = 0;
    public string str = "";
    public object o = null;

    public TestData()
    {
        Debug.Log("TestData构造函数被调用");
    }
}

TestDataPool.cs(自定义TestData对象池)

using UnityEngine;

/// <summary>
/// TestData对象池
/// 自定义TestData的重置逻辑
/// </summary>
public class TestDataPool : ObjPool<TestData>
{
    #region 构造函数

    /// <summary>
    /// 初始化构造函数
    /// </summary>
    public TestDataPool() : base()
    {
    }

    #endregion

    #region 重写方法

    /// <summary>
    /// 将对象放回对象池时的清理
    /// </summary>
    /// <param name="obj">放回的TestData对象</param>
    protected override void OnReturnObject(TestData obj)
    {
        // 重置TestData的数据
        obj.i = 0;
        obj.str = "";
        obj.o = null;
    }

    #endregion
}

DelayRemove.cs(延迟销毁脚本)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 延迟销毁脚本
/// 演示如何自动归还对象到对象池
/// </summary>
public class DelayRemove : MonoBehaviour
{
    // Start is called before the first frame update
    private void OnEnable()
    {
        Invoke("RemoveMe", 1f);
    }

    private void RemoveMe()
    {
        PoolMgr.Instance.ReturnGameObj(this.gameObject);
    }
}

GameObjectPoolUsageTest.cs(GameObject对象池使用测试)

using UnityEngine;

/// <summary>
/// GameObject对象池使用测试
/// 演示如何通过PoolMgr使用GameObject对象池
/// </summary>
public class GameObjectPoolUsageTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("GameObject对象池使用测试:开始");

        // 1. 获取GameObject对象
        GameObject obj1 = PoolMgr.Instance.GetGameObj("Cube");
        Debug.Log("获取对象:" + obj1.name);
        // 输出:获取对象:Cube

        // 2. 延迟归还对象
        Invoke("ReturnObject", 2f);
    }

    private void ReturnObject()
    {
        // 找到场景中的Cube对象并归还
        GameObject cube = GameObject.Find("Cube");
        if (cube != null)
        {
            PoolMgr.Instance.ReturnGameObj(cube);
            Debug.Log("归还对象:" + cube.name);
            // 输出:归还对象:Cube
        }
    }
}

ObjectPoolUsageTest.cs(普通类对象池使用测试)

using UnityEngine;

/// <summary>
/// 普通类对象池使用测试
/// 演示如何通过PoolMgr使用普通类对象池
/// </summary>
public class ObjectPoolUsageTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("普通类对象池使用测试:开始");

        // 1. 获取普通类对象
        TestData data1 = PoolMgr.Instance.GetObj<TestData>();
        data1.i = 999;
        data1.str = "测试字符串";
        Debug.Log($"获取对象,i={data1.i},str={data1.str}");
        // 输出:获取对象,i=999,str=测试字符串

        // 2. 归还对象
        PoolMgr.Instance.ReturnObj(data1);
        Debug.Log("归还对象");

        // 3. 再次获取(验证复用)
        TestData data2 = PoolMgr.Instance.GetObj<TestData>();
        Debug.Log($"再次获取对象,i={data2.i},str={data2.str}");
        // 输出:再次获取对象,i=0,str=(数据被重置)
        Debug.Log($"是否为同一对象:{data1 == data2}");
        // 输出:是否为同一对象:True
    }
}

ComprehensivePoolTest.cs(综合功能测试)

using UnityEngine;

/// <summary>
/// 综合功能测试
/// 演示对象池的所有功能
/// </summary>
public class ComprehensivePoolTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("综合功能测试:开始");

        // 1. 测试GameObject对象池
        TestGameObjectPool();
        
        // 2. 测试普通类对象池
        TestObjectPool();
        
        // 3. 延迟清理测试
        Invoke("TestClearPool", 5f);
    }

    private void TestGameObjectPool()
    {
        Debug.Log("测试GameObject对象池");
        
        // 获取多个GameObject
        GameObject obj1 = PoolMgr.Instance.GetGameObj("Cube");
        GameObject obj2 = PoolMgr.Instance.GetGameObj("Sphere");
        
        Debug.Log($"获取GameObject:{obj1.name},{obj2.name}");
        // 输出:获取GameObject:Cube,Sphere

        // 延迟归还
        Invoke("ReturnGameObjects", 2f);
    }

    private void ReturnGameObjects()
    {
        GameObject cube = GameObject.Find("Cube");
        GameObject sphere = GameObject.Find("Sphere");
        
        if (cube != null) PoolMgr.Instance.ReturnGameObj(cube);
        if (sphere != null) PoolMgr.Instance.ReturnGameObj(sphere);
        
        Debug.Log("归还GameObject对象");
    }

    private void TestObjectPool()
    {
        Debug.Log("测试普通类对象池");
        
        // 获取多个普通类对象
        TestData data1 = PoolMgr.Instance.GetObj<TestData>();
        TestData data2 = PoolMgr.Instance.GetObj<TestData>();
        
        data1.i = 100;
        data2.i = 200;
        
        Debug.Log($"获取普通类对象:data1.i={data1.i},data2.i={data2.i}");
        // 输出:获取普通类对象:data1.i=100,data2.i=200

        // 归还对象
        PoolMgr.Instance.ReturnObj(data1);
        PoolMgr.Instance.ReturnObj(data2);
        
        Debug.Log("归还普通类对象");
    }

    private void TestClearPool()
    {
        Debug.Log("测试资源清理");
        PoolMgr.Instance.ClearPool();
        Debug.Log("清理完成");
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏