From 4b36c5ebfd370fac7e3cf710ea1d5c904399184d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com>
Date: Tue, 10 Mar 2026 13:19:58 +0800
Subject: [PATCH] add GameObjectPoolModule
---
Runtime/ABase/GameObjectPool.meta | 8 +
.../ABase/GameObjectPool/GameObjectPool.cs | 1216 +++++++++++++++++
.../GameObjectPool/GameObjectPool.cs.meta | 11 +
.../GameObjectPool/GameObjectPoolEditor.cs | 322 +++++
.../GameObjectPoolEditor.cs.meta | 3 +
.../PoolConfigScriptableObject.cs | 12 +
.../PoolConfigScriptableObject.cs.meta | 11 +
Runtime/Resource/Resource/ResourceModule.cs | 5 -
8 files changed, 1583 insertions(+), 5 deletions(-)
create mode 100644 Runtime/ABase/GameObjectPool.meta
create mode 100644 Runtime/ABase/GameObjectPool/GameObjectPool.cs
create mode 100644 Runtime/ABase/GameObjectPool/GameObjectPool.cs.meta
create mode 100644 Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs
create mode 100644 Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs.meta
create mode 100644 Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs
create mode 100644 Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs.meta
diff --git a/Runtime/ABase/GameObjectPool.meta b/Runtime/ABase/GameObjectPool.meta
new file mode 100644
index 0000000..55fb2d8
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3f172362360c0cb4db92f112a02e959a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/GameObjectPool/GameObjectPool.cs b/Runtime/ABase/GameObjectPool/GameObjectPool.cs
new file mode 100644
index 0000000..8c599b8
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/GameObjectPool.cs
@@ -0,0 +1,1216 @@
+#region Class Documentation
+
+/************************************************************************************************************
+Class Name: GameObjectPool.cs
+Type: Pool, GameObject, GameObjectPool
+
+Example:
+ // 异步加载游戏物体。
+ var gameObject = await GameObjectPool.Instance.GetGameObjectAsync(path, token);
+
+ // 同步加载游戏物体。
+ var gameObject = GameObjectPool.Instance.GetGameObject(path);
+
+Example1:
+ // 异步加载游戏物体。
+ var gameObject = await GameObjectPoolHelper.LoadGameObjectAsync(path, token);
+
+ // 同步加载游戏物体。
+ var gameObject = GameObjectPoolHelper.LoadGameObject(path);
+************************************************************************************************************/
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using AlicizaX;
+using AlicizaX.Resource.Runtime;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+
+
+namespace AlicizaX
+{
+
+ ///
+ /// 对象池配置项。
+ ///
+ [Serializable]
+ public class PoolConfig
+ {
+ public string asset;
+ public float time;
+ public int poolCount;
+ }
+
+ ///
+ /// 预制体引用计数信息。
+ ///
+ public class PrefabRefInfo
+ {
+ public GameObject Prefab;
+ public int RefCount;
+ public float LastAccessTime;
+ public string AssetPath;
+
+ public PrefabRefInfo(GameObject prefab, string assetPath)
+ {
+ this.Prefab = prefab;
+ this.AssetPath = assetPath;
+ this.RefCount = 0;
+ this.LastAccessTime = Time.time;
+ }
+
+ public void AddRef()
+ {
+ RefCount++;
+ LastAccessTime = Time.time;
+ }
+
+ public void RemoveRef()
+ {
+ if (RefCount > 0) // 防止引用计数变成负数
+ {
+ RefCount--;
+ if (RefCount > 0)
+ {
+ LastAccessTime = Time.time;
+ }
+
+ Log.Info($"RemoveRef: {AssetPath}, refCount: {RefCount}");
+ }
+ else
+ {
+ Log.Warning($"尝试减少已经为0的引用计数: {AssetPath}");
+ }
+ }
+
+ public bool CanUnload(float expireTime)
+ {
+ return RefCount <= 0 && expireTime > 0 && (Time.time - LastAccessTime) > expireTime;
+ }
+ }
+
+ [Serializable]
+ public class PooledObject
+ {
+ public GameObject gameObject;
+ public string assetPath;
+ public float lastUsedTime;
+ public bool isActive;
+ public string instanceName;
+ public bool isRefCountReduced;
+
+ public PooledObject(GameObject go, string path)
+ {
+ gameObject = go;
+ assetPath = path;
+ lastUsedTime = Time.time;
+ isActive = false;
+ instanceName = go.name;
+ isRefCountReduced = false;
+ }
+
+ ///
+ /// 获取过期进度 (0-1),1表示即将过期。
+ ///
+ public float GetExpireProgress(float expireTime)
+ {
+ if (expireTime <= 0 || isActive) return 0f;
+ float timeElapsed = Time.time - lastUsedTime;
+ return Mathf.Clamp01(timeElapsed / expireTime);
+ }
+
+ ///
+ /// 获取剩余时间。
+ ///
+ public float GetRemainingTime(float expireTime)
+ {
+ if (expireTime <= 0 || isActive) return -1f;
+ float timeElapsed = Time.time - lastUsedTime;
+ return Mathf.Max(0f, expireTime - timeElapsed);
+ }
+ }
+
+ ///
+ /// Inspector显示用的对象信息。
+ ///
+ [Serializable]
+ public class PoolObjectInfo
+ {
+ [SerializeField] public string objectName;
+ [SerializeField] public string assetPath;
+ [SerializeField] public bool isActive;
+ [SerializeField] public float lastUsedTime;
+ [SerializeField] public float remainingTime;
+ [SerializeField] public float expireProgress;
+ [SerializeField] public GameObject gameObject;
+
+ public void UpdateFromPooledObject(PooledObject pooledObj, float expireTime)
+ {
+ objectName = pooledObj.instanceName;
+ assetPath = pooledObj.assetPath;
+ isActive = pooledObj.isActive;
+ lastUsedTime = pooledObj.lastUsedTime;
+ remainingTime = pooledObj.GetRemainingTime(expireTime);
+ expireProgress = pooledObj.GetExpireProgress(expireTime);
+ gameObject = pooledObj.gameObject;
+ }
+ }
+
+ ///
+ /// Inspector显示用的预制体信息.
+ ///
+ [Serializable]
+ public class PrefabRefInfoDisplay
+ {
+ [SerializeField] public string assetPath;
+ [SerializeField] public int refCount;
+ [SerializeField] public float lastAccessTime;
+ [SerializeField] public GameObject prefab;
+
+ public void UpdateFromPrefabRefInfo(PrefabRefInfo info)
+ {
+ assetPath = info.AssetPath;
+ refCount = info.RefCount;
+ lastAccessTime = info.LastAccessTime;
+ prefab = info.Prefab;
+ }
+ }
+
+ ///
+ /// Inspector显示用的池信息。
+ ///
+ [Serializable]
+ public class ConfigPoolInfo
+ {
+ [SerializeField] public string configAsset;
+ [SerializeField] public int maxCount;
+ [SerializeField] public float expireTime;
+ [SerializeField] public int totalObjects;
+ [SerializeField] public int activeObjects;
+ [SerializeField] public int availableObjects;
+ [SerializeField] public int loadedPrefabs;
+ [SerializeField] public List assetPaths = new List();
+ [SerializeField] public List objects = new List();
+ [SerializeField] public List prefabRefs = new List();
+
+ public void UpdateFromPool(ConfigPool pool)
+ {
+ configAsset = pool.Config.asset;
+ maxCount = pool.Config.poolCount;
+ expireTime = pool.Config.time;
+ totalObjects = pool.AllObjects.Count;
+
+ activeObjects = 0;
+ foreach (var obj in pool.AllObjects)
+ {
+ if (obj.isActive) activeObjects++;
+ }
+
+ // 计算所有队列中的可用对象总数
+ availableObjects = 0;
+ foreach (var queue in pool.AvailableObjectsByPath.Values)
+ {
+ availableObjects += queue.Count;
+ }
+ loadedPrefabs = pool.LoadedPrefabs.Count;
+
+ assetPaths.Clear();
+ assetPaths.AddRange(pool.LoadedPrefabs.Keys);
+
+ objects.Clear();
+ int objectIndex = 0;
+ foreach (var pooledObj in pool.AllObjects)
+ {
+ if (pooledObj.gameObject != null)
+ {
+ PoolObjectInfo info;
+ if (objectIndex < objects.Count)
+ {
+ info = objects[objectIndex];
+ }
+ else
+ {
+ info = new PoolObjectInfo();
+ objects.Add(info);
+ }
+
+ info.UpdateFromPooledObject(pooledObj, pool.Config.time);
+ objectIndex++;
+ }
+ }
+
+ prefabRefs.Clear();
+ int prefabIndex = 0;
+ foreach (var kvp in pool.LoadedPrefabs)
+ {
+ PrefabRefInfoDisplay info;
+ if (prefabIndex < prefabRefs.Count)
+ {
+ info = prefabRefs[prefabIndex];
+ }
+ else
+ {
+ info = new PrefabRefInfoDisplay();
+ prefabRefs.Add(info);
+ }
+
+ info.UpdateFromPrefabRefInfo(kvp.Value);
+ prefabIndex++;
+ }
+ }
+ }
+
+ ///
+ /// 配置组对象池 - 管理一个PoolConfig下的所有资源。
+ ///
+ public class ConfigPool
+ {
+ public readonly PoolConfig Config;
+ // 按资源路径分组的可用对象队列
+ public readonly Dictionary> AvailableObjectsByPath;
+ public readonly HashSet AllObjects;
+ public readonly Dictionary LoadedPrefabs;
+ public readonly Dictionary>> PendingRequests;
+ public readonly HashSet LoadingAssets;
+ public readonly Transform PoolRoot;
+
+ private readonly IResourceLoader _resourceLoader;
+
+ // GameObject到PooledObject的快速查找字典
+ private readonly Dictionary _gameObjectToPooledObject;
+
+ // 重用临时队列,避免重复创建。
+ private static Queue _tempQueue = new Queue();
+
+ // 重用过期对象列表,避免重复创建。
+ private static readonly List _expiredObjects = new List();
+ private static readonly List _expiredPrefabs = new List();
+
+ public ConfigPool(PoolConfig config, IResourceLoader resourceLoader)
+ {
+ _resourceLoader = resourceLoader;
+ Config = config;
+ AvailableObjectsByPath = new Dictionary>();
+ AllObjects = new HashSet();
+ LoadedPrefabs = new Dictionary();
+ PendingRequests = new Dictionary>>();
+ LoadingAssets = new HashSet();
+ _gameObjectToPooledObject = new Dictionary();
+
+ // 创建池根节点。
+ GameObject poolRootGo = new GameObject($"ConfigPool_{config.asset.Replace('/', '_')}");
+ PoolRoot = poolRootGo.transform;
+ PoolRoot.SetParent(GameObjectPool.Instance.poolContainer);
+ poolRootGo.SetActive(false);
+ }
+
+ public bool MatchesAsset(string assetPath)
+ {
+ return assetPath.StartsWith(Config.asset);
+ }
+
+ ///
+ /// 同步获取对象,如果资源未加载则同步加载。
+ ///
+ public GameObject Get(string assetPath)
+ {
+ if (!LoadedPrefabs.ContainsKey(assetPath))
+ {
+ if (LoadingAssets.Contains(assetPath))
+ {
+ Log.Warning($"资源 {assetPath} 正在异步加载中,同步获取可能导致重复加载,建议使用异步方法");
+ }
+
+ try
+ {
+ GameObject prefab = _resourceLoader.LoadPrefab(assetPath);
+ if (prefab != null)
+ {
+ LoadedPrefabs[assetPath] = new PrefabRefInfo(prefab, assetPath);
+ Log.Info($"同步加载资源成功: {assetPath}");
+ }
+ else
+ {
+ Log.Error($"同步加载资源失败: {assetPath}");
+ return null;
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error($"同步加载资源异常: {assetPath}, 错误: {e.Message}");
+ return null;
+ }
+ }
+
+ return GetInternal(assetPath);
+ }
+
+ ///
+ /// 异步获取对象。
+ ///
+ public async UniTask GetAsync(string assetPath, CancellationToken cancellationToken = default)
+ {
+ if (LoadedPrefabs.ContainsKey(assetPath))
+ {
+ return GetInternal(assetPath);
+ }
+
+ if (LoadingAssets.Contains(assetPath))
+ {
+ var completionSource = new UniTaskCompletionSource();
+ if (!PendingRequests.ContainsKey(assetPath))
+ {
+ PendingRequests[assetPath] = new List>();
+ }
+
+ PendingRequests[assetPath].Add(completionSource);
+
+ try
+ {
+ return await completionSource.Task.AttachExternalCancellation(cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ PendingRequests[assetPath].Remove(completionSource);
+ throw;
+ }
+ }
+
+ LoadingAssets.Add(assetPath);
+ try
+ {
+ GameObject prefab = await _resourceLoader.LoadPrefabAsync(assetPath, cancellationToken);
+ if (prefab != null)
+ {
+ LoadedPrefabs[assetPath] = new PrefabRefInfo(prefab, assetPath);
+ Log.Info($"异步加载资源成功: {assetPath}");
+
+ if (PendingRequests.ContainsKey(assetPath))
+ {
+ var requests = PendingRequests[assetPath];
+ PendingRequests.Remove(assetPath);
+
+ foreach (var request in requests)
+ {
+ try
+ {
+ var go = GetInternal(assetPath);
+ request.TrySetResult(go);
+ }
+ catch (Exception e)
+ {
+ request.TrySetException(e);
+ }
+ }
+ }
+
+ return GetInternal(assetPath);
+ }
+ else
+ {
+ throw new Exception($"无法异步加载资源: {assetPath}");
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error($"异步加载资源失败: {assetPath}, 错误: {e.Message}");
+
+ if (PendingRequests.ContainsKey(assetPath))
+ {
+ var requests = PendingRequests[assetPath];
+ PendingRequests.Remove(assetPath);
+
+ foreach (var request in requests)
+ {
+ request.TrySetException(e);
+ }
+ }
+
+ throw;
+ }
+ finally
+ {
+ LoadingAssets.Remove(assetPath);
+ }
+ }
+
+ ///
+ /// 创建新的池对象。
+ ///
+ private PooledObject CreatePooledObject(string assetPath)
+ {
+ var prefabRefInfo = LoadedPrefabs[assetPath];
+ GameObject instantiate = GameObject.Instantiate(prefabRefInfo.Prefab);
+ var pooledObj = new PooledObject(instantiate, assetPath);
+ AllObjects.Add(pooledObj);
+ _gameObjectToPooledObject[instantiate] = pooledObj;
+
+ prefabRefInfo.AddRef();
+
+ var monitor = instantiate.GetComponent();
+ if (monitor == null)
+ {
+ monitor = instantiate.AddComponent();
+ }
+
+ monitor.Initialize(this, pooledObj);
+ return pooledObj;
+ }
+
+ private GameObject GetInternal(string assetPath)
+ {
+ PooledObject pooledObj = null;
+
+ // 从按路径分组的队列中获取
+ if (AvailableObjectsByPath.TryGetValue(assetPath, out var queue) && queue.Count > 0)
+ {
+ // 清理已销毁的对象
+ while (queue.Count > 0)
+ {
+ var obj = queue.Dequeue();
+ if (obj.gameObject == null)
+ {
+ // 只有在引用计数未减少时才处理
+ if (!obj.isRefCountReduced)
+ {
+ OnObjectReallyDestroyed(obj);
+ }
+ else
+ {
+ // 只需要从集合中移除,不需要减少引用计数
+ AllObjects.Remove(obj);
+ }
+ continue;
+ }
+
+ pooledObj = obj;
+ break;
+ }
+ }
+
+ if (pooledObj == null)
+ {
+ if (AllObjects.Count < Config.poolCount)
+ {
+ pooledObj = CreatePooledObject(assetPath);
+ }
+ else
+ {
+ // 使用LINQ找到最旧的未使用对象(更高效)
+ var oldestObj = AllObjects
+ .Where(obj => !obj.isActive)
+ .OrderBy(obj => obj.lastUsedTime)
+ .FirstOrDefault();
+
+ if (oldestObj != null)
+ {
+ DestroyPooledObject(oldestObj);
+ pooledObj = CreatePooledObject(assetPath);
+ }
+ else
+ {
+ Log.Warning($"对象池已满且所有对象都在使用中: {Config.asset},无法创建新对象 {assetPath}");
+ return null;
+ }
+ }
+ }
+
+ pooledObj.isActive = true;
+ pooledObj.lastUsedTime = Time.time;
+ pooledObj.gameObject.SetActive(true);
+
+ return pooledObj.gameObject;
+ }
+
+ public void Return(GameObject go)
+ {
+ if (!_gameObjectToPooledObject.TryGetValue(go, out var pooledObj))
+ {
+ return;
+ }
+
+ if (pooledObj != null && pooledObj.isActive)
+ {
+ pooledObj.isActive = false;
+ pooledObj.lastUsedTime = Time.time;
+
+ go.SetActive(false);
+ go.transform.SetParent(PoolRoot);
+ go.transform.localPosition = Vector3.zero;
+ go.transform.localRotation = Quaternion.identity;
+ go.transform.localScale = Vector3.one;
+
+ // 放入按路径分组的队列
+ if (!AvailableObjectsByPath.TryGetValue(pooledObj.assetPath, out var queue))
+ {
+ queue = new Queue();
+ AvailableObjectsByPath[pooledObj.assetPath] = queue;
+ }
+ queue.Enqueue(pooledObj);
+ }
+ }
+
+ public void OnObjectDestroyed(PooledObject pooledObj)
+ {
+ // 从GameObjectPool的字典中移除,防止内存泄漏
+ if (pooledObj.gameObject != null)
+ {
+ GameObjectPool.Instance.RemoveGameObjectReference(pooledObj.gameObject);
+ }
+
+ // 防止重复减少引用计数
+ if (!pooledObj.isRefCountReduced)
+ {
+ OnObjectReallyDestroyed(pooledObj);
+ }
+ else
+ {
+ // 只需要从集合中移除
+ AllObjects.Remove(pooledObj);
+ CleanAvailableQueue(pooledObj);
+ }
+ }
+
+ private void OnObjectReallyDestroyed(PooledObject pooledObj)
+ {
+ // 标记引用计数已减少,防止重复处理
+ if (pooledObj.isRefCountReduced)
+ {
+ return;
+ }
+
+ pooledObj.isRefCountReduced = true;
+ AllObjects.Remove(pooledObj);
+
+ // 从快速查找字典中移除
+ if (pooledObj.gameObject != null)
+ {
+ _gameObjectToPooledObject.Remove(pooledObj.gameObject);
+ }
+
+ // 减少预制体引用计数
+ if (LoadedPrefabs.TryGetValue(pooledObj.assetPath, out PrefabRefInfo refInfo))
+ {
+ refInfo.RemoveRef();
+ }
+
+ CleanAvailableQueue(pooledObj);
+ }
+
+ // 清理可用队列
+ private void CleanAvailableQueue(PooledObject pooledObj)
+ {
+ // 从对应路径的队列中移除
+ if (AvailableObjectsByPath.TryGetValue(pooledObj.assetPath, out var queue))
+ {
+ _tempQueue.Clear();
+ while (queue.Count > 0)
+ {
+ var obj = queue.Dequeue();
+ if (obj != pooledObj)
+ {
+ _tempQueue.Enqueue(obj);
+ }
+ }
+
+ // 交换队列
+ (queue, _tempQueue) = (_tempQueue, queue);
+
+ // 如果队列为空,从字典中移除
+ if (queue.Count == 0)
+ {
+ AvailableObjectsByPath.Remove(pooledObj.assetPath);
+ }
+ }
+ }
+
+ private void DestroyPooledObject(PooledObject pooledObj)
+ {
+ // 先标记引用计数已减少
+ if (pooledObj.isRefCountReduced)
+ {
+ return;
+ }
+
+ // 先处理引用计数
+ OnObjectReallyDestroyed(pooledObj);
+
+ if (pooledObj.gameObject != null)
+ {
+ GameObject.Destroy(pooledObj.gameObject);
+ }
+ }
+
+ public void CheckExpiredObjects()
+ {
+ if (Config.time <= 0) return;
+
+ float currentTime = Time.time;
+
+ // 重用过期对象列表
+ _expiredObjects.Clear();
+
+ foreach (var obj in AllObjects)
+ {
+ if (!obj.isActive && !obj.isRefCountReduced && (currentTime - obj.lastUsedTime) > Config.time)
+ {
+ _expiredObjects.Add(obj);
+ }
+ }
+
+ foreach (var expiredObj in _expiredObjects)
+ {
+ DestroyPooledObject(expiredObj);
+ }
+
+ // 重建所有路径的可用队列
+ foreach (var kvp in AvailableObjectsByPath.ToList())
+ {
+ var assetPath = kvp.Key;
+ var queue = kvp.Value;
+
+ _tempQueue.Clear();
+ while (queue.Count > 0)
+ {
+ var obj = queue.Dequeue();
+ if (AllObjects.Contains(obj) && !obj.isRefCountReduced)
+ {
+ _tempQueue.Enqueue(obj);
+ }
+ }
+
+ if (_tempQueue.Count > 0)
+ {
+ // 交换队列
+ (queue, _tempQueue) = (_tempQueue, queue);
+ }
+ else
+ {
+ // 队列为空,从字典中移除
+ AvailableObjectsByPath.Remove(assetPath);
+ }
+ }
+
+ CheckExpiredPrefabs();
+ }
+
+ private void CheckExpiredPrefabs()
+ {
+ if (Config.time <= 0) return;
+
+ // 重用过期预制体列表
+ _expiredPrefabs.Clear();
+
+ foreach (var kvp in LoadedPrefabs)
+ {
+ var refInfo = kvp.Value;
+ if (refInfo.CanUnload(Config.time))
+ {
+ _expiredPrefabs.Add(kvp.Key);
+ }
+ }
+
+ foreach (var assetPath in _expiredPrefabs)
+ {
+ var refInfo = LoadedPrefabs[assetPath];
+ Log.Info($"卸载过期预制体: {assetPath}, 引用计数: {refInfo.RefCount}");
+
+ _resourceLoader.UnloadAsset(refInfo.Prefab);
+ LoadedPrefabs.Remove(assetPath);
+ }
+ }
+
+ public void Clear()
+ {
+ foreach (var obj in AllObjects)
+ {
+ if (obj.gameObject != null)
+ {
+ GameObject.Destroy(obj.gameObject);
+ }
+ }
+
+ AllObjects.Clear();
+ AvailableObjectsByPath.Clear();
+ _gameObjectToPooledObject.Clear();
+
+ foreach (var kvp in LoadedPrefabs)
+ {
+ var refInfo = kvp.Value;
+ if (refInfo.Prefab != null)
+ {
+ Log.Info($"清理时卸载预制体: {kvp.Key}, 引用计数: {refInfo.RefCount}");
+ _resourceLoader.UnloadAsset(refInfo.Prefab);
+ }
+ }
+
+ LoadedPrefabs.Clear();
+ LoadingAssets.Clear();
+
+ foreach (var requests in PendingRequests.Values)
+ {
+ foreach (var request in requests)
+ {
+ request.TrySetCanceled();
+ }
+ }
+
+ PendingRequests.Clear();
+
+ if (PoolRoot != null)
+ {
+ GameObject.Destroy(PoolRoot.gameObject);
+ }
+ }
+ }
+
+ ///
+ /// 对象销毁监听器。
+ ///
+ public class PoolObjectMonitor : MonoBehaviour
+ {
+ private ConfigPool _pool;
+ private PooledObject _pooledObject;
+
+ public void Initialize(ConfigPool pool, PooledObject pooledObject)
+ {
+ _pool = pool;
+ _pooledObject = pooledObject;
+ }
+
+ private void OnDestroy()
+ {
+ if (_pool != null && _pooledObject != null)
+ {
+ _pool.OnObjectDestroyed(_pooledObject);
+ }
+ }
+ }
+
+ ///
+ /// 游戏对象池管理器。
+ ///
+ public class GameObjectPool : MonoBehaviour
+ {
+ private static GameObjectPool _instance;
+
+ public static GameObjectPool Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ GameObject go = new GameObject("[GameObjectPool]");
+ _instance = go.AddComponent();
+ DontDestroyOnLoad(go);
+ }
+
+ return _instance;
+ }
+ }
+
+ [Header("检查间隔")] public float checkInterval = 10f;
+
+ [Header("资源加载器")] public bool useEngineResourceLoader = true;
+
+ [Header("配置路径")] public string poolConfigPath = "Assets/Bundles/Configs/ScriptableObject/PoolConfig";
+
+ [Header("Inspector显示设置")] public bool showDetailedInfo = true;
+
+ [Header("池状态信息")] [SerializeField] private List poolInfos = new List();
+
+ public Transform poolContainer;
+ internal IResourceLoader _resourceLoader;
+
+ private List _poolConfigs;
+ private List _configPools;
+ private Dictionary _gameObjectToPool;
+
+ // 重用预加载对象列表
+ private static readonly List _preloadedObjects = new List();
+
+ private float _lastCleanupTime;
+
+ private void Awake()
+ {
+ if (_instance == null)
+ {
+ _instance = this;
+ DontDestroyOnLoad(gameObject);
+ Initialize();
+ }
+ else if (_instance != this)
+ {
+ Destroy(gameObject);
+ }
+ }
+
+ private void Initialize()
+ {
+ _resourceLoader = useEngineResourceLoader ? new AlicizaResourceLoader() as IResourceLoader : new DefaultResourceLoader() as IResourceLoader;
+
+ GameObject containerGo = new GameObject("PoolContainer");
+ poolContainer = containerGo.transform;
+ poolContainer.SetParent(transform);
+
+ _configPools = new List();
+ _gameObjectToPool = new Dictionary();
+
+ try
+ {
+ _poolConfigs = ModuleSystem.GetModule().LoadAsset(poolConfigPath).configs;
+ _poolConfigs.Sort((a, b) => b.asset.Length.CompareTo(a.asset.Length));
+
+ foreach (var config in _poolConfigs)
+ {
+ var configPool = new ConfigPool(config, _resourceLoader);
+ _configPools.Add(configPool);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error($"加载对象池配置失败: {e.Message}");
+ _poolConfigs = new List();
+ }
+
+ // 初始化清理时间
+ _lastCleanupTime = Time.time;
+ }
+
+ private void Update()
+ {
+ if (Time.time - _lastCleanupTime >= checkInterval)
+ {
+ PerformCleanup();
+ _lastCleanupTime = Time.time;
+ }
+ }
+
+ ///
+ /// 执行对象池清理。
+ ///
+ private void PerformCleanup()
+ {
+ if (_configPools == null || _configPools.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var pool in _configPools)
+ {
+ pool.CheckExpiredObjects();
+ }
+ }
+
+ ///
+ /// 手动触发一次清理。
+ ///
+ public void ForceCleanup()
+ {
+ PerformCleanup();
+ _lastCleanupTime = Time.time;
+ }
+
+ ///
+ /// 内部方法:从字典中移除GameObject引用,防止内存泄漏。
+ ///
+ internal void RemoveGameObjectReference(GameObject go)
+ {
+ if (go != null)
+ {
+ _gameObjectToPool.Remove(go);
+ }
+ }
+
+ // Editor专用的刷新。
+ private void UpdateInspectorInfo()
+ {
+ poolInfos.Clear();
+ foreach (var pool in _configPools)
+ {
+ var info = new ConfigPoolInfo();
+ info.UpdateFromPool(pool);
+ poolInfos.Add(info);
+ }
+ }
+
+ public void SetResourceLoader(IResourceLoader resourceLoader)
+ {
+ _resourceLoader = resourceLoader;
+ }
+
+ public GameObject GetGameObject(string assetPath, Transform parent = null)
+ {
+ ConfigPool pool = FindConfigPool(assetPath);
+ GameObject go = null;
+
+ if (pool != null)
+ {
+ go = pool.Get(assetPath);
+ }
+ else
+ {
+ go = _resourceLoader.LoadGameObject(assetPath, parent);
+ }
+
+ if (go != null && pool != null)
+ {
+ _gameObjectToPool[go] = pool;
+ go.transform.SetParent(parent);
+ }
+
+ return go;
+ }
+
+ public async UniTask GetGameObjectAsync(string assetPath, Transform parent = null, CancellationToken cancellationToken = default)
+ {
+ ConfigPool pool = FindConfigPool(assetPath);
+ GameObject go = null;
+
+ if (pool != null)
+ {
+ go = await pool.GetAsync(assetPath, cancellationToken);
+ }
+ else
+ {
+ go = await _resourceLoader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
+ }
+
+ if (go != null && pool != null)
+ {
+ _gameObjectToPool[go] = pool;
+ go.transform.SetParent(parent); // 设置父节点
+ }
+
+ return go;
+ }
+
+ public void Release(GameObject go)
+ {
+ if (go == null) return;
+
+ if (_gameObjectToPool.TryGetValue(go, out ConfigPool pool))
+ {
+ pool.Return(go);
+ _gameObjectToPool.Remove(go);
+ }
+ else
+ {
+ Destroy(go);
+ }
+ }
+
+ public async UniTask PreloadAsync(string assetPath, int count = 1, CancellationToken cancellationToken = default)
+ {
+ ConfigPool pool = FindConfigPool(assetPath);
+ if (pool == null)
+ {
+ Log.Warning($"资源 {assetPath} 没有对应的池配置,无法预加载");
+ return;
+ }
+
+ // 优化:重用预加载对象列表
+ _preloadedObjects.Clear();
+ for (int i = 0; i < count; i++)
+ {
+ GameObject go = await pool.GetAsync(assetPath, cancellationToken);
+ if (go != null)
+ {
+ _preloadedObjects.Add(go);
+ }
+ }
+
+ foreach (var go in _preloadedObjects)
+ {
+ pool.Return(go);
+ _gameObjectToPool.Remove(go);
+ }
+ }
+
+ public void Preload(string assetPath, int count = 1)
+ {
+ ConfigPool pool = FindConfigPool(assetPath);
+ if (pool == null)
+ {
+ Log.Warning($"资源 {assetPath} 没有对应的池配置,无法预加载");
+ return;
+ }
+
+ // 优化:重用预加载对象列表
+ _preloadedObjects.Clear();
+ for (int i = 0; i < count; i++)
+ {
+ GameObject go = pool.Get(assetPath);
+ if (go != null)
+ {
+ _preloadedObjects.Add(go);
+ }
+ }
+
+ foreach (var go in _preloadedObjects)
+ {
+ pool.Return(go);
+ _gameObjectToPool.Remove(go);
+ }
+ }
+
+ private ConfigPool FindConfigPool(string assetPath)
+ {
+ foreach (var pool in _configPools)
+ {
+ if (pool.MatchesAsset(assetPath))
+ {
+ return pool;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// 手动刷新Inspector信息
+ ///
+ public void RefreshInspectorInfo()
+ {
+ UpdateInspectorInfo();
+ }
+
+ public void ClearAllPools()
+ {
+ foreach (var pool in _configPools)
+ {
+ pool.Clear();
+ }
+
+ _gameObjectToPool.Clear();
+ poolInfos.Clear();
+ }
+
+ private void OnDestroy()
+ {
+ ClearAllPools();
+ }
+ }
+
+ public interface IResourceLoader
+ {
+ GameObject LoadPrefab(string location);
+ UniTask LoadPrefabAsync(string location, CancellationToken cancellationToken = default);
+ GameObject LoadGameObject(string location, Transform parent = null);
+ UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default);
+ void UnloadAsset(GameObject gameObject);
+ }
+
+ public class DefaultResourceLoader : IResourceLoader
+ {
+ public GameObject LoadPrefab(string location)
+ {
+ return Resources.Load(location);
+ }
+
+ public async UniTask LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
+ {
+ return await Resources.LoadAsync(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
+ }
+
+ public GameObject LoadGameObject(string location, Transform parent = null)
+ {
+ var prefab = Resources.Load(location);
+ if (prefab == null) return null;
+
+ var instance = GameObject.Instantiate(prefab);
+ if (instance != null && parent != null)
+ {
+ instance.transform.SetParent(parent);
+ }
+
+ return instance;
+ }
+
+ public async UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
+ {
+ var prefab = await Resources.LoadAsync(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
+ if (prefab == null) return null;
+
+ var instance = GameObject.Instantiate(prefab);
+ if (instance != null && parent != null)
+ {
+ instance.transform.SetParent(parent);
+ }
+
+ return instance;
+ }
+
+ public void UnloadAsset(GameObject gameObject)
+ {
+ Resources.UnloadAsset(gameObject);
+ }
+ }
+
+ public class AlicizaResourceLoader : IResourceLoader
+ {
+ private IResourceModule _resourceModule;
+
+ private void CheckInit()
+ {
+ if (_resourceModule == null)
+ {
+ _resourceModule = ModuleSystem.GetModule();
+ }
+ }
+
+ public GameObject LoadPrefab(string location)
+ {
+ CheckInit();
+ return _resourceModule.LoadAsset(location);
+ }
+
+ public async UniTask LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
+ {
+ CheckInit();
+ return await _resourceModule.LoadAssetAsync(location, cancellationToken);
+ }
+
+ public GameObject LoadGameObject(string location, Transform parent = null)
+ {
+ CheckInit();
+ return _resourceModule.LoadGameObject(location, parent);
+ }
+
+ public async UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
+ {
+ CheckInit();
+ return await _resourceModule.LoadGameObjectAsync(location, parent, cancellationToken);
+ }
+
+ public void UnloadAsset(GameObject gameObject)
+ {
+ CheckInit();
+ _resourceModule.UnloadAsset(gameObject);
+ }
+ }
+
+ public static class ObjectPoolUtil
+ {
+ public static GameObject LoadGameObject(string assetPath, Transform parent = null)
+ {
+ return GameObjectPool.Instance.GetGameObject(assetPath);
+ }
+
+ public static async UniTask LoadGameObjectAsync(string assetPath, Transform parent = null, CancellationToken cancellationToken = default)
+ {
+ return await GameObjectPool.Instance.GetGameObjectAsync(assetPath, parent, cancellationToken);
+ }
+
+ public static void Release(GameObject go)
+ {
+ GameObjectPool.Instance.Release(go);
+ }
+ }
+}
+
+
diff --git a/Runtime/ABase/GameObjectPool/GameObjectPool.cs.meta b/Runtime/ABase/GameObjectPool/GameObjectPool.cs.meta
new file mode 100644
index 0000000..79c907a
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/GameObjectPool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ce0e8ead006ba324eaf2410a3dd556a5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs b/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs
new file mode 100644
index 0000000..9e69cc4
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs
@@ -0,0 +1,322 @@
+using UnityEditor;
+using UnityEngine;
+
+#if UNITY_EDITOR
+namespace AlicizaX
+{
+ [CustomEditor(typeof(GameObjectPool))]
+ public class GameObjectPoolEditor : UnityEditor.Editor
+ {
+ private bool[] _poolFoldouts;
+ private bool[] _prefabFoldouts;
+ private float _lastRefreshTime;
+ private const float AUTO_REFRESH_INTERVAL = 0.1f;
+
+ // 缓存序列化属性,避免重复查找
+ private SerializedProperty _poolInfosProperty;
+
+ private void OnEnable()
+ {
+ _poolInfosProperty = serializedObject.FindProperty("poolInfos");
+ _lastRefreshTime = Time.time;
+ }
+
+ public override void OnInspectorGUI()
+ {
+ var pool = (GameObjectPool)target;
+
+ // 更新序列化对象
+ serializedObject.Update();
+
+ // 绘制默认Inspector
+ DrawDefaultInspector();
+ EditorGUILayout.Space();
+
+ // 手动刷新按钮
+ if (GUILayout.Button("刷新池状态信息"))
+ {
+ RefreshPoolInfo(pool);
+ }
+
+ // 检查是否需要自动刷新
+ bool shouldAutoRefresh = EditorApplication.isPlaying && pool.showDetailedInfo &&
+ Selection.activeGameObject == pool.gameObject &&
+ Time.time - _lastRefreshTime > AUTO_REFRESH_INTERVAL;
+
+ if (shouldAutoRefresh)
+ {
+ RefreshPoolInfo(pool);
+ }
+
+ if (!pool.showDetailedInfo)
+ {
+ serializedObject.ApplyModifiedProperties();
+ return;
+ }
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("对象池详细信息", EditorStyles.boldLabel);
+
+ // 重新获取属性以确保数据是最新的
+ _poolInfosProperty = serializedObject.FindProperty("poolInfos");
+
+ if (_poolInfosProperty != null && _poolInfosProperty.arraySize > 0)
+ {
+ DrawPoolInfos();
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("暂无池信息,请等待系统初始化或点击刷新按钮", MessageType.Info);
+ }
+
+ // 显示自动刷新状态
+ if (Selection.activeGameObject == pool.gameObject)
+ {
+ EditorGUILayout.HelpBox("Inspector正在自动刷新 (仅在选中时)", MessageType.Info);
+ }
+
+ // 应用修改的属性
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ private void RefreshPoolInfo(GameObjectPool pool)
+ {
+ pool.RefreshInspectorInfo();
+ _lastRefreshTime = Time.time;
+ serializedObject.Update(); // 立即更新序列化对象
+
+ // 标记需要重绘
+ if (Selection.activeGameObject == pool.gameObject)
+ {
+ EditorUtility.SetDirty(pool);
+ Repaint();
+ }
+ }
+
+ private void DrawPoolInfos()
+ {
+ int poolCount = _poolInfosProperty.arraySize;
+
+ // 确保折叠状态数组大小正确
+ if (_poolFoldouts == null || _poolFoldouts.Length != poolCount)
+ {
+ bool[] oldPoolFoldouts = _poolFoldouts;
+ bool[] oldPrefabFoldouts = _prefabFoldouts;
+
+ _poolFoldouts = new bool[poolCount];
+ _prefabFoldouts = new bool[poolCount];
+
+ // 保持之前的折叠状态
+ if (oldPoolFoldouts != null)
+ {
+ for (int i = 0; i < Mathf.Min(oldPoolFoldouts.Length, poolCount); i++)
+ {
+ _poolFoldouts[i] = oldPoolFoldouts[i];
+ if (oldPrefabFoldouts != null && i < oldPrefabFoldouts.Length)
+ {
+ _prefabFoldouts[i] = oldPrefabFoldouts[i];
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < poolCount; i++)
+ {
+ DrawPoolInfo(i);
+ }
+ }
+
+ private void DrawPoolInfo(int poolIndex)
+ {
+ var poolInfo = _poolInfosProperty.GetArrayElementAtIndex(poolIndex);
+ if (poolInfo == null) return;
+
+ var configAssetProp = poolInfo.FindPropertyRelative("configAsset");
+ var totalObjectsProp = poolInfo.FindPropertyRelative("totalObjects");
+ var maxCountProp = poolInfo.FindPropertyRelative("maxCount");
+ var activeObjectsProp = poolInfo.FindPropertyRelative("activeObjects");
+
+ if (configAssetProp == null || totalObjectsProp == null || maxCountProp == null || activeObjectsProp == null)
+ return;
+
+ string configAsset = configAssetProp.stringValue;
+ int totalObjects = totalObjectsProp.intValue;
+ int maxCount = maxCountProp.intValue;
+ int activeObjects = activeObjectsProp.intValue;
+
+ EditorGUILayout.BeginVertical("box");
+
+ // 使用Rect布局来精确控制Foldout的大小
+ Rect rect = EditorGUILayout.GetControlRect();
+ Rect foldoutRect = new Rect(rect.x, rect.y, 15, rect.height);
+ Rect progressRect = new Rect(rect.x + 20, rect.y, rect.width - 120, rect.height);
+ Rect labelRect = new Rect(rect.x + rect.width - 95, rect.y, 95, rect.height);
+
+ // 绘制折叠按钮
+ _poolFoldouts[poolIndex] = EditorGUI.Foldout(foldoutRect, _poolFoldouts[poolIndex], GUIContent.none);
+
+ // 使用率进度条
+ float usage = maxCount > 0 ? (float)totalObjects / maxCount : 0f;
+ EditorGUI.ProgressBar(progressRect, usage, $"{configAsset} ({totalObjects}/{maxCount})");
+
+ // 活跃对象数
+ EditorGUI.LabelField(labelRect, $"活跃:{activeObjects}", EditorStyles.miniLabel);
+
+ if (_poolFoldouts[poolIndex])
+ {
+ EditorGUI.indentLevel++;
+ DrawPoolDetails(poolInfo, poolIndex);
+ EditorGUI.indentLevel--;
+ }
+
+ EditorGUILayout.EndVertical();
+ EditorGUILayout.Space();
+ }
+
+ private void DrawPoolDetails(SerializedProperty poolInfo, int poolIndex)
+ {
+ var configAssetProp = poolInfo.FindPropertyRelative("configAsset");
+ var maxCountProp = poolInfo.FindPropertyRelative("maxCount");
+ var expireTimeProp = poolInfo.FindPropertyRelative("expireTime");
+ var loadedPrefabsProp = poolInfo.FindPropertyRelative("loadedPrefabs");
+
+ if (configAssetProp != null)
+ EditorGUILayout.LabelField($"配置路径: {configAssetProp.stringValue}");
+ if (maxCountProp != null)
+ EditorGUILayout.LabelField($"最大数量: {maxCountProp.intValue}");
+ if (expireTimeProp != null)
+ EditorGUILayout.LabelField($"过期时间: {expireTimeProp.floatValue}s");
+ if (loadedPrefabsProp != null)
+ EditorGUILayout.LabelField($"已加载预制体: {loadedPrefabsProp.intValue}");
+
+ EditorGUILayout.Space();
+
+ // 绘制预制体引用信息
+ DrawPrefabRefs(poolInfo, poolIndex);
+
+ // 绘制对象详细信息
+ DrawObjectDetails(poolInfo);
+ }
+
+ private void DrawPrefabRefs(SerializedProperty poolInfo, int poolIndex)
+ {
+ var prefabRefsProp = poolInfo.FindPropertyRelative("prefabRefs");
+ if (prefabRefsProp == null || prefabRefsProp.arraySize <= 0) return;
+
+ // 使用简单的Foldout,不指定宽度
+ _prefabFoldouts[poolIndex] = EditorGUILayout.Foldout(_prefabFoldouts[poolIndex], "预制体引用信息:");
+
+ if (_prefabFoldouts[poolIndex])
+ {
+ EditorGUI.indentLevel++;
+
+ for (int j = 0; j < prefabRefsProp.arraySize; j++)
+ {
+ DrawPrefabRefInfo(prefabRefsProp.GetArrayElementAtIndex(j));
+ }
+
+ EditorGUI.indentLevel--;
+ }
+
+ EditorGUILayout.Space();
+ }
+
+ private void DrawPrefabRefInfo(SerializedProperty prefabRef)
+ {
+ if (prefabRef == null) return;
+
+ var assetPathProp = prefabRef.FindPropertyRelative("assetPath");
+ var refCountProp = prefabRef.FindPropertyRelative("refCount");
+ var lastAccessTimeProp = prefabRef.FindPropertyRelative("lastAccessTime");
+ var prefabObjProp = prefabRef.FindPropertyRelative("prefab");
+
+ EditorGUILayout.BeginHorizontal("box");
+
+ EditorGUILayout.BeginVertical();
+ if (assetPathProp != null)
+ EditorGUILayout.LabelField($"{System.IO.Path.GetFileName(assetPathProp.stringValue)}", EditorStyles.boldLabel);
+ if (refCountProp != null)
+ EditorGUILayout.LabelField($"引用计数: {refCountProp.intValue}", EditorStyles.miniLabel);
+ if (lastAccessTimeProp != null)
+ EditorGUILayout.LabelField($"最后访问: {(Time.time - lastAccessTimeProp.floatValue):F1}秒前", EditorStyles.miniLabel);
+ EditorGUILayout.EndVertical();
+
+ if (prefabObjProp != null)
+ EditorGUILayout.ObjectField(prefabObjProp.objectReferenceValue, typeof(GameObject), false, GUILayout.Width(100));
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void DrawObjectDetails(SerializedProperty poolInfo)
+ {
+ var objectsProp = poolInfo.FindPropertyRelative("objects");
+ if (objectsProp == null || objectsProp.arraySize <= 0) return;
+
+ EditorGUILayout.LabelField("对象详情:", EditorStyles.boldLabel);
+
+ for (int j = 0; j < objectsProp.arraySize; j++)
+ {
+ DrawObjectInfo(objectsProp.GetArrayElementAtIndex(j));
+ }
+ }
+
+ private void DrawObjectInfo(SerializedProperty obj)
+ {
+ if (obj == null) return;
+
+ var objNameProp = obj.FindPropertyRelative("objectName");
+ var objAssetPathProp = obj.FindPropertyRelative("assetPath");
+ var isActiveProp = obj.FindPropertyRelative("isActive");
+ var remainingTimeProp = obj.FindPropertyRelative("remainingTime");
+ var expireProgressProp = obj.FindPropertyRelative("expireProgress");
+ var gameObjectProp = obj.FindPropertyRelative("gameObject");
+
+ EditorGUILayout.BeginHorizontal("box");
+
+ // 状态颜色指示器
+ bool isActive = isActiveProp?.boolValue ?? false;
+ var statusColor = isActive ? Color.green : Color.yellow;
+ var prevColor = GUI.color;
+ GUI.color = statusColor;
+ EditorGUILayout.LabelField("●", GUILayout.Width(15));
+ GUI.color = prevColor;
+
+ EditorGUILayout.BeginVertical();
+
+ // 对象名称和路径
+ string objName = objNameProp?.stringValue ?? "Unknown";
+ string objAssetPath = objAssetPathProp?.stringValue ?? "";
+ EditorGUILayout.LabelField($"{objName} ({System.IO.Path.GetFileName(objAssetPath)})", EditorStyles.boldLabel);
+ EditorGUILayout.LabelField($"状态: {(isActive ? "活跃" : "空闲")}", EditorStyles.miniLabel);
+
+ // 过期进度条
+ if (!isActive && remainingTimeProp != null && expireProgressProp != null)
+ {
+ float remainingTime = remainingTimeProp.floatValue;
+ float expireProgress = expireProgressProp.floatValue;
+
+ if (remainingTime >= 0)
+ {
+ Rect expireRect = GUILayoutUtility.GetRect(100, 16, GUILayout.ExpandWidth(true), GUILayout.Height(16));
+ EditorGUI.ProgressBar(expireRect, expireProgress, $"释放倒计时: {remainingTime:F1}s");
+ }
+ }
+
+ EditorGUILayout.EndVertical();
+
+ // GameObject引用
+ if (gameObjectProp != null)
+ EditorGUILayout.ObjectField(gameObjectProp.objectReferenceValue, typeof(GameObject), true, GUILayout.Width(100));
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ public override bool RequiresConstantRepaint()
+ {
+ // 只有在选中对象池时才需要持续重绘
+ var pool = target as GameObjectPool;
+ return pool != null && pool.showDetailedInfo && Selection.activeGameObject == pool.gameObject;
+ }
+ }
+}
+#endif
diff --git a/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs.meta b/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs.meta
new file mode 100644
index 0000000..247e064
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: df436879f8854a95b5a92e8b77772189
+timeCreated: 1773109368
\ No newline at end of file
diff --git a/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs b/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs
new file mode 100644
index 0000000..7a00c9d
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace AlicizaX
+{
+ [CreateAssetMenu(fileName = "PoolConfig", menuName = "GameplaySystem/PoolConfig", order = 10)]
+ public class PoolConfigScriptableObject : ScriptableObject
+ {
+ public List configs;
+ }
+
+}
diff --git a/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs.meta b/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs.meta
new file mode 100644
index 0000000..b910757
--- /dev/null
+++ b/Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 32739bac255eb5f428628746c6e427f4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Resource/Resource/ResourceModule.cs b/Runtime/Resource/Resource/ResourceModule.cs
index 5a36ef2..624148f 100644
--- a/Runtime/Resource/Resource/ResourceModule.cs
+++ b/Runtime/Resource/Resource/ResourceModule.cs
@@ -644,11 +644,6 @@ namespace AlicizaX.Resource.Runtime
return;
}
- if (string.IsNullOrEmpty(location))
- {
- throw new GameFrameworkException("Asset name is invalid.");
- }
-
if (!CheckLocationValid(location, packageName))
{
Log.Error($"Could not found location [{location}].");