概述 入门系列中我们实现了基础对象池——一个队列管理子弹的复用。但在真实项目中,对象池需要处理多种类型、预热策略、异步加载、跨场景生命周期等问题。
Addressables 是 Unity 的现代资源管理系统,取代了传统的 Resources.Load 和 Asset Bundle 手动管理。它与对象池结合,可以构建完整的资源生命周期管理方案。
1. 对象池进阶 1.1 通用接口 1 2 3 4 5 6 7 8 9 10 11 12 13 public interface IPoolable { void OnSpawn () ; void OnDespawn () ; } public interface IObjectPool <T > where T : class { T Get () ; void Return (T obj ) ; void Prewarm (int count ) ; void Clear () ; }
所有可池化对象实现 IPoolable,在出入池时执行自己的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Bullet : MonoBehaviour , IPoolable { private ObjectPool<Bullet> _pool; public void Init (ObjectPool<Bullet> pool ) { _pool = pool; } public void OnSpawn () { gameObject.SetActive(true ); transform.position = Vector3.zero; _rigidbody.velocity = Vector3.zero; } public void OnDespawn () { gameObject.SetActive(false ); } private void OnDisable () { CancelInvoke(); } public void ReturnToPool () { _pool?.Return(this ); } }
1.2 通用对象池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class ObjectPool <T > : IObjectPool <T > where T : MonoBehaviour , IPoolable { private readonly Func<T> _factory; private readonly Stack<T> _pool = new (); private readonly HashSet<T> _active = new (); private readonly int _maxSize; public ObjectPool ( Func<T> factory, int prewarmCount = 0 , int maxSize = 100 ) { _factory = factory; _maxSize = maxSize; if (prewarmCount > 0 ) Prewarm(prewarmCount); } public T Get () { T obj; if (_pool.Count > 0 ) { obj = _pool.Pop(); } else { obj = _factory(); } _active.Add(obj); obj.OnSpawn(); return obj; } public void Return (T obj ) { if (!_active.Remove(obj)) return ; obj.OnDespawn(); if (_pool.Count < _maxSize) _pool.Push(obj); else GameObject.Destroy(obj.gameObject); } public void Prewarm (int count ) { var temp = new List<T>(); for (int i = 0 ; i < count; i++) { var obj = _factory(); obj.OnDespawn(); obj.gameObject.SetActive(false ); temp.Add(obj); } foreach (var obj in temp) _pool.Push(obj); } public void Clear () { foreach (var obj in _pool) GameObject.Destroy(obj.gameObject); _pool.Clear(); _active.Clear(); } }
2. Addressables 基础 2.1 配置 Addressables
通过 Window > Asset Management > Addressables > Groups 打开面板
点击 Create Addressables Settings
将资源拖入 Group,设置 Addressable Name(资源唯一标识)
2.2 加载资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;private async UniTask<GameObject> LoadEnemyPrefabAsync (){ var handle = Addressables.LoadAssetAsync<GameObject>("Enemy" ); await handle.ToUniTask(); return handle.Result; } private async UniTask LoadGameSceneAsync (){ await Addressables.LoadSceneAsync("GameScene" ).ToUniTask(); }
2.3 Addressables + 对象池 两者的结合是最佳实践——Addressables 负责异步加载预制体,对象池负责复用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class AddressablePool <T > where T : MonoBehaviour , IPoolable { private readonly string _addressableKey; private readonly Stack<T> _pool = new (); private readonly HashSet<T> _active = new (); private GameObject _prefab; private bool _isLoaded; private readonly int _maxSize; public AddressablePool (string addressableKey, int maxSize = 100 ) { _addressableKey = addressableKey; _maxSize = maxSize; } public async UniTask InitializeAsync () { if (_isLoaded) return ; var handle = Addressables.LoadAssetAsync<GameObject>(_addressableKey); await handle.ToUniTask(); _prefab = handle.Result; _isLoaded = true ; } public async UniTask<T> GetAsync () { if (!_isLoaded) await InitializeAsync(); T obj; if (_pool.Count > 0 ) { obj = _pool.Pop(); obj.gameObject.SetActive(true ); } else { var go = Addressables.InstantiateAsync(_addressableKey).WaitForCompletion(); obj = go.GetComponent<T>(); } _active.Add(obj); obj.OnSpawn(); return obj; } public void Return (T obj ) { if (!_active.Remove(obj)) return ; obj.OnDespawn(); if (_pool.Count < _maxSize) { obj.gameObject.SetActive(false ); _pool.Push(obj); } else { Addressables.ReleaseInstance(obj.gameObject); } } }
3. 资源依赖与分组策略 Addressables Groups 的合理分组直接影响加载性能:
分组
内容
加载策略
CoreAssets
基础 UI、字体、着色器
启动时预加载,常驻内存
Characters_Shared
角色共享资源(骨架、材质)
按需加载,使用后释放
Level_01
关卡特有资源
进入关卡时加载,离开时卸载
Effects
特效预制体
通过对象池懒加载
1 2 3 4 5 6 7 8 9 private async UniTask InitializeCoreAssetsAsync (){ var handle = Addressables.LoadAssetsAsync<GameObject>( new List<string > { "CoreAssets" }, null , Addressables.MergeMode.Union ); await handle.ToUniTask(); }
4. 常见陷阱 4.1 确保释放 1 2 3 4 5 Destroy(go); Addressables.ReleaseInstance(go);
4.2 场景加载时的实例处理 场景切换时,Addressables 实例化的对象不会被自动释放:
1 2 3 4 5 6 7 8 private void OnSceneLoaded (Scene scene, LoadSceneMode mode ){ var instances = FindObjectsByType<AddressableInstance>(FindObjectsSortMode.None); foreach (var inst in instances) { Addressables.ReleaseInstance(inst.gameObject); } }
总结
对象池 是性能基石,通过通用接口和多类型管理器适应复杂需求
Addressables 是资源管理的现代方案,替代 Resources 和手动 AssetBundle
Addressables + 对象池 的组合实现完整的异步加载到复用到自动释放生命周期
合理分组 Addressables Groups,按需加载和卸载
下一篇将探索 URP 渲染管线深入 ,了解可编写渲染管线的核心机制。