using System.Collections.Generic;
|
using UnityEngine;
|
using System;
|
using Cysharp.Threading.Tasks;
|
|
|
public class GameObjectPoolManager : SingletonMonobehaviour<GameObjectPoolManager>
|
{
|
#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));
|
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;
|
}
|
}
|
#endif
|
// 池统计数据
|
public Dictionary<int, PoolStats> PoolStatistics { get; private set; } = new Dictionary<int, PoolStats>();
|
|
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<int, GameObjectPool> m_PoolDict = null;
|
|
// 不需要销毁的对象列表
|
private List<int> dontDestoryGoInstIDList = null;
|
|
private Transform m_TargetContainer;
|
|
// 定期检查池的时间间隔(秒)
|
private float m_PoolCheckInterval = 600f;
|
|
// 记录每个池的使用频率
|
private Dictionary<int, int> m_PoolUsageFrequency = new Dictionary<int, int>();
|
|
//需要动态卸载的预制体,例如切换场景时
|
public List<GameObject> needDestroyPrefabList = new List<GameObject>();
|
|
public void Initialize()
|
{
|
if (m_PoolDict == null)
|
{
|
m_PoolDict = new Dictionary<int, GameObjectPool>();
|
}
|
|
if (!m_TargetContainer)
|
{
|
m_TargetContainer = new GameObject("Container").transform;
|
m_TargetContainer.SetParent(transform);
|
}
|
|
if (dontDestoryGoInstIDList == null)
|
{
|
dontDestoryGoInstIDList = new List<int>();
|
}
|
|
// 启动定期检查池的协程
|
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;
|
}
|
|
/// <summary>
|
/// 传入的为预制体,而非组件,否则会导致GetInstanceID管理混乱,每次实例化都会改变
|
/// </summary>
|
/// <param name="prefab"></param>
|
/// <returns></returns>
|
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<int> _removeList = new List<int>();
|
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<GameObject> m_ActiveHashSet = new HashSet<GameObject>();
|
public Queue<GameObject> m_FreeQueue = null;
|
private int Pool_FreeList_Warning_Threshold = 10; //当空闲对象高于此值时,会进行日志输出提醒
|
|
Action<GameObject> releaseCallBack;
|
|
|
public string assetBundleName;
|
public string assetName;
|
|
internal protected GameObjectPool(GameObject prefab)
|
{
|
m_Prefab = prefab;
|
PrefabInstanceId = m_Prefab.GetInstanceID();
|
m_ActiveHashSet = new HashSet<GameObject>();
|
m_FreeQueue = new Queue<GameObject>();
|
|
}
|
|
public void AddReleaseListener(Action<GameObject> _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<Animator>();
|
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<GameObject> tempQueue = new Queue<GameObject>();
|
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;
|
}
|
}
|
}
|