当前位置: 首页 > news >正文

Unity中的对象池ObjPool/PoolManager

1.先贴代码

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;/// <summary>
/// 对象池管理器 - 用于高效管理游戏对象的创建、回收和复用
/// 继承自MonoSingleton确保全局唯一性
/// </summary>
public class PoolManager : MonoSingleton<PoolManager>
{/// <summary>/// 对象池字典 - 存储每个预制体路径对应的对象队列/// </summary>private Dictionary<string, Queue<GameObject>> poolDict = new Dictionary<string, Queue<GameObject>>();/// <summary>/// 预制体缓存字典 - 缓存已加载的预制体以避免重复加载/// </summary>private Dictionary<string, GameObject> prefabDict = new Dictionary<string, GameObject>();/// <summary>/// 对象与路径的映射字典 - 用于追踪每个对象对应的预制体路径/// </summary>private Dictionary<GameObject, string> objToPathDict = new Dictionary<GameObject, string>();/// <summary>/// 当前场景名称 - 用于检测场景切换/// </summary>private string currentSceneName;protected override void Awake(){base.Awake();DontDestroyOnLoad(this);// 记录当前场景名称currentSceneName = SceneManager.GetActiveScene().name;// 注册场景切换事件SceneManager.sceneLoaded += OnSceneLoaded;}/// <summary>/// 场景加载完成时的回调处理/// </summary>/// <param name="scene">加载的场景</param>/// <param name="mode">加载模式</param>private void OnSceneLoaded(Scene scene, LoadSceneMode mode){// 检测到场景切换,清理失效的对象if (currentSceneName != scene.name){currentSceneName = scene.name;CleanupInvalidObjects();}}/// <summary>/// 清理失效的对象(通常是场景切换后被销毁的对象)/// </summary>private void CleanupInvalidObjects(){// 先收集所有需要更新的键值对,避免在遍历时修改字典Dictionary<string, Queue<GameObject>> updatedPools = new Dictionary<string, Queue<GameObject>>();foreach (var poolPair in poolDict){Queue<GameObject> queue = poolPair.Value;Queue<GameObject> newQueue = new Queue<GameObject>();// 检查队列中的每个对象是否仍然有效while (queue.Count > 0){GameObject obj = queue.Dequeue();if (obj != null && !obj.Equals(null)){newQueue.Enqueue(obj);}else{// 从映射字典中移除失效对象if (objToPathDict.ContainsKey(obj)){objToPathDict.Remove(obj);}}}// 如果队列发生了变化,记录需要更新的队列if (newQueue.Count != poolPair.Value.Count){updatedPools[poolPair.Key] = newQueue;}}// 在遍历完成后统一更新字典foreach (var updatePair in updatedPools){poolDict[updatePair.Key] = updatePair.Value;}// 清理映射字典中的失效引用List<GameObject> invalidObjects = new List<GameObject>();foreach (var objPair in objToPathDict){if (objPair.Key == null || objPair.Key.Equals(null)){invalidObjects.Add(objPair.Key);}}foreach (var invalidObj in invalidObjects){objToPathDict.Remove(invalidObj);}}/// <summary>/// 从对象池获取游戏对象/// </summary>/// <param name="path">预制体在Resources文件夹中的路径</param>/// <param name="callback">获取对象后的回调函数</param>public void GetObj(string path, Action<GameObject> callback){if (string.IsNullOrEmpty(path)){Debug.LogError("对象池:路径不能为空");callback?.Invoke(null);return;}// 确保路径格式正确,移除可能的前缀和后缀path = path.Replace("Resources/", "").Replace(".prefab", "");// 如果该路径的对象池不存在,创建新的队列if (!poolDict.ContainsKey(path)){poolDict[path] = new Queue<GameObject>();}Queue<GameObject> poolQueue = poolDict[path];GameObject obj = null;// 创建临时列表来处理需要重新排队的对象List<GameObject> objectsToRequeue = new List<GameObject>();// 尝试从对象池中获取可用的对象while (poolQueue.Count > 0 && obj == null){GameObject pooledObj = poolQueue.Dequeue();// 检查对象是否仍然有效且未激活if (pooledObj != null && !pooledObj.Equals(null)){if (!pooledObj.activeSelf){obj = pooledObj;}else{// 对象有效但已激活,暂存起来稍后重新排队objectsToRequeue.Add(pooledObj);}}else{// 对象已被销毁,从映射字典中移除if (objToPathDict.ContainsKey(pooledObj)){objToPathDict.Remove(pooledObj);}}}// 将需要重新排队的对象放回队列foreach (var objToRequeue in objectsToRequeue){poolQueue.Enqueue(objToRequeue);}// 如果没有可用对象,创建新对象if (obj == null){obj = CreateNewObject(path);if (obj == null){callback?.Invoke(null);return;}}// 激活对象并执行回调obj.SetActive(true);callback?.Invoke(obj);}/// <summary>/// 创建新的游戏对象/// </summary>/// <param name="path">预制体路径</param>/// <returns>创建的游戏对象</returns>private GameObject CreateNewObject(string path){// 加载预制体并缓存if (!prefabDict.ContainsKey(path)){GameObject prefab = Resources.Load<GameObject>(path);if (prefab == null){Debug.LogError($"对象池:预制体加载失败,路径:{path}");return null;}prefabDict[path] = prefab;}// 实例化对象GameObject obj = Instantiate(prefabDict[path]);obj.name = path; // 保持名称一致性// 添加到映射字典objToPathDict[obj] = path;return obj;}/// <summary>/// 释放对象回对象池/// </summary>/// <param name="obj">要释放的游戏对象</param>public void ReleaseObj(GameObject obj){if (obj == null || obj.Equals(null))return;// 检查对象是否来自对象池if (!objToPathDict.TryGetValue(obj, out string path)){Debug.LogWarning($"对象池:尝试释放非对象池管理的对象 {obj.name}");return;}// 设置对象的父级并停用obj.transform.SetParent(transform);obj.SetActive(false);// 重置对象状态(可选,根据需要实现)ResetObjectState(obj);// 将对象放回对应的池中if (poolDict.TryGetValue(path, out Queue<GameObject> queue)){queue.Enqueue(obj);}}/// <summary>/// 重置对象状态(可根据项目需求自定义)/// </summary>/// <param name="obj">要重置的对象</param>private void ResetObjectState(GameObject obj){// 停止所有正在运行的协程MonoBehaviour[] monoBehaviours = obj.GetComponentsInChildren<MonoBehaviour>();foreach (var mb in monoBehaviours){if (mb != null){mb.StopAllCoroutines();}}// 重置Transform(但不重置WorldSpace Canvas的缩放)Canvas canvas = obj.GetComponent<Canvas>();if (canvas != null && canvas.renderMode == RenderMode.WorldSpace){// WorldSpace Canvas保持其缩放设置obj.transform.localRotation = Quaternion.identity;}else{// 普通对象重置所有Transform属性obj.transform.localPosition = Vector3.zero;obj.transform.localRotation = Quaternion.identity;obj.transform.localScale = Vector3.one;}// 重置CanvasGroup透明度CanvasGroup canvasGroup = obj.GetComponent<CanvasGroup>();if (canvasGroup != null){canvasGroup.alpha = 1f;}// 重置Rigidbody状态Rigidbody rb = obj.GetComponent<Rigidbody>();if (rb != null){rb.velocity = Vector3.zero;rb.angularVelocity = Vector3.zero;}Rigidbody2D rb2d = obj.GetComponent<Rigidbody2D>();if (rb2d != null){rb2d.velocity = Vector2.zero;rb2d.angularVelocity = 0f;}// 重置动画状态Animator animator = obj.GetComponent<Animator>();if (animator != null){animator.Rebind();}}/// <summary>/// 预加载指定数量的对象到池中/// </summary>/// <param name="path">预制体路径</param>/// <param name="count">预加载数量</param>public void PreloadObjects(string path, int count){if (string.IsNullOrEmpty(path) || count <= 0)return;path = path.Replace("Resources/", "").Replace(".prefab", "");for (int i = 0; i < count; i++){GetObj(path, (obj) =>{if (obj != null){ReleaseObj(obj);}});}}/// <summary>/// 获取指定路径对象池的当前大小/// </summary>/// <param name="path">预制体路径</param>/// <returns>对象池大小</returns>public int GetPoolSize(string path){path = path.Replace("Resources/", "").Replace(".prefab", "");if (poolDict.TryGetValue(path, out Queue<GameObject> queue)){return queue.Count;}return 0;}/// <summary>/// 清空指定路径的对象池/// </summary>/// <param name="path">预制体路径</param>public void ClearPool(string path){path = path.Replace("Resources/", "").Replace(".prefab", "");if (poolDict.TryGetValue(path, out Queue<GameObject> queue)){while (queue.Count > 0){GameObject obj = queue.Dequeue();if (obj != null && !obj.Equals(null)){if (objToPathDict.ContainsKey(obj)){objToPathDict.Remove(obj);}Destroy(obj);}}poolDict.Remove(path);}// 清理预制体缓存if (prefabDict.ContainsKey(path)){prefabDict.Remove(path);}}/// <summary>/// 清空所有对象池/// </summary>public void ClearAllPools(){foreach (var poolPair in poolDict){while (poolPair.Value.Count > 0){GameObject obj = poolPair.Value.Dequeue();if (obj != null && !obj.Equals(null)){Destroy(obj);}}}poolDict.Clear();prefabDict.Clear();objToPathDict.Clear();}/// <summary>/// 获取对象池的统计信息/// </summary>/// <returns>统计信息字符串</returns>public string GetPoolStats(){System.Text.StringBuilder sb = new System.Text.StringBuilder();sb.AppendLine("=== 对象池统计信息 ===");sb.AppendLine($"管理的对象池数量: {poolDict.Count}");sb.AppendLine($"缓存的预制体数量: {prefabDict.Count}");sb.AppendLine($"追踪的对象数量: {objToPathDict.Count}");sb.AppendLine("各池详情:");foreach (var poolPair in poolDict){sb.AppendLine($"  {poolPair.Key}: {poolPair.Value.Count} 个对象");}return sb.ToString();}/// <summary>   /// 组件销毁时的清理工作/// </summary>protected override void OnDestroy(){base.OnDestroy();// 取消事件注册SceneManager.sceneLoaded -= OnSceneLoaded;// 清理所有对象池ClearAllPools();}
}

