简介 基于 addressable,结合对象池进行对象资源的管理。并且提供定时清理资源的接口。
原理 资源管理 打包的部分就是官方的 addressable 使用方式,本项目主要是对资源加载和持续化进行管理。
首先我们定义一个资源数据类 ResInfo
用于保存加载器的资源名、异步加载句柄和引用数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System;using UnityEngine.ResourceManagement.AsyncOperations;namespace ResourceUtils { public class ResInfo { public string name = String.Empty; public int reference = 0 ; public AsyncOperationHandle handle; } }
然后我们定义一个加载器类 ABLoader
用于实现资源加载相关的功能,包括同步加载、异步加载、资源释放、资源引用数记录等。
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 using System;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;namespace ResourceUtils { public class ABLoader { private ResInfo _res; private bool _isLoaded = false ; public ABLoader (string name ) { _res = new ResInfo(); _res.name = name; } public void AddReference (bool add = true ) { if (add ) { _res.reference++; } else { _res.reference--; } } public int GetReference () { return _res.reference; } public void LoadAsync <T >(Action<T> callback = null ) where T:class { if (_isLoaded) { if (_res.handle.IsDone) { callback?.Invoke(_res.handle.Result as T); } else { Loading(callback); } } else { Loading(callback); } } public T Load <T >() where T:class { _isLoaded = true ; _res.handle = Addressables.LoadAssetAsync<T>(_res.name); T obj = _res.handle.WaitForCompletion() as T; _isLoaded = false ; Debug.Log("同步加载完成" ); return obj; } public void Release () { if (_isLoaded) { _isLoaded = false ; Addressables.Release(_res.handle); } } private void Loading <T >(Action<T> callback ) where T:class { _isLoaded = true ; _res.handle = Addressables.LoadAssetAsync<T>(_res.name); _res.handle.Completed += result => { if (result.Status == AsyncOperationStatus.Succeeded) { T obj = result.Result as T; callback?.Invoke(obj); } else { callback?.Invoke(null ); Debug.LogError(_res.name + " 加载失败" ); } }; } } }
我们对资源的加载和释放,都是根据加载器类进行判断和实现。
为了管理每种资源的加载器,我们定义一个加载类 ABManager
进行管理。我们定义一个字典保存每一个资源名对应的 ABLoader
,第一次加载的时候初始化,之后就用第一次加载的 ABLoader
进行相关的操作。
释放的时候也要调用对应的 Release
方法并且从字典移除。
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 using System;using System.Collections.Generic;namespace ResourceUtils { public class ABManager : Singleton <ABManager > { private Dictionary<string , ABLoader> _loaders = new Dictionary<string , ABLoader>(); private Dictionary<string , int > _resourceCount = new Dictionary<string , int >(); public void LoadAsync <T >(string name, Action<T> callback ) where T : class { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader == null ) { loader = new ABLoader(name); _loaders.Add(name, loader); } loader.LoadAsync(callback); } public T Load <T >(string name ) where T : class { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader == null ) { loader = new ABLoader(name); _loaders.Add(name, loader); } T obj = loader.Load<T>(); return obj; } public void AddReference (string name, bool add = true ) { if (_loaders.ContainsKey(name)) { _loaders[name].AddReference(add ); } else { if (!_resourceCount.TryAdd(name, 1 )) { if (add ) { _resourceCount[name]++; } else { _resourceCount[name]--; } } } } public int GetReference (string name ) { if (_loaders.ContainsKey(name)) { return _loaders[name].GetReference(); } else { return _resourceCount[name]; } } public void Release (string name ) { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader != null ) { loader.Release(); } _loaders.Remove(name); } public void Clear () { foreach (var loader in _loaders) { loader.Value.Release(); } _loaders = null ; } } }
上述资源加载和释放相关的功能都是调用 addressable 相关的 API 实现。
对象池 对象池的原理很简单,就是在不需要对象的时候回收到一个池里,一般用栈来存储,然后定义一个字典来存储不同名字对象的栈。在获取对象的时候,根据对象名来获取对应的存储池,然后从池里拿到我们需要的对象,如果没有对象则返回 null。在场景上我们创建一个 inactive 的对象用于挂载对象池中的对象。
本项目的对象池还有设置了定时清理的方法。
定时清理分为两个阶段:
清理到目标数量为止。
全部清理。
为了实现对象池清理的两个阶段,我们定义枚举 ReleaseFlag
,一共有两种状态:
New。
Old。
当我们回收或者获取对象的时候,更新目标对象池状态为 New;当清理的时候,如果对象池状态是 New 的话就清理到目标数量为止,然后把该对象池状态置为 Old;当清理的时候对象池的状态为 Old 的话,就把该对象池清空,然后移除。
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 using System.Collections.Generic;using System.Linq;using GameObjectUtils;using UnityEngine;namespace PoolUtils { public class ObjPoolManager : Singleton <ObjPoolManager > { enum ReleaseFlag { New, Old } private Transform _root; private Dictionary<string , Stack<GameObject>> _pool = new Dictionary<string , Stack<GameObject>>(); private Dictionary<string , ReleaseFlag> _releaseFlags = new Dictionary<string , ReleaseFlag>(); private int _waterline = 10 ; public void Init () { _root = new GameObject().transform; _root.name = "ObjPoolRoot" ; _root.gameObject.SetActive(false ); } public GameObject Get (string name ) { Stack<GameObject> stack; GameObject obj = null ; _pool.TryGetValue(name, out stack); stack?.TryPop(out obj); _releaseFlags[name] = ReleaseFlag.New; return obj; } public void Recycle (string name, GameObject obj ) { Stack<GameObject> stack; _pool.TryGetValue(name, out stack); if (stack == null ) { stack = new Stack<GameObject>(); _pool[name] = stack; } stack.Push(obj); obj.transform.SetParent(_root); _releaseFlags[name] = ReleaseFlag.New; } public void CheckRelease () { var keys = _pool.Keys.ToList(); foreach (var key in keys) { Stack<GameObject> stack = _pool[key]; ReleaseFlag flag = _releaseFlags[key]; switch (flag) { case ReleaseFlag.New: while (stack.Count > _waterline) { GameObject obj = stack.Pop(); ObjManager.Ins().Release(key, obj); } _releaseFlags[key] = ReleaseFlag.Old; break ; case ReleaseFlag.Old: while (stack.Count > 0 ) { GameObject obj = stack.Pop(); ObjManager.Ins().Release(key, obj); } _pool.Remove(key); _releaseFlags.Remove(key); break ; } } } public void Clear () { foreach (var data in _pool) { foreach (var obj in data.Value) { ObjManager.Ins().Release(data.Key,obj); } } _pool.Clear(); } } }
对象管理 有了资源管理和对象池之后,我们把这两个功能都封装到一个类 ObjManager
中。
在这个类中我们定义一个字典用于保存对象的预制体,这样我们之后需要创建新的对象的时候就可以克隆该预制体而不用重新从 AB 包中加载。
同步获取对象的流程如下:
graph TD;
A["对象池"]
B["对象"]
C["预制体字典"]
D["克隆预制体"]
E["加载对象预制体并保存"]
F["增加引用计数"]
A--"有对象"-->B
A--"没有对象"-->C
C--"有预制体"-->D
C--"没有预制体"-->E
E-->D
D-->F
异步获取对象主要用于提前加载预制体,因此流程比较简单:
graph TD;
A["预制体字典"]
B["加载对象预制体"]
C["保存到预制体字典并触发回调"]
A--"不存在预制体"-->B
B-->C
对象的引用计数一般在获取对象和销毁对象的时候变化,回收对象不做改变。
对象管理器也有定时清理资源的方法,清理资源的时候遍历预制体字典获取对象名称,然后查询其引用计数是否为 0,为 0 的就释放并移出字典。
考虑到可能对象管理类的定时清理资源和对象池的定时清理资源方法存在调用时机不同的问题,这两个方法没有合为同一个方法,需要手动分辨调用。
不过清空对象的方法是集中为一个。
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 using System;using System.Collections.Generic;using System.Linq;using UnityEngine;using ResourceUtils;using PoolUtils;using Object = UnityEngine.Object;namespace GameObjectUtils { public class ObjManager : Singleton <ObjManager > { private Dictionary<string , GameObject> _prefabs = new Dictionary<string , GameObject>(); public GameObject Get (string name ) { GameObject obj = ObjPoolManager.Ins().Get(name); if (obj == null ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { prefab = ABManager.Ins().Load<GameObject>(name); _prefabs.Add(name, prefab); } obj = Object.Instantiate(prefab); ABManager.Ins().AddReference(name); } return obj; } public GameObject GetRes (string name ) { GameObject obj = ObjPoolManager.Ins().Get(name); if (obj == null ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { prefab = Resources.Load<GameObject>(name); _prefabs.Add(name, prefab); } obj = Object.Instantiate(prefab); ABManager.Ins().AddReference(name); } return obj; } public void GetAsync (string name, Action<GameObject> callback ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { ABManager.Ins().LoadAsync<GameObject>(name, res => { callback?.Invoke(res); _prefabs.Add(name, prefab); }); } } public void Recycle (string name, GameObject obj ) { ObjPoolManager.Ins().Recycle(name, obj); } public void Release (string name, GameObject obj ) { Object.Destroy(obj); ABManager.Ins().AddReference(name,false ); } public void CheckRelease () { var keys = _prefabs.Keys.ToList(); foreach (var key in keys) { int reference = ABManager.Ins().GetReference(key); if (reference == 0 ) { ABManager.Ins().Release(key); _prefabs.Remove(key); } } } public void Clear () { ObjPoolManager.Ins().Clear();; ABManager.Ins().Clear(); _prefabs.Clear(); } } }
代码 ResInfo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System;using UnityEngine.ResourceManagement.AsyncOperations;namespace ResourceUtils { public class ResInfo { public string name = String.Empty; public int reference = 0 ; public AsyncOperationHandle handle; } }
ABLoader 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 using System;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;namespace ResourceUtils { public class ABLoader { private ResInfo _res; private bool _isLoaded = false ; public ABLoader (string name ) { _res = new ResInfo(); _res.name = name; } public void AddReference (bool add = true ) { if (add ) { _res.reference++; } else { _res.reference--; } } public int GetReference () { return _res.reference; } public void LoadAsync <T >(Action<T> callback = null ) where T:class { if (_isLoaded) { if (_res.handle.IsDone) { callback?.Invoke(_res.handle.Result as T); } else { Loading(callback); } } else { Loading(callback); } } public T Load <T >() where T:class { _isLoaded = true ; _res.handle = Addressables.LoadAssetAsync<T>(_res.name); T obj = _res.handle.WaitForCompletion() as T; _isLoaded = false ; Debug.Log("同步加载完成" ); return obj; } public void Release () { if (_isLoaded) { _isLoaded = false ; Addressables.Release(_res.handle); } } private void Loading <T >(Action<T> callback ) where T:class { _isLoaded = true ; _res.handle = Addressables.LoadAssetAsync<T>(_res.name); _res.handle.Completed += result => { if (result.Status == AsyncOperationStatus.Succeeded) { T obj = result.Result as T; callback?.Invoke(obj); } else { callback?.Invoke(null ); Debug.LogError(_res.name + " 加载失败" ); } }; } } }
ABManager 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 using System;using System.Collections.Generic;namespace ResourceUtils { public class ABManager : Singleton <ABManager > { private Dictionary<string , ABLoader> _loaders = new Dictionary<string , ABLoader>(); private Dictionary<string , int > _resourceCount = new Dictionary<string , int >(); public void LoadAsync <T >(string name, Action<T> callback ) where T : class { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader == null ) { loader = new ABLoader(name); _loaders.Add(name, loader); } loader.LoadAsync(callback); } public T Load <T >(string name ) where T : class { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader == null ) { loader = new ABLoader(name); _loaders.Add(name, loader); } T obj = loader.Load<T>(); return obj; } public void AddReference (string name, bool add = true ) { if (_loaders.ContainsKey(name)) { _loaders[name].AddReference(add ); } else { if (!_resourceCount.TryAdd(name, 1 )) { if (add ) { _resourceCount[name]++; } else { _resourceCount[name]--; } } } } public int GetReference (string name ) { if (_loaders.ContainsKey(name)) { return _loaders[name].GetReference(); } else { return _resourceCount[name]; } } public void Release (string name ) { ABLoader loader; _loaders.TryGetValue(name, out loader); if (loader != null ) { loader.Release(); } _loaders.Remove(name); } public void Clear () { foreach (var loader in _loaders) { loader.Value.Release(); } _loaders = null ; } } }
ObjPoolManager 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 using System.Collections.Generic;using System.Linq;using GameObjectUtils;using UnityEngine;namespace PoolUtils { public class ObjPoolManager : Singleton <ObjPoolManager > { enum ReleaseFlag { New, Old } private Transform _root; private Dictionary<string , Stack<GameObject>> _pool = new Dictionary<string , Stack<GameObject>>(); private Dictionary<string , ReleaseFlag> _releaseFlags = new Dictionary<string , ReleaseFlag>(); private int _waterline = 10 ; public void Init () { _root = new GameObject().transform; _root.name = "ObjPoolRoot" ; _root.gameObject.SetActive(false ); } public GameObject Get (string name ) { Stack<GameObject> stack; GameObject obj = null ; _pool.TryGetValue(name, out stack); stack?.TryPop(out obj); _releaseFlags[name] = ReleaseFlag.New; return obj; } public void Recycle (string name, GameObject obj ) { Stack<GameObject> stack; _pool.TryGetValue(name, out stack); if (stack == null ) { stack = new Stack<GameObject>(); _pool[name] = stack; } stack.Push(obj); obj.transform.SetParent(_root); _releaseFlags[name] = ReleaseFlag.New; } public void CheckRelease () { var keys = _pool.Keys.ToList(); foreach (var key in keys) { Stack<GameObject> stack = _pool[key]; ReleaseFlag flag = _releaseFlags[key]; switch (flag) { case ReleaseFlag.New: while (stack.Count > _waterline) { GameObject obj = stack.Pop(); ObjManager.Ins().Release(key, obj); } _releaseFlags[key] = ReleaseFlag.Old; break ; case ReleaseFlag.Old: while (stack.Count > 0 ) { GameObject obj = stack.Pop(); ObjManager.Ins().Release(key, obj); } _pool.Remove(key); _releaseFlags.Remove(key); break ; } } } public void Clear () { foreach (var data in _pool) { foreach (var obj in data.Value) { ObjManager.Ins().Release(data.Key,obj); } } _pool.Clear(); } } }
ObjManager 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 using System;using System.Collections.Generic;using System.Linq;using UnityEngine;using ResourceUtils;using PoolUtils;using Object = UnityEngine.Object;namespace GameObjectUtils { public class ObjManager : Singleton <ObjManager > { private Dictionary<string , GameObject> _prefabs = new Dictionary<string , GameObject>(); public GameObject Get (string name ) { GameObject obj = ObjPoolManager.Ins().Get(name); if (obj == null ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { prefab = ABManager.Ins().Load<GameObject>(name); _prefabs.Add(name, prefab); } obj = Object.Instantiate(prefab); ABManager.Ins().AddReference(name); } return obj; } public GameObject GetRes (string name ) { GameObject obj = ObjPoolManager.Ins().Get(name); if (obj == null ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { prefab = Resources.Load<GameObject>(name); _prefabs.Add(name, prefab); } obj = Object.Instantiate(prefab); ABManager.Ins().AddReference(name); } return obj; } public void GetAsync (string name, Action<GameObject> callback ) { GameObject prefab; _prefabs.TryGetValue(name, out prefab); if (prefab == null ) { ABManager.Ins().LoadAsync<GameObject>(name, res => { callback?.Invoke(res); _prefabs.Add(name, prefab); }); } } public void Recycle (string name, GameObject obj ) { ObjPoolManager.Ins().Recycle(name, obj); } public void Release (string name, GameObject obj ) { Object.Destroy(obj); ABManager.Ins().AddReference(name,false ); } public void CheckRelease () { var keys = _prefabs.Keys.ToList(); foreach (var key in keys) { int reference = ABManager.Ins().GetReference(key); if (reference == 0 ) { ABManager.Ins().Release(key); _prefabs.Remove(key); } } } public void Clear () { ObjPoolManager.Ins().Clear();; ABManager.Ins().Clear(); _prefabs.Clear(); } } }
测试用例
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 using System;using ResourceUtils;using GameObjectUtils;using PoolUtils;using Timer;using UnityEditor;using UnityEngine;namespace Script { public class ABTest : MonoBehaviour { private void Awake () { TimerUtils.Init(); ObjPoolManager.Ins().Init(); #if UNITY_EDITOR EditorApplication.playModeStateChanged += (PlayModeStateChange state) => { if (state == PlayModeStateChange.ExitingPlayMode) { TimerUtils.Stop(); } }; #endif } private void Start () { var obj = ObjManager.Ins().Get("pre_face" ); TimerUtils.Once(2000 , () => { ObjManager.Ins().Recycle("pre_face" , obj); }); TimerUtils.Loop(5000 , () => { ObjPoolManager.Ins().CheckRelease(); ObjManager.Ins().CheckRelease(); }, 1000 ); } } }
项目 若干功能的测试项目,目前更新在 xLua 分支。
更新日志