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 GameObjectPoolUtil { 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); } } }