2.用法

基础用法

1. 获取对象

// 从Resources加载"Prefabs/Bullet.prefab"
PoolManager.Instance.GetObj("Prefabs/Bullet", go => 
{if(go != null){go.transform.position = transform.position;go.GetComponent<Bullet>().Launch();}
});

2. 回收对象

// 子弹命中后回收
public class Bullet : MonoBehaviour
{void OnCollisionEnter(){PoolManager.Instance.ReleaseObj(gameObject);}
}
//延时自动回收
public float lifeTimer;private void OnEnable()
{StartCoroutine(AutoReleaseTimer(gameObject, lifeTimer));
}private IEnumerator AutoReleaseTimer(GameObject obj, float time)
{yield return new WaitForSeconds(time);PoolManager.Instance.ReleaseObj(obj);
}

3. 预加载对象

// 游戏初始化时预加载10个子弹
void Start()
{PoolManager.Instance.PreloadObjects("Prefabs/Bullet", 10);
}

高级功能

1. 获取对象池状态

// 调试时查看池状态
void DebugPools()
{Debug.Log(PoolManager.Instance.GetPoolStats());// 输出示例:// === 对象池统计信息 ===// 管理的对象池数量: 3// Prefabs/Bullet: 15 个对象// Prefabs/Explosion: 5 个对象
}

