using System.Collections.Generic; using UnityEngine; using System; using Cysharp.Threading.Tasks; using System.Linq; #if UNITY_EDITOR using UnityEngine.Profiling; #endif public class GameObjectPoolManager : SingletonMonobehaviour { #if UNITY_EDITOR private bool m_ShowDebugPanel = false; private Vector2 m_ScrollPosition = Vector2.zero; private void OnGUI() { if (!m_ShowDebugPanel) return; GUILayout.BeginArea(new Rect(10, 10, 400, 600)); GUILayout.BeginVertical("Box"); m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUILayout.Width(380), GUILayout.Height(580)); GUILayout.Label("对象池调试面板", GUILayout.Height(30)); LogPoolMemoryUsage(); GUILayout.Space(10); foreach (var poolEntry in m_PoolDict) { int prefabInstanceId = poolEntry.Key; GameObjectPool pool = poolEntry.Value; GUILayout.BeginVertical("Box"); GUILayout.Label($"池名称: {pool.Prefab.name}"); GUILayout.Label($"活跃对象数: {pool.m_ActiveHashSet.Count}"); GUILayout.Label($"空闲对象数: {pool.m_FreeQueue.Count}"); int count = m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int frequency) ? frequency : 0; GUILayout.Label($"使用频率: {count}"); GUILayout.EndVertical(); GUILayout.Space(5); } GUILayout.EndScrollView(); GUILayout.EndVertical(); GUILayout.EndArea(); } [UnityEditor.MenuItem("Tools/对象池/显示调试面板")] private static void ToggleDebugPanel() { if (Instance != null) { Instance.m_ShowDebugPanel = !Instance.m_ShowDebugPanel; } } public void LogPoolMemoryUsage() { long totalMemory = 0; long totalFreeMemory = 0; var prefabMemoryDict = new Dictionary(); foreach (var poolEntry in m_PoolDict) { int prefabInstanceId = poolEntry.Key; GameObjectPool pool = poolEntry.Value; string prefabName = pool.Prefab.name; long prefabMemory = 0; long freeMemory = 0; // 计算活跃对象的内存占用 foreach (var gameObject in pool.m_ActiveHashSet) { if (gameObject != null) { long memory = Profiler.GetRuntimeMemorySizeLong(gameObject); prefabMemory += memory; } } // 计算空闲对象的内存占用 foreach (var gameObject in pool.m_FreeQueue) { if (gameObject != null) { long memory = Profiler.GetRuntimeMemorySizeLong(gameObject); prefabMemory += memory; freeMemory += memory; } } totalMemory += prefabMemory; totalFreeMemory += freeMemory; prefabMemoryDict[prefabName] = prefabMemory; } // 按内存占用排序 var sortedPrefabs = prefabMemoryDict.OrderByDescending(kv => kv.Value).Take(3).ToList(); GUILayout.Label($"总内存占用: {totalMemory / 1024} KB", GUILayout.Height(30)); GUILayout.Label($"空闲内存占用: {totalFreeMemory / 1024} KB", GUILayout.Height(30)); GUILayout.Label("占用最高的前3预制体名:", GUILayout.Height(30)); foreach (var prefabInstance in sortedPrefabs) { GUILayout.BeginHorizontal("Box"); GUILayout.Label($"{prefabInstance.Key}({prefabInstance.Value / 1024} KB)", GUILayout.Height(25)); GUILayout.EndHorizontal(); } } #endif // 池统计数据 public Dictionary PoolStatistics { get; private set; } = new Dictionary(); public class PoolStats { public int TotalObjects { get; set; } public int ActiveObjects { get; set; } public int FreeObjects { get; set; } public int UsageFrequency { get; set; } } // 更新池统计数据 private void UpdatePoolStats() { if (m_PoolDict == null) return; foreach (var poolEntry in m_PoolDict) { int prefabInstanceId = poolEntry.Key; GameObjectPool pool = poolEntry.Value; if (!PoolStatistics.ContainsKey(prefabInstanceId)) { PoolStatistics[prefabInstanceId] = new PoolStats(); } PoolStats stats = PoolStatistics[prefabInstanceId]; stats.TotalObjects = pool.m_ActiveHashSet.Count + pool.m_FreeQueue.Count; stats.ActiveObjects = pool.m_ActiveHashSet.Count; stats.FreeObjects = pool.m_FreeQueue.Count; stats.UsageFrequency = m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int frequency) ? frequency : 0; } } private Dictionary m_PoolDict = null; // 不需要销毁的对象列表 private List dontDestoryGoInstIDList = null; private Transform m_TargetContainer; // 定期检查池的时间间隔(秒) private float m_PoolCheckInterval = 600f; // 记录每个池的使用频率 private Dictionary m_PoolUsageFrequency = new Dictionary(); //需要动态卸载的预制体,例如切换场景时 public List needDestroyPrefabList = new List(); public void Initialize() { if (m_PoolDict == null) { m_PoolDict = new Dictionary(); } if (!m_TargetContainer) { m_TargetContainer = new GameObject("Container").transform; m_TargetContainer.SetParent(transform); } if (dontDestoryGoInstIDList == null) { dontDestoryGoInstIDList = new List(); } // 启动定期检查池的协程 CheckPoolUsage(); } private async UniTask CheckPoolUsage() { while (true) { await UniTask.Delay(TimeSpan.FromSeconds(m_PoolCheckInterval)); foreach (var poolEntry in m_PoolDict) { int prefabInstanceId = poolEntry.Key; GameObjectPool pool = poolEntry.Value; // 如果池的使用频率低于阈值,减少池的大小 if (m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int usageCount) && usageCount < 5) { // 保留至少一个对象,避免频繁创建和销毁 int targetSize = Mathf.Max(1, pool.m_FreeQueue.Count / 2); while (pool.m_FreeQueue.Count > targetSize) { GameObject obj = pool.m_FreeQueue.Dequeue(); if (!dontDestoryGoInstIDList.Contains(obj.GetInstanceID())) { Destroy(obj); } } } // 重置使用频率 m_PoolUsageFrequency[prefabInstanceId] = 0; } } } public void AddDontDestroyGoInstID(int id) { if (!dontDestoryGoInstIDList.Contains(id)) { dontDestoryGoInstIDList.Add(id); } } public bool ExistPool(GameObject prefab) { if (prefab == null) { return false; } if (m_PoolDict.ContainsKey(prefab.GetInstanceID())) { return true; } return false; } /// /// 传入的为预制体,而非组件,否则会导致GetInstanceID管理混乱,每次实例化都会改变 /// /// /// public GameObjectPool RequestPool(GameObject prefab) { if (prefab == null) { return null; } #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabAssetType(prefab) == UnityEditor.PrefabAssetType.NotAPrefab) { Debug.LogError($"{prefab.name}不是一个预制体!"); return null; } #endif int _prefabInstanceId = prefab.GetInstanceID(); GameObjectPool _pool = null; if (m_PoolDict.TryGetValue(_prefabInstanceId, out _pool)) { return _pool; } _pool = new GameObjectPool(prefab); m_PoolDict[_prefabInstanceId] = _pool; return _pool; } public void CacheGameObject(GameObject prefab, int count, bool _prefabActive) { if (prefab == null) { return; } RequestPool(prefab).Cache(count, _prefabActive); } public GameObject RequestGameObject(GameObject prefab) { if (prefab == null) { return null; } #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabAssetType(prefab) == UnityEditor.PrefabAssetType.NotAPrefab) { Debug.LogError($"{prefab.name}不是一个预制体!"); return null; } #endif return RequestPool(prefab).Request(); } //特定情况下需要动态卸载的,如切换场景时 public void UnLoadAll(bool force = false) { List _removeList = new List(); foreach (var _pool in m_PoolDict.Values) { if (needDestroyPrefabList.Contains(_pool.Prefab)) { _pool.Clear(); _removeList.Add(_pool.PrefabInstanceId); } } for (int i = 0; i < _removeList.Count; ++i) { m_PoolDict.Remove(_removeList[i]); } } public class GameObjectPool { private GameObject m_Prefab = null; public GameObject Prefab { get { return m_Prefab; } } //预制体实例id,用于检索池 public int PrefabInstanceId { get; private set; } public HashSet m_ActiveHashSet = new HashSet(); public Queue m_FreeQueue = null; private int Pool_FreeList_Warning_Threshold = 10; //当空闲对象高于此值时,会进行日志输出提醒 Action releaseCallBack; public string assetBundleName; public string assetName; internal protected GameObjectPool(GameObject prefab) { m_Prefab = prefab; PrefabInstanceId = m_Prefab.GetInstanceID(); m_ActiveHashSet = new HashSet(); m_FreeQueue = new Queue(); } public void AddReleaseListener(Action _callBack) { releaseCallBack = _callBack; } public void RemoveReleaseListener() { releaseCallBack = null; } public void Cache(int count, bool _prefabActive) { GameObject _go; for (int i = 0; i < count; ++i) { _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity); m_FreeQueue.Enqueue(_go); _go.transform.SetParent(Instance.m_TargetContainer); _go.transform.position = Constants.Special_Hide_Position; #if UNITY_EDITOR // 检查空闲列表数量是否超过阈值 if (m_FreeQueue.Count > Pool_FreeList_Warning_Threshold) { Debug.LogWarning($"GameObjectPool {m_Prefab.name} 的空闲对象数量 ({m_FreeQueue.Count}) 超过阈值 ({Pool_FreeList_Warning_Threshold}),请检查是否需要清理。"); } #endif var animator = _go.GetComponent(); if (animator != null) { animator.enabled = false; } _go.SetActive(_prefabActive); } } public GameObject Request() { GameObject _go = null; if (m_FreeQueue.Count > 0) { _go = m_FreeQueue.Dequeue(); m_ActiveHashSet.Add(_go); // 统计池的使用频率 if (Instance.m_PoolUsageFrequency.ContainsKey(m_Prefab.GetInstanceID())) { Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()]++; } else { Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()] = 1; } } if (_go == null) { _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity); m_ActiveHashSet.Add(_go); } _go.transform.SetParent(null); _go.transform.localScale = Vector3.one; // 更新池统计数据 Instance.UpdatePoolStats(); return _go; } public void Release(GameObject go) { if (go == null) { return; } m_ActiveHashSet.Remove(go); m_FreeQueue.Enqueue(go); go.transform.SetParent(Instance.m_TargetContainer); go.transform.position = Constants.Special_Hide_Position; #if UNITY_EDITOR // 检查空闲列表数量是否超过阈值 if (m_FreeQueue.Count > Pool_FreeList_Warning_Threshold) { Debug.LogWarning($"GameObjectPool {m_Prefab.name} 的空闲对象数量 ({m_FreeQueue.Count}) 超过阈值 ({Pool_FreeList_Warning_Threshold}),请检查是否需要清理。"); } #endif if (releaseCallBack != null) { releaseCallBack(go); } // 更新池统计数据 Instance.UpdatePoolStats(); } public void ReleaseAll() { //释放 m_ActiveHashSet中 的GameObject,排除 foreach (GameObject go in m_ActiveHashSet) { if (go != null) { m_FreeQueue.Enqueue(go); go.transform.SetParent(Instance.m_TargetContainer); go.transform.position = Constants.Special_Hide_Position; if (releaseCallBack != null) { releaseCallBack(go); } } } m_ActiveHashSet.Clear(); // 更新池统计数据 Instance.UpdatePoolStats(); } public void Clear() { foreach (GameObject go in m_ActiveHashSet) { if (go != null) { if (!Instance.dontDestoryGoInstIDList.Contains(go.GetInstanceID())) { m_ActiveHashSet.Remove(go); Destroy(go); } } } #if UNITY_EDITOR if (m_ActiveHashSet.Count != 0) { Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个活跃对象. ", m_Prefab.name, m_ActiveHashSet.Count); } #endif Queue tempQueue = new Queue(); while (m_FreeQueue.Count > 0) { GameObject obj = m_FreeQueue.Dequeue(); if (Instance.dontDestoryGoInstIDList.Contains(obj.GetInstanceID())) tempQueue.Enqueue(obj); else Destroy(obj); } m_FreeQueue = tempQueue; #if UNITY_EDITOR if (m_FreeQueue.Count != 0) { Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个空闲对象. ", m_Prefab.name, m_FreeQueue.Count); // for (int i = 0; i < m_FreeList.Count; ++i) // { // Debug.LogWarningFormat(" |-- {0}", m_FreeList[i].name); // } } #endif if (IsEmpty()) { m_Prefab = null; } if (!string.IsNullOrEmpty(assetBundleName) && !string.IsNullOrEmpty(assetName)) { ResManager.Instance.UnloadAsset(assetBundleName, assetName); // ResManager.Instance.UnloadAssetBundle(assetBundleName, true, false); } } public bool IsEmpty() { return m_ActiveHashSet.Count == 0 && m_FreeQueue.Count == 0; } } }