2. 清理对象池

// 关卡结束时清理特定池
void OnLevelComplete()
{PoolManager.Instance.ClearPool("Prefabs/Enemy");
}// 游戏退出时清理所有池
void OnApplicationQuit()
{PoolManager.Instance.ClearAllPools();
}

3. 自定义重置逻辑

// 如需特殊重置逻辑,继承并扩展
public class CustomPoolManager : PoolManager
{protected override void ResetObjectState(GameObject obj){base.ResetObjectState(obj); // 保持基础重置// 自定义重置var particle = obj.GetComponent<ParticleSystem>();if(particle) particle.Stop();obj.GetComponent<Rigidbody>().isKinematic = true;}
}

使用注意事项

  1. 路径规范

    • 预制体必须放在 Resources 目录下

    • 使用相对路径(如 "UI/Popups/MessageBox"

  2. 对象生命周期

    • 永远使用 ReleaseObj() 代替 Destroy()

    • 场景切换时自动清理无效对象

  3. 特殊组件处理

    • WorldSpace Canvas 不会重置缩放

    • 自动停止所有协程

    • 重置 Rigidbody 物理状态

  4. 错误处理

    • 尝试释放非池管理对象会触发警告

    • 加载失败会返回 null 并报错

最佳实践

预加载策略

// 主菜单加载时预加载常用资源
void LoadMainMenu()
{PreloadObjects("Effects/Fire", 5);PreloadObjects("Projectiles/Rocket", 8);PreloadObjects("UI/DamageText", 10);
}

结合场景管理

// 场景卸载时清理相关对象
void OnSceneUnload(string sceneName)
{if(sceneName == "CombatScene"){ClearPool("Enemies/Zombie");ClearPool("Weapons/Laser");}
}

性能监控

void Update()
{// 每30秒输出池状态if(Time.frameCount % 1800 == 0)Debug.Log(GetPoolStats());
}

⚠️ 重要:

对象池管理的对象禁止直接使用 Destroy(),必须通过 ReleaseObj() 回收

相关文章:

  • 从零手写Java版本的LSM Tree (三):MemTable 内存表
  • 如何使用CodeRider插件在IDEA中生成代码
  • 写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
  • 189. 轮转数组
  • Django RBAC项目后端实战 - 03 DRF权限控制实现
  • fpga系列 HDL : Microchip FlashPro 导出与烧录FPGA
  • C++八股 —— 单例模式
  • UE5 学习系列(一)创建一个游戏工程
  • 创建型模式-单例模式
  • “扛不住了就排队!”——聊聊消息队列在高并发系统中的那些硬核用途
  • tomcat入门
  • 免费批量抠图工具使用说明
  • 内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
  • Python训练打卡Day45
  • LoRA(Low-Rank Adaptation,低秩适应)
  • 跨链模式:多链互操作架构与性能扩展方案
  • Linux线程互斥与竞态条件解析
  • 若依项目部署--传统架构--未完待续
  • 西电【网络与协议安全】课程期末复习的一些可用情报
  • K8S认证|CKS题库+答案| 9. 网络策略 NetworkPolicy
  • 企业产品做哪个网站推广好/软文发布
  • 上海做网站报价/怎么申请域名建网站
  • 中国建设银行手机版网站首页/百度下载安装到手机
  • 公司简介网站怎么做/站长工具的使用seo综合查询排名
  • 那个网站的公众后推广做的好/重庆seo教程
  • phpwind 做的网站/seo排名优化怎样