├── .gitattributes ├── .idea └── .idea.BundleMaster.dir │ └── .idea │ ├── .gitignore │ ├── encodings.xml │ ├── indexLayout.xml │ └── vcs.xml ├── BMWebGLAdapter ├── AssetComponentInit_WebGL.cs ├── AssetComponentTools_WebGL.cs ├── AssetComponentUpdate_WebGL.cs └── LoadBaseRuntimeLoad_WebGL.cs ├── BundleMasterRuntime ├── AssetComponent.cs ├── AssetComponentCheck.cs ├── AssetComponentConfig.cs ├── AssetComponentInit.cs ├── AssetComponentLife.cs ├── AssetComponentTools.cs ├── AssetComponentUnLoad.cs ├── AssetComponentUpdate.cs ├── Base │ ├── LoadBase.cs │ ├── LoadBaseRuntimeLoad.cs │ ├── LoadBaseUnLoad.cs │ ├── LoadDepend.cs │ ├── LoadFile.cs │ ├── LoadGroup.cs │ ├── LoadHandlerFactory.cs │ └── WebLoadProgress.cs ├── BundleMasterRuntimeConfig.cs ├── BundleRuntimeInfo.cs ├── DownLoadTask.cs ├── Helper │ ├── AssetLogHelper.cs │ ├── DeleteHelper.cs │ ├── DownloadBundleHelper.cs │ ├── GroupAssetHelper.cs │ ├── HandlerIdHelper.cs │ ├── PathUnifiedHelper.cs │ ├── TimeAwaitHelper.cs │ ├── ValueHelper.cs │ └── VerifyHelper.cs ├── LoadHandler.cs ├── LoadHandlerBase.cs ├── LoadSceneHandler.cs └── UpdateBundleDataInfo.cs ├── CoroutineLock ├── CoroutineLock.cs ├── CoroutineLockComponent.cs ├── CoroutineLockQueue.cs ├── CoroutineLockType.cs └── LoadPathConvertHelper.cs ├── Editor └── BundleMasterEditor │ ├── AssetLoadTable.cs │ ├── AssetsLoadSetting.cs │ ├── AssetsOriginSetting.cs │ ├── AssetsSetting.cs │ ├── BuildAssets.cs │ ├── BuildAssetsTools.cs │ ├── BundleMasterInterface │ ├── BundleMasterAssetsLoadWindow.cs │ ├── BundleMasterMainWindow.cs │ ├── BundleMasterOriginLoadWindow.cs │ └── BundleMasterWindow.cs │ ├── BundleUsefulTool.cs │ └── DevelopSceneChange.cs ├── LICENSE ├── LiteMultiThreadDownLoad ├── BundleMaster.LMTD.asmdef ├── BundleMaster.LMTD.asmdef.meta ├── ILiteThreadAction.cs ├── LMTDProxy.cs ├── LMTDownLoad.cs ├── LiteThread.cs ├── LmtdTable.cs └── ThreadFactory.cs ├── NintendoAdapter ├── AssetComponentInit_Nintendo.cs ├── AssetComponentTools_Nintendo.cs ├── AssetComponentUpdate_Nintendo.cs └── LoadBaseRuntimeLoad_Nintendo.cs └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.idea/.idea.BundleMaster.dir/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # Rider 忽略的文件 5 | /modules.xml 6 | /projectSettingsUpdater.xml 7 | /contentModel.xml 8 | /.idea.BundleMaster.iml 9 | # 基于编辑器的 HTTP 客户端请求 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.BundleMaster.dir/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.BundleMaster.dir/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.BundleMaster.dir/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BMWebGLAdapter/AssetComponentInit_WebGL.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | using UnityEngine; 5 | using ET; 6 | using UnityEngine.Networking; 7 | 8 | namespace BM 9 | { 10 | public static partial class AssetComponent 11 | { 12 | #if BMWebGL 13 | /// 14 | /// 初始化 15 | /// 16 | /// WebGL模式下 secretKey 参数失效,仅作占位使用 17 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null) 18 | { 19 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 20 | { 21 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件"); 22 | return false; 23 | } 24 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName)) 25 | { 26 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化"); 27 | return false; 28 | } 29 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName); 30 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo); 31 | 32 | string filePath = BundleFileExistPath_WebGL(bundlePackageName, "FileLogs.txt"); 33 | string fileLogs = await LoadWebGLFileText(filePath); 34 | { 35 | Regex reg = new Regex(@"\<(.+?)>"); 36 | MatchCollection matchCollection = reg.Matches(fileLogs); 37 | List dependFileName = new List(); 38 | foreach (Match m in matchCollection) 39 | { 40 | string[] fileLog = m.Groups[1].Value.Split('|'); 41 | LoadFile loadFile = new LoadFile(); 42 | loadFile.FilePath = fileLog[0]; 43 | loadFile.AssetBundleName = fileLog[1]; 44 | 45 | if (fileLog.Length > 2) 46 | { 47 | for (int i = 2; i < fileLog.Length; i++) 48 | { 49 | dependFileName.Add(fileLog[i]); 50 | } 51 | } 52 | loadFile.DependFileName = dependFileName.ToArray(); 53 | dependFileName.Clear(); 54 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile); 55 | } 56 | } 57 | 58 | string dependPath = BundleFileExistPath_WebGL(bundlePackageName, "DependLogs.txt"); 59 | string dependLogs = await LoadWebGLFileText(dependPath); 60 | { 61 | Regex reg = new Regex(@"\<(.+?)>"); 62 | MatchCollection matchCollection = reg.Matches(dependLogs); 63 | foreach (Match m in matchCollection) 64 | { 65 | string[] dependLog = m.Groups[1].Value.Split('|'); 66 | LoadDepend loadDepend = new LoadDepend(); 67 | loadDepend.FilePath = dependLog[0]; 68 | loadDepend.AssetBundleName = dependLog[1]; 69 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend); 70 | } 71 | } 72 | 73 | ETTask groupTcs = ETTask.Create(); 74 | string groupPath = BundleFileExistPath_WebGL(bundlePackageName, "GroupLogs.txt"); 75 | string groupLogs = await LoadWebGLFileText(groupPath); 76 | { 77 | Regex reg = new Regex(@"\<(.+?)>"); 78 | MatchCollection matchCollection = reg.Matches(groupLogs); 79 | foreach (Match m in matchCollection) 80 | { 81 | string[] groupLog = m.Groups[1].Value.Split('|'); 82 | LoadGroup loadGroup = new LoadGroup(); 83 | loadGroup.FilePath = groupLog[0]; 84 | loadGroup.AssetBundleName = groupLog[1]; 85 | if (groupLog.Length > 2) 86 | { 87 | for (int i = 2; i < groupLog.Length; i++) 88 | { 89 | loadGroup.DependFileName.Add(groupLog[i]); 90 | } 91 | } 92 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup); 93 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath); 94 | } 95 | } 96 | 97 | //加载当前分包的shader 98 | await LoadShader_WebGL(bundlePackageName); 99 | return true; 100 | } 101 | 102 | private static async ETTask LoadShader_WebGL(string bundlePackageName) 103 | { 104 | ETTask tcs = ETTask.Create(); 105 | string shaderPath = BundleFileExistPath_WebGL(bundlePackageName, "shader_" + bundlePackageName.ToLower()); 106 | //判断文件是否存在 107 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(shaderPath)) 108 | { 109 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 110 | weq.completed += (o) => { tcs.SetResult(); }; 111 | await tcs; 112 | #if UNITY_2020_1_OR_NEWER 113 | if (webRequest.result == UnityWebRequest.Result.Success) 114 | #else 115 | if (string.IsNullOrEmpty(webRequest.error)) 116 | #endif 117 | { 118 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); 119 | BundleNameToRuntimeInfo[bundlePackageName].Shader = assetBundle; 120 | } 121 | else 122 | { 123 | return; 124 | } 125 | } 126 | } 127 | #endif 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /BMWebGLAdapter/AssetComponentTools_WebGL.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using ET; 4 | using UnityEngine.Networking; 5 | 6 | namespace BM 7 | { 8 | #if BMWebGL 9 | public static partial class AssetComponent 10 | { 11 | /// 12 | /// WebGL获取Bundle信息文件的路径 13 | /// 14 | internal static string BundleFileExistPath_WebGL(string bundlePackageName, string fileName) 15 | { 16 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName); 17 | return path; 18 | } 19 | 20 | internal static async ETTask LoadWebGLFileText(string filePath) 21 | { 22 | ETTask tcs = ETTask.Create(); 23 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) 24 | { 25 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 26 | weq.completed += (o) => 27 | { 28 | tcs.SetResult(); 29 | }; 30 | await tcs; 31 | #if UNITY_2020_1_OR_NEWER 32 | if (webRequest.result == UnityWebRequest.Result.Success) 33 | #else 34 | if (string.IsNullOrEmpty(webRequest.error)) 35 | #endif 36 | { 37 | string str = webRequest.downloadHandler.text; 38 | return str; 39 | } 40 | else 41 | { 42 | AssetLogHelper.LogError("WebGL初始化分包未找到要读取的文件: \t" + filePath); 43 | return ""; 44 | } 45 | 46 | } 47 | } 48 | 49 | 50 | } 51 | #endif 52 | } 53 | -------------------------------------------------------------------------------- /BMWebGLAdapter/AssetComponentUpdate_WebGL.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | public static partial class AssetComponent 7 | { 8 | #if BMWebGL 9 | /// 10 | /// 检查分包是否需要更新 11 | /// 12 | /// 所有分包的名称以及是否验证文件CRC 13 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 14 | public static async ETTask CheckAllBundlePackageUpdate(Dictionary bundlePackageNames) 15 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 16 | { 17 | UpdateBundleDataInfo updateBundleDataInfo = new UpdateBundleDataInfo(); 18 | updateBundleDataInfo.NeedUpdate = false; 19 | 20 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build) 21 | { 22 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local) 23 | { 24 | AssetComponentConfig.HotfixPath = AssetComponentConfig.LocalBundlePath; 25 | } 26 | else 27 | { 28 | #if !UNITY_EDITOR 29 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Develop 只能在编辑器下运行"); 30 | #endif 31 | } 32 | } 33 | else 34 | { 35 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Build WebGL无需更新,请用Local模式"); 36 | } 37 | return updateBundleDataInfo; 38 | } 39 | 40 | /// 41 | /// 下载更新 42 | /// 43 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 44 | public static async ETTask DownLoadUpdate(UpdateBundleDataInfo updateBundleDataInfo) 45 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 46 | { 47 | AssetLogHelper.LogError("WebGL无需更新"); 48 | } 49 | 50 | #endif 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BMWebGLAdapter/LoadBaseRuntimeLoad_WebGL.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using ET; 4 | using UnityEngine.Networking; 5 | 6 | namespace BM 7 | { 8 | public partial class LoadBase 9 | { 10 | #if BMWebGL 11 | internal void LoadAssetBundle(string bundlePackageName) 12 | { 13 | AddRefCount(); 14 | if (_loadState == LoadState.Finish) 15 | { 16 | return; 17 | } 18 | //资源没有加载过也没有正在加载就同步加载出来 19 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName); 20 | 21 | if (_loadState == LoadState.Loading) 22 | { 23 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, WebGL出现此错误请检查资源加载是否冲突, 否则会引起异步加载失败。资源名: " + FilePath + 24 | "\nAssetBundle包名: " + AssetBundleName); 25 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath)) 26 | { 27 | webRequest.SendWebRequest(); 28 | while (!webRequest.isDone) { } 29 | #if UNITY_2020_1_OR_NEWER 30 | if (webRequest.result == UnityWebRequest.Result.Success) 31 | #else 32 | if (string.IsNullOrEmpty(webRequest.error)) 33 | #endif 34 | { 35 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); 36 | if (AssetBundle == null) 37 | { 38 | AssetBundle = assetBundle; 39 | } 40 | else 41 | { 42 | assetBundle.Unload(true); 43 | } 44 | } 45 | else 46 | { 47 | AssetLogHelper.LogError("UnityWebRequest同步加载正在异步加载的资源 AssetBundle失败: \t" + assetBundlePath); 48 | } 49 | } 50 | return; 51 | } 52 | 53 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath)) 54 | { 55 | webRequest.SendWebRequest(); 56 | while (!webRequest.isDone) { } 57 | #if UNITY_2020_1_OR_NEWER 58 | if (webRequest.result == UnityWebRequest.Result.Success) 59 | #else 60 | if (string.IsNullOrEmpty(webRequest.error)) 61 | #endif 62 | { 63 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); 64 | if (AssetBundle == null) 65 | { 66 | AssetBundle = assetBundle; 67 | } 68 | else 69 | { 70 | assetBundle.Unload(true); 71 | } 72 | } 73 | else 74 | { 75 | AssetLogHelper.LogError("UnityWebRequest加载AssetBundle失败: \t" + assetBundlePath); 76 | } 77 | } 78 | _loadState = LoadState.Finish; 79 | if (_loadProgress != null) 80 | { 81 | _loadProgress.WeqOperation = null; 82 | } 83 | } 84 | 85 | /// 86 | /// 异步加载LoadBase的AssetBundle 87 | /// 88 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName) 89 | { 90 | AddRefCount(); 91 | if (_loadState == LoadState.Finish) 92 | { 93 | return; 94 | } 95 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName]; 96 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName); 97 | //获取一个协程锁 98 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath)); 99 | if (_loadState == LoadState.NoLoad) 100 | { 101 | _loadState = LoadState.Loading; 102 | await LoadBundleFinish(assetBundlePath); 103 | _loadState = LoadState.Finish; 104 | if (_loadProgress != null) 105 | { 106 | _loadProgress.WeqOperation = null; 107 | } 108 | } 109 | //协程锁解锁 110 | coroutineLock.Dispose(); 111 | } 112 | 113 | /// 114 | /// 通过路径直接加载硬盘上的AssetBundle 115 | /// 116 | private async ETTask LoadBundleFinish(string assetBundlePath) 117 | { 118 | if (_loadState == LoadState.Finish) 119 | { 120 | return; 121 | } 122 | ETTask tcs = ETTask.Create(true); 123 | 124 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath)) 125 | { 126 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 127 | if (_loadProgress != null) 128 | { 129 | _loadProgress.WeqOperation = weq; 130 | } 131 | weq.completed += (o) => 132 | { 133 | tcs.SetResult(); 134 | }; 135 | await tcs; 136 | #if UNITY_2020_1_OR_NEWER 137 | if (webRequest.result == UnityWebRequest.Result.Success) 138 | #else 139 | if (string.IsNullOrEmpty(webRequest.error)) 140 | #endif 141 | { 142 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); 143 | //判断是否还需要 144 | if (_refCount > 0) 145 | { 146 | 147 | if (AssetBundle == null) 148 | { 149 | AssetBundle = assetBundle; 150 | } 151 | else 152 | { 153 | assetBundle.Unload(true); 154 | } 155 | } 156 | else 157 | { 158 | assetBundle.Unload(true); 159 | AssetComponent.AddPreUnLoadPool(this); 160 | } 161 | } 162 | else 163 | { 164 | AssetLogHelper.LogError("UnityWebRequest加载AssetBundle失败: \t" + assetBundlePath); 165 | } 166 | } 167 | 168 | } 169 | 170 | /// 171 | /// 强制加载完成 172 | /// 173 | internal void ForceLoadFinish(string bundlePackageName) 174 | { 175 | if (_loadState == LoadState.Finish) 176 | { 177 | return; 178 | } 179 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName); 180 | if (AssetBundle == null) 181 | { 182 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath)) 183 | { 184 | webRequest.SendWebRequest(); 185 | while (!webRequest.isDone){} 186 | #if UNITY_2020_1_OR_NEWER 187 | if (webRequest.result == UnityWebRequest.Result.Success) 188 | #else 189 | if (string.IsNullOrEmpty(webRequest.error)) 190 | #endif 191 | { 192 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest); 193 | if (AssetBundle == null) 194 | { 195 | AssetBundle = assetBundle; 196 | _loadState = LoadState.Finish; 197 | if (_loadProgress != null) 198 | { 199 | _loadProgress.WeqOperation = null; 200 | } 201 | if (_refCount <= 0) 202 | { 203 | AssetComponent.AddPreUnLoadPool(this); 204 | } 205 | } 206 | else 207 | { 208 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, WebGL出现此错误请检查资源加载是否冲突, 否则会引起异步加载失败。资源名: " + FilePath + 209 | "\nAssetBundle包名: " + AssetBundleName); 210 | assetBundle.Unload(true); 211 | } 212 | } 213 | else 214 | { 215 | AssetLogHelper.LogError("UnityWebRequest同步加载正在异步加载的资源 AssetBundle失败: \t" + assetBundlePath); 216 | } 217 | } 218 | return; 219 | } 220 | 221 | } 222 | 223 | /// 224 | /// 打开进度统计 225 | /// 226 | internal void OpenProgress() 227 | { 228 | _loadProgress = new WebLoadProgress(); 229 | } 230 | 231 | /// 232 | /// 获取当前资源加载进度 233 | /// 234 | internal float GetProgress() 235 | { 236 | if (_loadProgress == null) 237 | { 238 | AssetLogHelper.LogError("未打开进度统计无法获取进度"); 239 | return 0; 240 | } 241 | if (_loadState == LoadState.Finish) 242 | { 243 | return 1; 244 | } 245 | if (_loadState == LoadState.NoLoad) 246 | { 247 | return 0; 248 | } 249 | return _loadProgress.GetWebProgress(); 250 | } 251 | 252 | #endif 253 | 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /BundleMasterRuntime/AssetComponentCheck.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | public static partial class AssetComponent 8 | { 9 | /// 10 | /// 判定一个资源是否存在,如果这个资源是组里的资源,那么只能检测到这个资源所在的组是否存在 11 | /// 12 | public static bool CheckAssetExist(string assetPath, string bundlePackageName = null) 13 | { 14 | if (bundlePackageName == null) 15 | { 16 | bundlePackageName = AssetComponentConfig.DefaultBundlePackageName; 17 | } 18 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 19 | { 20 | #if UNITY_EDITOR 21 | assetPath = Path.Combine(Application.dataPath + "/../", assetPath); 22 | return File.Exists(assetPath); 23 | #else 24 | AssetLogHelper.LogError("检查资源: " + assetPath + " 失败(资源检查Develop模式只能在编辑器下运行)"); 25 | return false; 26 | #endif 27 | } 28 | if (!BundleNameToRuntimeInfo.TryGetValue(bundlePackageName, out BundleRuntimeInfo bundleRuntimeInfo)) 29 | { 30 | AssetLogHelper.LogError(bundlePackageName + "检查资源时分包没有初始化"); 31 | return false; 32 | } 33 | string groupPath = GroupAssetHelper.IsGroupAsset(assetPath, bundleRuntimeInfo.LoadGroupDicKey); 34 | if (groupPath != null) 35 | { 36 | return true; 37 | } 38 | if (bundleRuntimeInfo.LoadFileDic.ContainsKey(assetPath)) 39 | { 40 | return true; 41 | } 42 | return false; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/AssetComponentConfig.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BM 4 | { 5 | /// 6 | /// 资源加载初始化所需配置信息 7 | /// 8 | public class AssetComponentConfig 9 | { 10 | /// 11 | /// 运行时配置 12 | /// 13 | private static BundleMasterRuntimeConfig _bmRuntimeConfig = null; 14 | 15 | /// 16 | /// 资源更新目录 Application.dataPath + "/../HotfixBundles/" 17 | /// 18 | public static string HotfixPath = ""; 19 | 20 | /// 21 | /// 存放本地Bundle的位置 Application.streamingAssetsPath; 22 | /// 23 | public static string LocalBundlePath = Application.streamingAssetsPath; 24 | 25 | /// 26 | /// 资源服务器的地址 http://192.168.50.157/BundleData/ 27 | /// 28 | public static string BundleServerUrl = "http://192.168.50.157/BundleData/"; 29 | 30 | /// 31 | /// 默认加载的Bundle名 32 | /// 33 | public static string DefaultBundlePackageName = ""; 34 | 35 | private static void InitRuntimeConfig() 36 | { 37 | if (_bmRuntimeConfig == null) 38 | { 39 | _bmRuntimeConfig = Resources.Load("BMConfig"); 40 | } 41 | } 42 | 43 | /// 44 | /// 加载模式 45 | /// 46 | public static AssetLoadMode AssetLoadMode 47 | { 48 | get 49 | { 50 | InitRuntimeConfig(); 51 | return _bmRuntimeConfig.AssetLoadMode; 52 | } 53 | } 54 | 55 | /// 56 | /// 最大同时下载的资源数量 57 | /// 58 | public static int MaxDownLoadCount 59 | { 60 | get 61 | { 62 | InitRuntimeConfig(); 63 | return _bmRuntimeConfig.MaxDownLoadCount; 64 | } 65 | } 66 | 67 | /// 68 | /// 下载失败最多重试次数 69 | /// 70 | public static int ReDownLoadCount 71 | { 72 | get 73 | { 74 | InitRuntimeConfig(); 75 | return _bmRuntimeConfig.ReDownLoadCount; 76 | } 77 | } 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/AssetComponentInit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | using UnityEngine; 5 | using ET; 6 | using UnityEngine.Networking; 7 | 8 | namespace BM 9 | { 10 | public static partial class AssetComponent 11 | { 12 | /// 13 | /// Bundle初始化的信息 14 | /// 15 | internal static readonly Dictionary BundleNameToRuntimeInfo = new Dictionary(); 16 | 17 | #if !(Nintendo_Switch || BMWebGL) 18 | /// 19 | /// 初始化 20 | /// 21 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null) 22 | { 23 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 24 | { 25 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件"); 26 | return false; 27 | } 28 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName)) 29 | { 30 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化"); 31 | return false; 32 | } 33 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName, secretKey); 34 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo); 35 | 36 | ETTask fileTcs= ETTask.Create(); 37 | string filePath = BundleFileExistPath(bundlePackageName, "FileLogs.txt", true); 38 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) 39 | { 40 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 41 | weq.completed += (o) => 42 | { 43 | fileTcs.SetResult(); 44 | }; 45 | await fileTcs; 46 | #if UNITY_2020_1_OR_NEWER 47 | if (webRequest.result != UnityWebRequest.Result.Success) 48 | #else 49 | if (!string.IsNullOrEmpty(webRequest.error)) 50 | #endif 51 | { 52 | AssetLogHelper.LogError("初始化分包未找到FileLogs 分包名: " + bundlePackageName + "\t" + filePath); 53 | return false; 54 | } 55 | string fileLogs = webRequest.downloadHandler.text; 56 | Regex reg = new Regex(@"\<(.+?)>"); 57 | MatchCollection matchCollection = reg.Matches(fileLogs); 58 | List dependFileName = new List(); 59 | foreach (Match m in matchCollection) 60 | { 61 | string[] fileLog = m.Groups[1].Value.Split('|'); 62 | LoadFile loadFile = new LoadFile(); 63 | loadFile.FilePath = fileLog[0]; 64 | loadFile.AssetBundleName = fileLog[1]; 65 | 66 | if (fileLog.Length > 2) 67 | { 68 | for (int i = 2; i < fileLog.Length; i++) 69 | { 70 | dependFileName.Add(fileLog[i]); 71 | } 72 | } 73 | loadFile.DependFileName = dependFileName.ToArray(); 74 | dependFileName.Clear(); 75 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile); 76 | } 77 | } 78 | ETTask dependTcs = ETTask.Create(); 79 | string dependPath = BundleFileExistPath(bundlePackageName, "DependLogs.txt", true); 80 | using (UnityWebRequest webRequest = UnityWebRequest.Get(dependPath)) 81 | { 82 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 83 | weq.completed += (o) => 84 | { 85 | dependTcs.SetResult(); 86 | }; 87 | await dependTcs; 88 | #if UNITY_2020_1_OR_NEWER 89 | if (webRequest.result != UnityWebRequest.Result.Success) 90 | #else 91 | if (!string.IsNullOrEmpty(webRequest.error)) 92 | #endif 93 | { 94 | AssetLogHelper.LogError("初始化分包未找到DependLogs 分包名: " + bundlePackageName + "\t" + dependPath); 95 | return false; 96 | } 97 | string dependLogs = webRequest.downloadHandler.text; 98 | Regex reg = new Regex(@"\<(.+?)>"); 99 | MatchCollection matchCollection = reg.Matches(dependLogs); 100 | foreach (Match m in matchCollection) 101 | { 102 | string[] dependLog = m.Groups[1].Value.Split('|'); 103 | LoadDepend loadDepend = new LoadDepend(); 104 | loadDepend.FilePath = dependLog[0]; 105 | loadDepend.AssetBundleName = dependLog[1]; 106 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend); 107 | } 108 | } 109 | ETTask groupTcs = ETTask.Create(); 110 | string groupPath = BundleFileExistPath(bundlePackageName, "GroupLogs.txt", true); 111 | using (UnityWebRequest webRequest = UnityWebRequest.Get(groupPath)) 112 | { 113 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 114 | weq.completed += (o) => 115 | { 116 | groupTcs.SetResult(); 117 | }; 118 | await groupTcs; 119 | #if UNITY_2020_1_OR_NEWER 120 | if (webRequest.result != UnityWebRequest.Result.Success) 121 | #else 122 | if (!string.IsNullOrEmpty(webRequest.error)) 123 | #endif 124 | { 125 | AssetLogHelper.LogError("初始化分包未找到GroupLogs 分包名: " + bundlePackageName+ "\t" + groupPath); 126 | return false; 127 | } 128 | string groupLogs = webRequest.downloadHandler.text; 129 | Regex reg = new Regex(@"\<(.+?)>"); 130 | MatchCollection matchCollection = reg.Matches(groupLogs); 131 | foreach (Match m in matchCollection) 132 | { 133 | string[] groupLog = m.Groups[1].Value.Split('|'); 134 | LoadGroup loadGroup = new LoadGroup(); 135 | loadGroup.FilePath = groupLog[0]; 136 | loadGroup.AssetBundleName = groupLog[1]; 137 | if (groupLog.Length > 2) 138 | { 139 | for (int i = 2; i < groupLog.Length; i++) 140 | { 141 | loadGroup.DependFileName.Add(groupLog[i]); 142 | } 143 | } 144 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup); 145 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath); 146 | } 147 | } 148 | //加载当前分包的shader 149 | await LoadShader(bundlePackageName); 150 | return true; 151 | } 152 | 153 | /// 154 | /// 加载Shader文件 155 | /// 156 | private static async ETTask LoadShader(string bundlePackageName) 157 | { 158 | ETTask tcs = ETTask.Create(); 159 | string shaderPath = BundleFileExistPath(bundlePackageName, "shader_" + bundlePackageName.ToLower(), true); 160 | if (BundleNameToRuntimeInfo[bundlePackageName].Encrypt) 161 | { 162 | byte[] shaderData = await VerifyHelper.GetDecryptDataAsync(shaderPath, null, BundleNameToRuntimeInfo[bundlePackageName].SecretKey); 163 | if (shaderData == null) 164 | { 165 | tcs.SetResult(); 166 | } 167 | else 168 | { 169 | AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(shaderData); 170 | request.completed += operation => 171 | { 172 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle; 173 | tcs.SetResult(); 174 | }; 175 | } 176 | } 177 | else 178 | { 179 | byte[] shaderData = await VerifyHelper.GetDecryptDataAsync(shaderPath, null, BundleNameToRuntimeInfo[bundlePackageName].SecretKey); 180 | if (shaderData == null) 181 | { 182 | tcs.SetResult(); 183 | } 184 | else 185 | { 186 | AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(BundleFileExistPath(bundlePackageName, "shader_" + bundlePackageName.ToLower(), false)); 187 | request.completed += operation => 188 | { 189 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle; 190 | tcs.SetResult(); 191 | }; 192 | } 193 | } 194 | await tcs; 195 | } 196 | #endif 197 | 198 | } 199 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/AssetComponentLife.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | public static partial class AssetComponent 8 | { 9 | /// 10 | /// 计时 11 | /// 12 | private static float _timer = 0; 13 | 14 | /// 15 | /// 下载进度更新器 16 | /// 17 | internal static Action DownLoadAction = null; 18 | 19 | /// 20 | /// 等待计时队列 21 | /// 22 | internal static Queue TimerAwaitQueue = new Queue(); 23 | 24 | /// 25 | /// 卸载周期计时循环 26 | /// 27 | public static void Update() 28 | { 29 | float nowTime = Time.deltaTime; 30 | int awaitQueueCount = TimerAwaitQueue.Count; 31 | for (int i = 0; i < awaitQueueCount; i++) 32 | { 33 | TimerAwait timerAwait = TimerAwaitQueue.Dequeue(); 34 | if (!timerAwait.CalcSubTime(nowTime)) 35 | { 36 | TimerAwaitQueue.Enqueue(timerAwait); 37 | } 38 | } 39 | _timer += nowTime; 40 | if (_timer >= _unLoadCirculateTime) 41 | { 42 | _timer = 0; 43 | AutoAddToTrueUnLoadPool(); 44 | } 45 | 46 | //更新下载完成任务 47 | lock (DownloadBundleHelper.DownLoadFinishQueue) 48 | { 49 | if (DownloadBundleHelper.DownLoadFinishQueue.Count > 0) 50 | { 51 | int downLoadFinishCount = DownloadBundleHelper.DownLoadFinishQueue.Count; 52 | for (int i = 0; i < downLoadFinishCount; i++) 53 | { 54 | DownloadBundleHelper.DownLoadFinishQueue.Dequeue().SetResult(); 55 | } 56 | } 57 | } 58 | DownLoadAction?.Invoke(nowTime); 59 | 60 | 61 | CoroutineLockComponent.UpDate(); 62 | } 63 | 64 | /// 65 | /// 游戏关闭时调用 66 | /// 67 | public static void Destroy() 68 | { 69 | #if !BMWebGL 70 | LMTD.ThreadFactory.Destroy(); 71 | #endif 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/AssetComponentTools.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | public static partial class AssetComponent 8 | { 9 | 10 | /// 11 | /// 获取Bundle信息文件的路径 12 | /// 13 | internal static string BundleFileExistPath(string bundlePackageName, string fileName, bool isWebLoad) 14 | { 15 | string path = GetBasePath(bundlePackageName, fileName); 16 | if (isWebLoad) 17 | { 18 | //通过webReq加载 19 | #if UNITY_ANDROID && !UNITY_EDITOR 20 | if (!path.Contains("file:///")) 21 | { 22 | path = "file://" + path; 23 | } 24 | #elif UNITY_IOS && !UNITY_EDITOR 25 | path = "file://" + path; 26 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX 27 | path = "file://" + path; 28 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN 29 | #else 30 | #endif 31 | return path; 32 | } 33 | else 34 | { 35 | //直接加载 36 | #if UNITY_ANDROID && !UNITY_EDITOR 37 | #elif UNITY_IOS && !UNITY_EDITOR 38 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX 39 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN 40 | #else 41 | #endif 42 | return path; 43 | } 44 | } 45 | 46 | /// 47 | /// 得到基础的路径 48 | /// 49 | private static string GetBasePath(string bundlePackageName, string fileName) 50 | { 51 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local) 52 | { 53 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName); 54 | return path; 55 | } 56 | else 57 | { 58 | string path = Path.Combine(AssetComponentConfig.HotfixPath, bundlePackageName, fileName); 59 | if (!File.Exists(path)) 60 | { 61 | //热更目录不存在,返回streaming目录 62 | path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName); 63 | } 64 | //热更目录存在,返回热更目录 65 | return path; 66 | } 67 | } 68 | 69 | /// 70 | /// 获取分包的更新索引列表 71 | /// 72 | private static async ETTask GetRemoteBundlePackageVersionLog(string bundlePackageName) 73 | { 74 | byte[] data = await DownloadBundleHelper.DownloadDataByUrl(Path.Combine(AssetComponentConfig.BundleServerUrl, bundlePackageName, "VersionLogs.txt")); 75 | if (data == null) 76 | { 77 | AssetLogHelper.LogError(bundlePackageName + "获取更新索引列表失败"); 78 | return null; 79 | } 80 | return System.Text.Encoding.UTF8.GetString(data); 81 | } 82 | 83 | /// 84 | /// 创建更新后的Log文件 85 | /// 86 | /// 文件的全路径 87 | /// 文件的内容 88 | private static void CreateUpdateLogFile(string filePath, string fileData) 89 | { 90 | using (StreamWriter sw = new StreamWriter(filePath)) 91 | { 92 | StringBuilder sb = new StringBuilder(); 93 | sb.Append(fileData); 94 | sw.WriteLine(sb.ToString()); 95 | } 96 | } 97 | 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadBase.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | /// 4 | /// 加载类型的基类 5 | /// 6 | public partial class LoadBase 7 | { 8 | public string FilePath; 9 | public string AssetBundleName; 10 | protected LoadType LoadType = LoadType.Null; 11 | } 12 | 13 | public enum LoadType 14 | { 15 | Null = 0, 16 | File = 1, 17 | Depend = 2, 18 | Group = 3 19 | } 20 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadBaseRuntimeLoad.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | public partial class LoadBase 8 | { 9 | /// 10 | /// 引用计数 11 | /// 12 | private int _refCount = 0; 13 | 14 | /// 15 | /// AssetBundle加载的状态 16 | /// 17 | private LoadState _loadState = LoadState.NoLoad; 18 | 19 | /// 20 | /// 加载请求索引 21 | /// 22 | private AssetBundleCreateRequest _assetBundleCreateRequest; 23 | 24 | /// 25 | /// AssetBundle的引用 26 | /// 27 | public AssetBundle AssetBundle = null; 28 | 29 | #if !Nintendo_Switch 30 | /// 31 | /// 需要统计进度 32 | /// 33 | private WebLoadProgress _loadProgress = null; 34 | #endif 35 | 36 | private void AddRefCount() 37 | { 38 | _refCount++; 39 | if (_refCount == 1 && _loadState == LoadState.Finish) 40 | { 41 | AssetComponent.SubPreUnLoadPool(this); 42 | } 43 | } 44 | 45 | internal void SubRefCount() 46 | { 47 | _refCount--; 48 | if (_loadState == LoadState.NoLoad) 49 | { 50 | AssetLogHelper.LogError("资源未被加载,引用不可能减少\n" + FilePath); 51 | return; 52 | } 53 | if (_loadState == LoadState.Loading) 54 | { 55 | AssetLogHelper.Log("资源加载中,等加载完成后再进入卸载逻辑\n" + FilePath); 56 | return; 57 | } 58 | if (_refCount <= 0) 59 | { 60 | //需要进入预卸载池等待卸载 61 | AssetComponent.AddPreUnLoadPool(this); 62 | } 63 | } 64 | 65 | #if !(BMWebGL || Nintendo_Switch) 66 | 67 | internal void LoadAssetBundle(string bundlePackageName) 68 | { 69 | AddRefCount(); 70 | if (_loadState == LoadState.Finish) 71 | { 72 | return; 73 | } 74 | if (_loadState == LoadState.Loading) 75 | { 76 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath + 77 | "\nAssetBundle包名: " + AssetBundleName); 78 | if (_assetBundleCreateRequest != null) 79 | { 80 | AssetBundle = _assetBundleCreateRequest.assetBundle; 81 | return; 82 | } 83 | } 84 | //资源没有加载过也没有正在加载就同步加载出来 85 | if (AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt) 86 | { 87 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, true); 88 | byte[] data = VerifyHelper.GetDecryptData(assetBundlePath, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].SecretKey); 89 | AssetBundle = AssetBundle.LoadFromMemory(data); 90 | } 91 | else 92 | { 93 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, false); 94 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath); 95 | } 96 | _loadState = LoadState.Finish; 97 | } 98 | 99 | /// 100 | /// 异步加载LoadBase的AssetBundle 101 | /// 102 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName) 103 | { 104 | AddRefCount(); 105 | if (_loadState == LoadState.Finish) 106 | { 107 | return; 108 | } 109 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName]; 110 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, bundleRuntimeInfo.Encrypt); 111 | //获取一个协程锁 112 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath)); 113 | if (_loadState == LoadState.NoLoad) 114 | { 115 | _loadState = LoadState.Loading; 116 | if (bundleRuntimeInfo.Encrypt) 117 | { 118 | await LoadDataFinish(assetBundlePath, bundleRuntimeInfo.SecretKey); 119 | } 120 | else 121 | { 122 | await LoadBundleFinish(assetBundlePath); 123 | } 124 | _loadState = LoadState.Finish; 125 | } 126 | //协程锁解锁 127 | coroutineLock.Dispose(); 128 | } 129 | 130 | /// 131 | /// 通过Byte加载完成(只有启用了异或加密才使用此加载方式) 132 | /// 133 | private async ETTask LoadDataFinish(string assetBundlePath, char[] bundlePackageSecretKey) 134 | { 135 | byte[] data = await VerifyHelper.GetDecryptDataAsync(assetBundlePath, _loadProgress, bundlePackageSecretKey); 136 | if (_loadState == LoadState.Finish) 137 | { 138 | return; 139 | } 140 | ETTask tcs = ETTask.Create(true); 141 | _assetBundleCreateRequest = AssetBundle.LoadFromMemoryAsync(data); 142 | _assetBundleCreateRequest.completed += operation => 143 | { 144 | AssetBundle = _assetBundleCreateRequest.assetBundle; 145 | tcs.SetResult(); 146 | //判断是否还需要 147 | if (_refCount <= 0) 148 | { 149 | AssetComponent.AddPreUnLoadPool(this); 150 | } 151 | }; 152 | await tcs; 153 | } 154 | 155 | /// 156 | /// 通过路径直接加载硬盘上的AssetBundle 157 | /// 158 | private async ETTask LoadBundleFinish(string assetBundlePath) 159 | { 160 | if (_loadState == LoadState.Finish) 161 | { 162 | return; 163 | } 164 | ETTask tcs = ETTask.Create(true); 165 | _assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(assetBundlePath); 166 | _assetBundleCreateRequest.completed += operation => 167 | { 168 | AssetBundle = _assetBundleCreateRequest.assetBundle; 169 | tcs.SetResult(); 170 | //判断是否还需要 171 | if (_refCount <= 0) 172 | { 173 | AssetComponent.AddPreUnLoadPool(this); 174 | } 175 | }; 176 | await tcs; 177 | } 178 | 179 | /// 180 | /// 强制加载完成 181 | /// 182 | internal void ForceLoadFinish(string bundlePackageName) 183 | { 184 | if (_loadState == LoadState.Finish) 185 | { 186 | return; 187 | } 188 | if (_assetBundleCreateRequest != null) 189 | { 190 | AssetLogHelper.LogError("触发强制加载, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath + 191 | "\nAssetBundle包名: " + AssetBundleName); 192 | AssetBundle = _assetBundleCreateRequest.assetBundle; 193 | return; 194 | } 195 | 196 | if (AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt) 197 | { 198 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt); 199 | byte[] data = VerifyHelper.GetDecryptData(assetBundlePath, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].SecretKey); 200 | AssetBundle = AssetBundle.LoadFromMemory(data); 201 | } 202 | else 203 | { 204 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, false); 205 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath); 206 | } 207 | _loadState = LoadState.Finish; 208 | //判断是否还需要 209 | if (_refCount <= 0) 210 | { 211 | AssetComponent.AddPreUnLoadPool(this); 212 | } 213 | } 214 | 215 | /// 216 | /// 打开进度统计 217 | /// 218 | internal void OpenProgress() 219 | { 220 | _loadProgress = new WebLoadProgress(); 221 | } 222 | 223 | internal float GetProgress() 224 | { 225 | if (_loadProgress == null) 226 | { 227 | AssetLogHelper.LogError("未打开进度统计无法获取进度"); 228 | return 0; 229 | } 230 | if (_loadState == LoadState.Finish) 231 | { 232 | return 1; 233 | } 234 | if (_loadState == LoadState.NoLoad) 235 | { 236 | return 0; 237 | } 238 | 239 | if (_loadProgress.WeqOperation == null) 240 | { 241 | if (_assetBundleCreateRequest == null) 242 | { 243 | return _loadProgress.GetWebProgress() / 2; 244 | } 245 | return _assetBundleCreateRequest.progress; 246 | } 247 | if (_assetBundleCreateRequest == null) 248 | { 249 | return _loadProgress.GetWebProgress() / 2; 250 | } 251 | return (_assetBundleCreateRequest.progress + 1.0f) / 2; 252 | } 253 | 254 | #endif 255 | 256 | 257 | } 258 | 259 | /// 260 | /// AssetBundle加载的状态 261 | /// 262 | internal enum LoadState 263 | { 264 | NoLoad = 0, 265 | Loading = 1, 266 | Finish = 2 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadBaseUnLoad.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | public partial class LoadBase 4 | { 5 | /// 6 | /// 卸载AssetBundle 7 | /// 8 | public void UnLoad() 9 | { 10 | _assetBundleCreateRequest = null; 11 | AssetBundle.Unload(true); 12 | _loadState = LoadState.NoLoad; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadDepend.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | /// 4 | /// 一个文件的依赖项 5 | /// 6 | public class LoadDepend : LoadBase 7 | { 8 | public LoadDepend() 9 | { 10 | base.LoadType = LoadType.Depend; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadFile.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | /// 4 | /// 一个文件以及它的bundle包信息 5 | /// 6 | public class LoadFile : LoadBase 7 | { 8 | /// 9 | /// 依赖的文件的名字 10 | /// 11 | public string[] DependFileName; 12 | 13 | public LoadFile() 14 | { 15 | base.LoadType = LoadType.File; 16 | } 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BM 4 | { 5 | public class LoadGroup : LoadBase 6 | { 7 | /// 8 | /// 组Bundle里所有资源 9 | /// 10 | public List FilePathList = new List(); 11 | 12 | /// 13 | /// 依赖的文件的名字 14 | /// 15 | public List DependFileName = new List(); 16 | 17 | public LoadGroup() 18 | { 19 | base.LoadType = LoadType.Group; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/LoadHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BM 4 | { 5 | internal static class LoadHandlerFactory 6 | { 7 | private static Queue _loadHandlerPool = new Queue(); 8 | 9 | internal static LoadHandler GetLoadHandler(string assetPath, string bundlePackageName, bool haveHandler, bool isPool) 10 | { 11 | LoadHandler loadHandler; 12 | if (_loadHandlerPool.Count > 0 && isPool) 13 | { 14 | loadHandler = _loadHandlerPool.Dequeue(); 15 | loadHandler.Init(assetPath, bundlePackageName, haveHandler); 16 | return loadHandler; 17 | } 18 | loadHandler = CreateLoadHandler(assetPath, bundlePackageName, haveHandler, isPool); 19 | return loadHandler; 20 | } 21 | 22 | private static LoadHandler CreateLoadHandler(string assetPath, string bundlePackageName, bool haveHandler, bool isPool) 23 | { 24 | LoadHandler loadHandler = new LoadHandler(isPool); 25 | loadHandler.Init(assetPath, bundlePackageName, haveHandler); 26 | return loadHandler; 27 | } 28 | 29 | internal static void EnterPool(LoadHandler loadHandler) 30 | { 31 | _loadHandlerPool.Enqueue(loadHandler); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Base/WebLoadProgress.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine.Networking; 2 | 3 | namespace BM 4 | { 5 | public class WebLoadProgress 6 | { 7 | internal UnityWebRequestAsyncOperation WeqOperation; 8 | 9 | internal float GetWebProgress() 10 | { 11 | if (WeqOperation != null) 12 | { 13 | return WeqOperation.progress; 14 | } 15 | return 1; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/BundleMasterRuntimeConfig.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BM 4 | { 5 | public class BundleMasterRuntimeConfig : ScriptableObject 6 | { 7 | /// 8 | /// 加载模式 9 | /// 10 | public AssetLoadMode AssetLoadMode; 11 | 12 | /// 13 | /// 最大同时下载的资源数量 14 | /// 15 | public int MaxDownLoadCount; 16 | 17 | /// 18 | /// 下载失败最多重试次数 19 | /// 20 | public int ReDownLoadCount; 21 | } 22 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/BundleRuntimeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | /// 8 | /// 存放一个Bundle分包的初始化信息 9 | /// 10 | public class BundleRuntimeInfo 11 | { 12 | /// 13 | /// 主动加载的文件 14 | /// 15 | internal readonly Dictionary LoadFileDic = new Dictionary(); 16 | 17 | /// 18 | /// 依赖加载的文件 19 | /// 20 | internal readonly Dictionary LoadDependDic = new Dictionary(); 21 | 22 | /// 23 | /// 细分组的文件 24 | /// 25 | internal readonly Dictionary LoadGroupDic = new Dictionary(); 26 | 27 | /// 28 | /// 细分组的路径 29 | /// 30 | internal readonly List LoadGroupDicKey = new List(); 31 | 32 | /// 33 | /// 资源路径对应的资源LoadHandler 34 | /// 35 | internal readonly Dictionary AllAssetLoadHandler = new Dictionary(); 36 | 37 | /// 38 | /// 所有没有卸载的LoadHandler 39 | /// 40 | internal readonly Dictionary UnLoadHandler = new Dictionary(); 41 | 42 | /// 43 | /// 分包的名称 44 | /// 45 | private string BundlePackageName; 46 | 47 | /// 48 | /// 分包是否加密 49 | /// 50 | internal bool Encrypt = false; 51 | 52 | /// 53 | /// 分包的加密Key 54 | /// 55 | internal char[] SecretKey = null; 56 | 57 | /// 58 | /// Shader的AssetBundle 59 | /// 60 | internal AssetBundle Shader = null; 61 | 62 | public BundleRuntimeInfo(string bundlePackageName, string secretKey = null) 63 | { 64 | BundlePackageName = bundlePackageName; 65 | if (secretKey != null) 66 | { 67 | SecretKey = secretKey.ToCharArray(); 68 | Encrypt = true; 69 | } 70 | } 71 | 72 | public T Load(string assetPath) where T : UnityEngine.Object => AssetComponent.Load(assetPath, BundlePackageName); 73 | public T Load(out LoadHandler loadHandler, string assetPath, bool isPool = false) where T : UnityEngine.Object => AssetComponent.Load(out loadHandler, assetPath, isPool, BundlePackageName); 74 | public UnityEngine.Object Load(string assetPath) => AssetComponent.Load(assetPath, BundlePackageName); 75 | public UnityEngine.Object Load(out LoadHandler loadHandler, string assetPath, bool isPool = false) => AssetComponent.Load(out loadHandler, assetPath, isPool, BundlePackageName); 76 | public async ETTask LoadAsync(string assetPath) where T : UnityEngine.Object => await AssetComponent.LoadAsync(assetPath, BundlePackageName); 77 | public ETTask LoadAsync(out LoadHandler loadHandler, string assetPath, bool isPool = false) where T : UnityEngine.Object => AssetComponent.LoadAsync(out loadHandler, assetPath, isPool, BundlePackageName); 78 | public async ETTask LoadAsync(string assetPath) => await AssetComponent.LoadAsync(assetPath, BundlePackageName); 79 | public ETTask LoadAsync(out LoadHandler loadHandler, string assetPath, bool isPool = false) => AssetComponent.LoadAsync(out loadHandler,assetPath, isPool, BundlePackageName); 80 | public LoadSceneHandler LoadScene(string scenePath) => AssetComponent.LoadScene(scenePath, BundlePackageName); 81 | public async ETTask LoadSceneAsync(string scenePath) => await AssetComponent.LoadSceneAsync(scenePath, BundlePackageName); 82 | public ETTask LoadSceneAsync(out LoadSceneHandler loadSceneHandler, string scenePath) => AssetComponent.LoadSceneAsync(out loadSceneHandler, scenePath, BundlePackageName); 83 | 84 | /// 85 | /// 通过路径卸载(场景资源不可以通过路径卸载) 86 | /// 87 | public void UnLoadByPath(string assetPath) => AssetComponent.UnLoadByPath(assetPath, BundlePackageName); 88 | } 89 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/DownLoadTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ET; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using UnityEngine.Networking; 6 | using UnityEngine; 7 | 8 | #if !BMWebGL 9 | using LMTD; 10 | #endif 11 | 12 | namespace BM 13 | { 14 | #if !BMWebGL 15 | public class DownLoadTask 16 | { 17 | public UpdateBundleDataInfo UpdateBundleDataInfo; 18 | 19 | /// 20 | /// 资源更新下载完成回调位 21 | /// 22 | public ETTask DownLoadingKey; 23 | 24 | public Dictionary> PackageDownLoadTask; 25 | 26 | /// 27 | /// 下载的资源分包名称 28 | /// 29 | public string PackegName; 30 | 31 | /// 32 | /// 分包所在路径 33 | /// 34 | public string DownLoadPackagePath; 35 | 36 | /// 37 | /// 下载的文件的名称 38 | /// 39 | public string FileName; 40 | 41 | /// 42 | /// 下载的文件的大小 43 | /// 44 | public long FileSize; 45 | 46 | public async ETTask DownLoad() 47 | { 48 | string url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, UnityWebRequest.EscapeURL(FileName)); 49 | if (FileName.Contains("\\")) 50 | { 51 | string[] pathSplits = FileName.Split('\\'); 52 | string filePath = ""; 53 | string fileUrls = ""; 54 | for (int i = 0; i < pathSplits.Length - 1; i++) 55 | { 56 | filePath += (pathSplits[i] + "/"); 57 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[i]) + "/"); 58 | } 59 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[pathSplits.Length - 1])); 60 | Directory.CreateDirectory(Path.Combine(AssetComponentConfig.HotfixPath, PackegName, filePath)); 61 | url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, fileUrls); 62 | } 63 | float startDownLoadTime = Time.realtimeSinceStartup; 64 | DownLoadData downLoadData = await DownloadBundleHelper.DownloadRefDataByUrl(url); 65 | if (downLoadData.Data == null) 66 | { 67 | UpdateBundleDataInfo.CancelUpdate(); 68 | } 69 | //说明下载更新已经被取消 70 | if (UpdateBundleDataInfo.Cancel) 71 | { 72 | DownLoadData.Recovery(downLoadData); 73 | return; 74 | } 75 | Debug.Assert(downLoadData.Data != null, "downLoadData.Data != null"); 76 | int dataLength = downLoadData.Data.Length; 77 | 78 | string fileCreatePath = PathUnifiedHelper.UnifiedPath(Path.Combine(DownLoadPackagePath, FileName)); 79 | using (FileStream fs = new FileStream(fileCreatePath, FileMode.Create)) 80 | { 81 | //大于2M用异步 82 | if (dataLength > 2097152) 83 | { 84 | await fs.WriteAsync(downLoadData.Data, 0, downLoadData.Data.Length); 85 | } 86 | else 87 | { 88 | fs.Write(downLoadData.Data, 0, downLoadData.Data.Length); 89 | } 90 | fs.Close(); 91 | } 92 | UpdateBundleDataInfo.AddCRCFileInfo(PackegName, FileName, VerifyHelper.GetCRC32(downLoadData)); 93 | UpdateBundleDataInfo.FinishUpdateSize += downLoadData.Data.Length; 94 | UpdateBundleDataInfo.FinishDownLoadBundleCount++; 95 | DownLoadData.Recovery(downLoadData); 96 | foreach (Queue downLoadTaskQueue in PackageDownLoadTask.Values) 97 | { 98 | if (downLoadTaskQueue.Count > 0) 99 | { 100 | downLoadTaskQueue.Dequeue().DownLoad().Coroutine(); 101 | return; 102 | } 103 | } 104 | //说明下载完成了 105 | if (UpdateBundleDataInfo.FinishDownLoadBundleCount < UpdateBundleDataInfo.NeedDownLoadBundleCount) 106 | { 107 | DownLoadData.ClearPool(); 108 | return; 109 | } 110 | UpdateBundleDataInfo.FinishUpdate = true; 111 | DownLoadingKey.SetResult(); 112 | } 113 | 114 | 115 | public async ETTask ThreadDownLoad() 116 | { 117 | //计算URL 118 | string url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, UnityWebRequest.EscapeURL(FileName)); 119 | if (FileName.Contains("\\")) 120 | { 121 | string[] pathSplits = FileName.Split('\\'); 122 | string filePath = ""; 123 | string fileUrls = ""; 124 | for (int i = 0; i < pathSplits.Length - 1; i++) 125 | { 126 | filePath += (pathSplits[i] + "/"); 127 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[i]) + "/"); 128 | } 129 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[pathSplits.Length - 1])); 130 | Directory.CreateDirectory(Path.Combine(AssetComponentConfig.HotfixPath, PackegName, filePath)); 131 | url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, fileUrls); 132 | } 133 | //计算文件存储路径 134 | string fileCreatePath = PathUnifiedHelper.UnifiedPath(Path.Combine(DownLoadPackagePath, FileName)); 135 | //开始下载 136 | LmtDownloadInfo lmtDownloadInfo = await DownloadBundleHelper.DownloadData(url, fileCreatePath, UpdateBundleDataInfo); 137 | //说明下载更新已经被取消 138 | if (UpdateBundleDataInfo.Cancel) 139 | { 140 | return; 141 | } 142 | UpdateBundleDataInfo.AddCRCFileInfo(PackegName, FileName, lmtDownloadInfo.DownLoadFileCRC); 143 | UpdateBundleDataInfo.FinishDownLoadBundleCount++; 144 | //检查新的需要下载的资源 145 | foreach (Queue downLoadTaskQueue in PackageDownLoadTask.Values) 146 | { 147 | if (downLoadTaskQueue.Count > 0) 148 | { 149 | downLoadTaskQueue.Dequeue().ThreadDownLoad().Coroutine(); 150 | return; 151 | } 152 | } 153 | //说明下载完成了 154 | if (UpdateBundleDataInfo.FinishDownLoadBundleCount < UpdateBundleDataInfo.NeedDownLoadBundleCount) 155 | { 156 | DownLoadData.ClearPool(); 157 | return; 158 | } 159 | UpdateBundleDataInfo.FinishUpdate = true; 160 | DownLoadingKey.SetResult(); 161 | } 162 | 163 | } 164 | #endif 165 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/AssetLogHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace BM 5 | { 6 | public static class AssetLogHelper 7 | { 8 | public static void Log(string message) 9 | { 10 | Debug.Log(message); 11 | } 12 | 13 | public static void LogError(string message) 14 | { 15 | Debug.LogError(message); 16 | } 17 | 18 | public static void LogError(Exception e) 19 | { 20 | if (e.Data.Contains("StackTrace")) 21 | { 22 | Debug.LogError($"{e.Data["StackTrace"]}\n{e}"); 23 | return; 24 | } 25 | string str = e.ToString(); 26 | Debug.LogError(str); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/DeleteHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace BM 5 | { 6 | public static class DeleteHelper 7 | { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/DownloadBundleHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using UnityEngine.Networking; 4 | using ET; 5 | 6 | #if !BMWebGL 7 | using LMTD; 8 | #endif 9 | 10 | namespace BM 11 | { 12 | public static class DownloadBundleHelper 13 | { 14 | public static async ETTask DownloadDataByUrl(string url) 15 | { 16 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++) 17 | { 18 | byte[] data = await DownloadData(url); 19 | if (data != null) 20 | { 21 | return data; 22 | } 23 | } 24 | AssetLogHelper.LogError("下载资源失败: " + url); 25 | return null; 26 | } 27 | 28 | private static async ETTask DownloadData(string url) 29 | { 30 | using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) 31 | { 32 | UnityWebRequestAsyncOperation webRequestAsync = webRequest.SendWebRequest(); 33 | ETTask waitDown = ETTask.Create(true); 34 | webRequestAsync.completed += (asyncOperation) => 35 | { 36 | waitDown.SetResult(); 37 | }; 38 | await waitDown; 39 | #if UNITY_2020_1_OR_NEWER 40 | if (webRequest.result != UnityWebRequest.Result.Success) 41 | #else 42 | if (!string.IsNullOrEmpty(webRequest.error)) 43 | #endif 44 | { 45 | AssetLogHelper.Log("下载Bundle失败 重试\n" + webRequest.error + "\nURL:" + url); 46 | return null; 47 | } 48 | return webRequest.downloadHandler.data; 49 | } 50 | } 51 | 52 | 53 | public static async ETTask DownloadRefDataByUrl(string url) 54 | { 55 | DownLoadData downLoadData = DownLoadData.Get(); 56 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++) 57 | { 58 | await DownloadData(url, downLoadData); 59 | if (downLoadData.Data != null) 60 | { 61 | return downLoadData; 62 | } 63 | } 64 | AssetLogHelper.LogError("下载资源失败: " + url); 65 | return null; 66 | } 67 | 68 | private static async ETTask DownloadData(string url, DownLoadData downLoadData) 69 | { 70 | using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) 71 | { 72 | UnityWebRequestAsyncOperation webRequestAsync = webRequest.SendWebRequest(); 73 | ETTask waitDown = ETTask.Create(true); 74 | webRequestAsync.completed += (asyncOperation) => 75 | { 76 | waitDown.SetResult(); 77 | }; 78 | await waitDown; 79 | #if UNITY_2020_1_OR_NEWER 80 | if (webRequest.result != UnityWebRequest.Result.Success) 81 | #else 82 | if (!string.IsNullOrEmpty(webRequest.error)) 83 | #endif 84 | { 85 | AssetLogHelper.Log("下载Bundle失败 重试\n" + webRequest.error + "\nURL:" + url); 86 | return; 87 | } 88 | downLoadData.Data = webRequest.downloadHandler.data; 89 | webRequest.downloadHandler.Dispose(); 90 | } 91 | } 92 | 93 | /// 94 | /// 下载完成回调位 95 | /// 96 | internal static readonly Queue DownLoadFinishQueue = new Queue(); 97 | 98 | #if !BMWebGL 99 | 100 | /// 101 | /// 多线程下载资源直存 102 | /// 103 | public static async ETTask DownloadData(string url, string filePath, UpdateBundleDataInfo updateBundleDataInfo) 104 | { 105 | LmtDownloadInfo lmtDownloadInfo = new LmtDownloadInfo(); 106 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++) 107 | { 108 | //说明下载更新已经被取消 109 | if (updateBundleDataInfo.Cancel) 110 | { 111 | return lmtDownloadInfo; 112 | } 113 | 114 | lmtDownloadInfo = await DownloadOneData(url, filePath, updateBundleDataInfo); 115 | if (lmtDownloadInfo.LmtDownloadResult == LmtDownloadResult.Success) 116 | { 117 | return lmtDownloadInfo; 118 | } 119 | } 120 | //多次下载失败直接取消下载 121 | updateBundleDataInfo.CancelUpdate(); 122 | return lmtDownloadInfo; 123 | } 124 | 125 | private static async ETTask DownloadOneData(string url, string filePath, UpdateBundleDataInfo updateBundleDataInfo) 126 | { 127 | ETTask tcs = ETTask.Create(true); 128 | LMTDownLoad lmtDownLoad = LMTDownLoad.Create(url, filePath); 129 | long lastDownSize = 0; 130 | lmtDownLoad.UpDateInfo += () => 131 | { 132 | // ReSharper disable AccessToDisposedClosure 133 | updateBundleDataInfo.FinishUpdateSize += lmtDownLoad.LmtDownloadInfo.DownLoadSize - lastDownSize; 134 | lastDownSize = lmtDownLoad.LmtDownloadInfo.DownLoadSize; 135 | // ReSharper restore AccessToDisposedClosure 136 | lock (updateBundleDataInfo) 137 | { 138 | if (updateBundleDataInfo.Cancel) 139 | { 140 | // ReSharper disable once AccessToDisposedClosure 141 | lmtDownLoad.CancelLock = true; 142 | } 143 | } 144 | }; 145 | lmtDownLoad.Completed += (info) => 146 | { 147 | lock (DownLoadFinishQueue) 148 | { 149 | DownLoadFinishQueue.Enqueue(tcs); 150 | } 151 | }; 152 | ThreadFactory.ThreadAction(lmtDownLoad); 153 | await tcs; 154 | LmtDownloadInfo lmtDownloadInfo = lmtDownLoad.LmtDownloadInfo; 155 | if (lmtDownloadInfo.LmtDownloadResult != LmtDownloadResult.Success) 156 | { 157 | updateBundleDataInfo.FinishUpdateSize -= lmtDownLoad.LmtDownloadInfo.DownLoadSize; 158 | //主动取消下载就没必要再输出log了 159 | if (lmtDownloadInfo.LmtDownloadResult != LmtDownloadResult.CancelDownLoad) 160 | { 161 | AssetLogHelper.LogError("下载资源失败: " + lmtDownloadInfo.LmtDownloadResult); 162 | } 163 | } 164 | lmtDownLoad.Dispose(); 165 | return lmtDownloadInfo; 166 | } 167 | 168 | /// 169 | /// 另一种多线程下载的方式,不建议使用 170 | /// 171 | public static async Task DownloadDataTask(string url, string filePath) 172 | { 173 | LMTDownLoad lmtDownLoad = LMTDownLoad.Create(url, filePath); 174 | Task tcs = new Task(lmtDownLoad.DownLoad); 175 | tcs.Start(); 176 | await tcs; 177 | lmtDownLoad.Dispose(); 178 | return lmtDownLoad.LmtDownloadInfo; 179 | } 180 | #endif 181 | 182 | } 183 | 184 | public class DownLoadData 185 | { 186 | public byte[] Data; 187 | 188 | private static readonly Queue DownLoadDataPool = new Queue(); 189 | 190 | public static DownLoadData Get() 191 | { 192 | if (DownLoadDataPool.Count > 0) 193 | { 194 | return DownLoadDataPool.Dequeue(); 195 | } 196 | else 197 | { 198 | return new DownLoadData(); 199 | } 200 | } 201 | 202 | public static void Recovery(DownLoadData downLoadData) 203 | { 204 | downLoadData.Data = null; 205 | DownLoadDataPool.Enqueue(downLoadData); 206 | } 207 | 208 | public static void ClearPool() 209 | { 210 | DownLoadDataPool.Clear(); 211 | } 212 | 213 | } 214 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/GroupAssetHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BM 4 | { 5 | public static class GroupAssetHelper 6 | { 7 | /// 8 | /// 判断是否是颗粒化组的文件 9 | /// 10 | /// 组路径 11 | public static string IsGroupAsset(string assetPath, List loadGroupKey) 12 | { 13 | for (int i = 0; i < loadGroupKey.Count; i++) 14 | { 15 | if (assetPath.Contains(loadGroupKey[i])) 16 | { 17 | return loadGroupKey[i]; 18 | } 19 | } 20 | return null; 21 | } 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/HandlerIdHelper.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | public static class HandlerIdHelper 4 | { 5 | private static uint _handlerIdCounter = 0; 6 | 7 | /// 8 | /// 获取唯一ID 9 | /// 10 | /// 11 | public static uint GetUniqueId() 12 | { 13 | _handlerIdCounter++; 14 | return _handlerIdCounter; 15 | } 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/PathUnifiedHelper.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | public static class PathUnifiedHelper 4 | { 5 | /// 6 | /// 解决路径资源是否存在的判定问题 7 | /// 8 | public static string UnifiedPath(string path) 9 | { 10 | 11 | #if UNITY_ANDROID && !UNITY_EDITOR 12 | path = path.Replace("\\", "/"); 13 | #elif UNITY_IOS && !UNITY_EDITOR 14 | path = path.Replace("\\", "/"); 15 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX 16 | 17 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN 18 | 19 | #endif 20 | return path; 21 | } 22 | 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/TimeAwaitHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | public static class TimeAwaitHelper 7 | { 8 | internal static readonly Queue TimerFactoryQueue = new Queue(); 9 | 10 | /// 11 | /// 等待一定时间 12 | /// 13 | /// 单位 秒 14 | /// 异步取消锁 15 | public static ETTask AwaitTime(float time, ETCancellationToken cancellationToken = null) 16 | { 17 | TimerAwait timerAwait; 18 | if (TimerFactoryQueue.Count > 0) 19 | { 20 | timerAwait = TimerFactoryQueue.Dequeue(); 21 | } 22 | else 23 | { 24 | timerAwait = new TimerAwait(); 25 | } 26 | ETTask tcs = ETTask.Create(true); 27 | timerAwait.Init(time, tcs); 28 | cancellationToken?.Add(() => 29 | { 30 | timerAwait.Cancel(); 31 | tcs.SetResult(); 32 | }); 33 | return tcs; 34 | } 35 | } 36 | 37 | 38 | internal class TimerAwait 39 | { 40 | private float remainingTime = 0; 41 | private ETTask tcs; 42 | private bool cancelTimer = false; 43 | 44 | internal void Init(float time, ETTask task) 45 | { 46 | this.remainingTime = time; 47 | this.tcs = task; 48 | AssetComponent.TimerAwaitQueue.Enqueue(this); 49 | cancelTimer = false; 50 | } 51 | 52 | internal bool CalcSubTime(float time) 53 | { 54 | remainingTime -= time; 55 | if (remainingTime > 0) 56 | { 57 | return false; 58 | } 59 | if (!cancelTimer) 60 | { 61 | tcs.SetResult(); 62 | } 63 | tcs = null; 64 | remainingTime = 0; 65 | TimeAwaitHelper.TimerFactoryQueue.Enqueue(this); 66 | return true; 67 | } 68 | 69 | internal void Cancel() 70 | { 71 | cancelTimer = true; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/ValueHelper.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | public static class ValueHelper 4 | { 5 | public static float GetMinValue(float value1, float value2) 6 | { 7 | if (value1 > value2) 8 | { 9 | return value2; 10 | } 11 | return value1; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/Helper/VerifyHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using ET; 6 | using UnityEngine.Networking; 7 | 8 | namespace BM 9 | { 10 | /// 11 | /// 验证脚本 12 | /// 13 | public static class VerifyHelper 14 | { 15 | public static string GetMd5Hash(string filePath) 16 | { 17 | byte[] data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(filePath)); 18 | StringBuilder sb = new StringBuilder(); 19 | foreach (byte t in data) 20 | { 21 | sb.Append(t.ToString("x2")); 22 | } 23 | return sb.ToString(); 24 | } 25 | 26 | /// 27 | /// 得到一个路径下文件的CRC32 28 | /// 29 | internal static async ETTask GetFileCRC32(string filePath) 30 | { 31 | uint fileCRC32; 32 | ETTask tcs = ETTask.Create(true); 33 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) 34 | { 35 | webRequest.timeout = 0; 36 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 37 | weq.completed += o => 38 | { 39 | tcs.SetResult(); 40 | }; 41 | await tcs; 42 | #if UNITY_2020_1_OR_NEWER 43 | if (webRequest.result == UnityWebRequest.Result.Success) 44 | #else 45 | if (string.IsNullOrEmpty(webRequest.error)) 46 | #endif 47 | { 48 | byte[] data = webRequest.downloadHandler.data; 49 | fileCRC32 = VerifyHelper.GetCRC32(data); 50 | } 51 | else 52 | { 53 | fileCRC32 = 0; 54 | } 55 | } 56 | return fileCRC32; 57 | } 58 | public static uint GetCRC32(byte[] bytes) 59 | { 60 | uint iCount = (uint)bytes.Length; 61 | uint crc = 0xFFFFFFFF; 62 | for (uint i = 0; i < iCount; i++) 63 | { 64 | crc = (crc << 8) ^ CRCTable[(crc >> 24) ^ bytes[i]]; 65 | } 66 | return crc; 67 | } 68 | 69 | internal static uint GetCRC32(DownLoadData downLoadData) 70 | { 71 | uint iCount = (uint)downLoadData.Data.Length; 72 | uint crc = 0xFFFFFFFF; 73 | for (uint i = 0; i < iCount; i++) 74 | { 75 | crc = (crc << 8) ^ CRCTable[(crc >> 24) ^ downLoadData.Data[i]]; 76 | } 77 | return crc; 78 | } 79 | 80 | /// 81 | /// 异步获取解密的数据(无密钥的情况下直接获取数据) 82 | /// 83 | internal static async ETTask GetDecryptDataAsync(string filePath, WebLoadProgress loadProgress = null, char[] secretKey = null) 84 | { 85 | byte[] encryptData; 86 | ETTask tcs = ETTask.Create(); 87 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) 88 | { 89 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest(); 90 | if (loadProgress != null) 91 | { 92 | loadProgress.WeqOperation = weq; 93 | } 94 | weq.completed += (o) => 95 | { 96 | tcs.SetResult(); 97 | }; 98 | await tcs; 99 | #if UNITY_2020_1_OR_NEWER 100 | if (webRequest.result == UnityWebRequest.Result.Success) 101 | #else 102 | if (string.IsNullOrEmpty(webRequest.error)) 103 | #endif 104 | { 105 | encryptData = webRequest.downloadHandler.data; 106 | if (secretKey != null) 107 | { 108 | for (int i = 0; i < encryptData.Length; i++) 109 | { 110 | encryptData[i] = (byte)(encryptData[i] ^ secretKey[i % secretKey.Length]); 111 | } 112 | } 113 | } 114 | else 115 | { 116 | encryptData = null; 117 | } 118 | } 119 | return encryptData; 120 | } 121 | 122 | /// 123 | /// 获取解密的数据(无密钥的情况下直接获取数据) 124 | /// 125 | internal static byte[] GetDecryptData(string filePath, char[] secretKey = null) 126 | { 127 | byte[] encryptData; 128 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath)) 129 | { 130 | webRequest.SendWebRequest(); 131 | while (!webRequest.isDone) { } 132 | #if UNITY_2020_1_OR_NEWER 133 | if (webRequest.result == UnityWebRequest.Result.Success) 134 | #else 135 | if (string.IsNullOrEmpty(webRequest.error)) 136 | #endif 137 | { 138 | encryptData = webRequest.downloadHandler.data; 139 | if (secretKey != null) 140 | { 141 | for (int i = 0; i < encryptData.Length; i++) 142 | { 143 | encryptData[i] = (byte)(encryptData[i] ^ secretKey[i % secretKey.Length]); 144 | } 145 | } 146 | } 147 | else 148 | { 149 | encryptData = null; 150 | } 151 | } 152 | return encryptData; 153 | } 154 | 155 | /// 156 | /// 注意此Table和多线程下载的Table以及打AssetBundle加密用的Table需要一样,其中在多线程下载的Table里还有一份拷贝 157 | /// 158 | private static readonly UInt32[] CRCTable = 159 | { 160 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 161 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 162 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 163 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 164 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 165 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 166 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 167 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 168 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 169 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 170 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 171 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 172 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 173 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 174 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 175 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 176 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 177 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 178 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 179 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 180 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 181 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 182 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 183 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 184 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 185 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 186 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 187 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 188 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 189 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 190 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 191 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 192 | }; 193 | 194 | } 195 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/LoadHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | public class LoadHandler : LoadHandlerBase 8 | { 9 | internal Action CompleteCallback; 10 | public event Action Completed 11 | { 12 | add 13 | { 14 | if (Asset != null) 15 | { 16 | value(this); 17 | } 18 | else 19 | { 20 | this.CompleteCallback += value; 21 | } 22 | } 23 | remove => this.CompleteCallback -= value; 24 | } 25 | 26 | /// 27 | /// 是否进池 28 | /// 29 | private bool isPool; 30 | 31 | /// 32 | /// 加载出来的资源 33 | /// 34 | public UnityEngine.Object Asset = null; 35 | 36 | /// 37 | /// 加载的状态 38 | /// 39 | internal LoadState LoadState = LoadState.NoLoad; 40 | 41 | internal LoadHandler(bool isPool) 42 | { 43 | this.isPool = isPool; 44 | } 45 | 46 | internal void Init(string assetPath, string bundlePackageName, bool haveHandler) 47 | { 48 | AssetPath = assetPath; 49 | UniqueId = HandlerIdHelper.GetUniqueId(); 50 | BundlePackageName = bundlePackageName; 51 | LoadState = LoadState.NoLoad; 52 | UnloadFinish = false; 53 | HaveHandler = haveHandler; 54 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 55 | { 56 | //Develop模式直接返回就行 57 | return; 58 | } 59 | //说明是组里的资源 60 | string groupPath = GroupAssetHelper.IsGroupAsset(AssetPath, AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDicKey); 61 | if (groupPath != null) 62 | { 63 | //先找到对应加载的LoadGroup类 64 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(groupPath, out LoadGroup loadGroup)) 65 | { 66 | AssetLogHelper.LogError("没有找到资源组: " + groupPath); 67 | return; 68 | } 69 | LoadBase = loadGroup; 70 | //需要记录loadGroup的依赖 71 | for (int i = 0; i < loadGroup.DependFileName.Count; i++) 72 | { 73 | string dependFile = loadGroup.DependFileName[i]; 74 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend)) 75 | { 76 | LoadDepends.Add(loadDepend); 77 | continue; 78 | } 79 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile)) 80 | { 81 | LoadDependFiles.Add(loadDependFile); 82 | continue; 83 | } 84 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup)) 85 | { 86 | LoadDependGroups.Add(loadDependGroup); 87 | continue; 88 | } 89 | AssetLogHelper.LogError("依赖的资源没有找到对应的类: " + dependFile); 90 | } 91 | return; 92 | } 93 | //先找到对应加载的LoadFile类 94 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(AssetPath, out LoadFile loadFile)) 95 | { 96 | AssetLogHelper.LogError("没有找到资源: " + AssetPath); 97 | return; 98 | } 99 | LoadBase = loadFile; 100 | //需要记录loadFile的依赖 101 | for (int i = 0; i < loadFile.DependFileName.Length; i++) 102 | { 103 | string dependFile = loadFile.DependFileName[i]; 104 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend)) 105 | { 106 | LoadDepends.Add(loadDepend); 107 | continue; 108 | } 109 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile)) 110 | { 111 | LoadDependFiles.Add(loadDependFile); 112 | continue; 113 | } 114 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup)) 115 | { 116 | LoadDependGroups.Add(loadDependGroup); 117 | continue; 118 | } 119 | AssetLogHelper.LogError("依赖的资源没有找到对应的类: " + dependFile); 120 | } 121 | } 122 | 123 | /// 124 | /// 同步加载所有的Bundle 125 | /// 126 | internal void Load() 127 | { 128 | LoadBase.LoadAssetBundle(BundlePackageName); 129 | for (int i = 0; i < LoadDepends.Count; i++) 130 | { 131 | LoadDepends[i].LoadAssetBundle(BundlePackageName); 132 | } 133 | for (int i = 0; i < LoadDependFiles.Count; i++) 134 | { 135 | LoadDependFiles[i].LoadAssetBundle(BundlePackageName); 136 | } 137 | for (int i = 0; i < LoadDependGroups.Count; i++) 138 | { 139 | LoadDependGroups[i].LoadAssetBundle(BundlePackageName); 140 | } 141 | FileAssetBundle = LoadBase.AssetBundle; 142 | LoadState = LoadState.Finish; 143 | } 144 | 145 | /// 146 | /// 异步加载所有的Bundle 147 | /// 148 | internal async ETTask LoadAsync() 149 | { 150 | LoadState = LoadState.Loading; 151 | //计算出所有需要加载的Bundle包的总数 152 | RefLoadFinishCount = LoadDepends.Count + LoadDependFiles.Count + LoadDependGroups.Count + 1; 153 | ETTask tcs = ETTask.Create(true); 154 | LoadAsyncLoader(LoadBase, tcs).Coroutine(); 155 | for (int i = 0; i < LoadDepends.Count; i++) 156 | { 157 | LoadAsyncLoader(LoadDepends[i], tcs).Coroutine(); 158 | } 159 | for (int i = 0; i < LoadDependFiles.Count; i++) 160 | { 161 | LoadAsyncLoader(LoadDependFiles[i], tcs).Coroutine(); 162 | } 163 | for (int i = 0; i < LoadDependGroups.Count; i++) 164 | { 165 | LoadAsyncLoader(LoadDependGroups[i], tcs).Coroutine(); 166 | } 167 | await tcs; 168 | if (LoadState == LoadState.Finish) 169 | { 170 | AssetLogHelper.Log("此资源异步加载时触发了强制加载: " + AssetPath); 171 | } 172 | LoadState = LoadState.Finish; 173 | FileAssetBundle = LoadBase.AssetBundle; 174 | } 175 | 176 | /// 177 | /// 强制异步加载完成 178 | /// 179 | internal void ForceAsyncLoadFinish() 180 | { 181 | LoadBase.ForceLoadFinish(BundlePackageName); 182 | for (int i = 0; i < LoadDepends.Count; i++) 183 | { 184 | LoadDepends[i].ForceLoadFinish(BundlePackageName); 185 | } 186 | for (int i = 0; i < LoadDependFiles.Count; i++) 187 | { 188 | LoadDependFiles[i].ForceLoadFinish(BundlePackageName); 189 | } 190 | for (int i = 0; i < LoadDependGroups.Count; i++) 191 | { 192 | LoadDependGroups[i].ForceLoadFinish(BundlePackageName); 193 | } 194 | FileAssetBundle = LoadBase.AssetBundle; 195 | LoadState = LoadState.Finish; 196 | } 197 | 198 | /// 199 | /// 清理引用 200 | /// 201 | protected override void ClearAsset() 202 | { 203 | Asset = null; 204 | FileAssetBundle = null; 205 | LoadState = LoadState.NoLoad; 206 | foreach (LoadDepend loadDepends in LoadDepends) 207 | { 208 | loadDepends.SubRefCount(); 209 | } 210 | LoadDepends.Clear(); 211 | foreach (LoadFile loadDependFiles in LoadDependFiles) 212 | { 213 | loadDependFiles.SubRefCount(); 214 | } 215 | LoadDependFiles.Clear(); 216 | foreach (LoadGroup loadDependGroups in LoadDependGroups) 217 | { 218 | loadDependGroups.SubRefCount(); 219 | } 220 | LoadDependGroups.Clear(); 221 | LoadBase.SubRefCount(); 222 | LoadBase = null; 223 | //从缓存里取出进池 224 | if (!AssetComponent.BundleNameToRuntimeInfo.TryGetValue(BundlePackageName, out BundleRuntimeInfo bundleRuntimeInfo)) 225 | { 226 | AssetLogHelper.LogError("没要找到分包的信息: " + BundlePackageName); 227 | return; 228 | } 229 | if (HaveHandler) 230 | { 231 | if (!bundleRuntimeInfo.AllAssetLoadHandler.ContainsKey(AssetPath)) 232 | { 233 | AssetLogHelper.LogError("没要找到缓存的LoadHandler: " + AssetPath); 234 | return; 235 | } 236 | bundleRuntimeInfo.AllAssetLoadHandler.Remove(AssetPath); 237 | } 238 | CompleteCallback = null; 239 | if (isPool) 240 | { 241 | //进池 242 | LoadHandlerFactory.EnterPool(this); 243 | } 244 | } 245 | 246 | } 247 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/LoadHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | public abstract class LoadHandlerBase 8 | { 9 | /// 10 | /// 是否是通过路径加载的 11 | /// 12 | internal bool HaveHandler = true; 13 | 14 | /// 15 | /// 对应的加载的资源的路径 16 | /// 17 | protected string AssetPath; 18 | 19 | /// 20 | /// 唯一ID 21 | /// 22 | public uint UniqueId = 0; 23 | 24 | /// 25 | /// 所属分包的名称 26 | /// 27 | protected string BundlePackageName; 28 | 29 | /// 30 | /// 加载完成的计数 31 | /// 32 | protected int RefLoadFinishCount = 0; 33 | 34 | /// 35 | /// 是否卸载标记位 36 | /// 37 | protected bool UnloadFinish = false; 38 | 39 | /// 40 | /// File文件AssetBundle的引用 41 | /// 42 | public AssetBundle FileAssetBundle; 43 | 44 | /// 45 | /// 资源所在的LoadBase包 46 | /// 47 | protected LoadBase LoadBase = null; 48 | 49 | /// 50 | /// 依赖的Bundle包 51 | /// 52 | protected readonly List LoadDepends = new List(); 53 | 54 | /// 55 | /// 依赖的其它File包 56 | /// 57 | protected readonly List LoadDependFiles = new List(); 58 | 59 | /// 60 | /// 依赖的其它Group包 61 | /// 62 | protected readonly List LoadDependGroups = new List(); 63 | 64 | /// 65 | /// 加载计数器(负责完成所有依赖的Bundle加载完成) 66 | /// 67 | protected async ETTask LoadAsyncLoader(LoadBase loadBase, ETTask baseTcs) 68 | { 69 | await loadBase.LoadAssetBundleAsync(BundlePackageName); 70 | RefLoadFinishCount--; 71 | if (RefLoadFinishCount == 0) 72 | { 73 | baseTcs.SetResult(); 74 | } 75 | if (RefLoadFinishCount < 0) 76 | { 77 | AssetLogHelper.LogError("资源加载引用计数不正确: " + RefLoadFinishCount); 78 | } 79 | } 80 | 81 | public void UnLoad() 82 | { 83 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 84 | { 85 | return; 86 | } 87 | if (UnloadFinish) 88 | { 89 | AssetLogHelper.LogError(AssetPath + "已经卸载完了"); 90 | return; 91 | } 92 | AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].UnLoadHandler.Remove(UniqueId); 93 | //减少引用数量 94 | ClearAsset(); 95 | UnloadFinish = true; 96 | } 97 | 98 | /// 99 | /// 子类需要实现清理资源引用的逻辑 100 | /// 101 | protected abstract void ClearAsset(); 102 | } 103 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/LoadSceneHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | public partial class LoadSceneHandler : LoadHandlerBase 8 | { 9 | public LoadSceneHandler(string scenePath, string bundlePackageName) 10 | { 11 | AssetPath = scenePath; 12 | UniqueId = HandlerIdHelper.GetUniqueId(); 13 | BundlePackageName = bundlePackageName; 14 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 15 | { 16 | //Develop模式直接返回 17 | return; 18 | } 19 | //说明是组里的资源 20 | string groupPath = GroupAssetHelper.IsGroupAsset(AssetPath, AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDicKey); 21 | if (groupPath != null) 22 | { 23 | //先找到对应加载的LoadGroup类 24 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(groupPath, out LoadGroup loadGroup)) 25 | { 26 | AssetLogHelper.LogError("没有找到资源组: " + groupPath); 27 | return; 28 | } 29 | LoadBase = loadGroup; 30 | //需要记录loadGroup的依赖 31 | for (int i = 0; i < loadGroup.DependFileName.Count; i++) 32 | { 33 | string dependFile = loadGroup.DependFileName[i]; 34 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend)) 35 | { 36 | LoadDepends.Add(loadDepend); 37 | continue; 38 | } 39 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile)) 40 | { 41 | LoadDependFiles.Add(loadDependFile); 42 | continue; 43 | } 44 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup)) 45 | { 46 | LoadDependGroups.Add(loadDependGroup); 47 | continue; 48 | } 49 | AssetLogHelper.LogError("场景依赖的资源没有找到对应的类: " + dependFile); 50 | } 51 | return; 52 | } 53 | //先找到对应加载的LoadFile类 54 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(AssetPath, out LoadFile loadFile)) 55 | { 56 | AssetLogHelper.LogError("没有找到资源: " + AssetPath); 57 | return; 58 | } 59 | LoadBase = loadFile; 60 | //需要记录loadFile的依赖 61 | for (int i = 0; i < loadFile.DependFileName.Length; i++) 62 | { 63 | string dependFile = loadFile.DependFileName[i]; 64 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend)) 65 | { 66 | LoadDepends.Add(loadDepend); 67 | continue; 68 | } 69 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile)) 70 | { 71 | LoadDependFiles.Add(loadDependFile); 72 | continue; 73 | } 74 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup)) 75 | { 76 | LoadDependGroups.Add(loadDependGroup); 77 | continue; 78 | } 79 | AssetLogHelper.LogError("场景依赖的资源没有找到对应的类: " + dependFile); 80 | } 81 | 82 | } 83 | 84 | /// 85 | /// 同步加载场景资源所需的的AssetBundle包 86 | /// 87 | public void LoadSceneBundle() 88 | { 89 | LoadBase.LoadAssetBundle(BundlePackageName); 90 | for (int i = 0; i < LoadDepends.Count; i++) 91 | { 92 | LoadDepends[i].LoadAssetBundle(BundlePackageName); 93 | } 94 | for (int i = 0; i < LoadDependFiles.Count; i++) 95 | { 96 | LoadDependFiles[i].LoadAssetBundle(BundlePackageName); 97 | } 98 | for (int i = 0; i < LoadDependGroups.Count; i++) 99 | { 100 | LoadDependGroups[i].LoadAssetBundle(BundlePackageName); 101 | } 102 | FileAssetBundle = LoadBase.AssetBundle; 103 | } 104 | 105 | /// 106 | /// 异步加载场景的Bundle 107 | /// 108 | public async ETTask LoadSceneBundleAsync(ETTask finishTask) 109 | { 110 | //计算出所有需要加载的Bundle包的总数 111 | RefLoadFinishCount = LoadDepends.Count + LoadDependFiles.Count + LoadDependGroups.Count + 1; 112 | LoadBase.OpenProgress(); 113 | LoadAsyncLoader(LoadBase, finishTask).Coroutine(); 114 | for (int i = 0; i < LoadDepends.Count; i++) 115 | { 116 | LoadDepends[i].OpenProgress(); 117 | LoadAsyncLoader(LoadDepends[i], finishTask).Coroutine(); 118 | } 119 | for (int i = 0; i < LoadDependFiles.Count; i++) 120 | { 121 | LoadDependFiles[i].OpenProgress(); 122 | LoadAsyncLoader(LoadDependFiles[i], finishTask).Coroutine(); 123 | } 124 | for (int i = 0; i < LoadDependGroups.Count; i++) 125 | { 126 | LoadDependGroups[i].OpenProgress(); 127 | LoadAsyncLoader(LoadDependGroups[i], finishTask).Coroutine(); 128 | } 129 | await finishTask; 130 | FileAssetBundle = LoadBase.AssetBundle; 131 | } 132 | 133 | /// 134 | /// 获取场景AssetBundle加载的进度 135 | /// 136 | public float GetProgress() 137 | { 138 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 139 | { 140 | //Develop模式无法异步加载 141 | return 100; 142 | } 143 | if (UnloadFinish) 144 | { 145 | return 0; 146 | } 147 | float progress = 0; 148 | int loadCount = 1; 149 | progress += LoadBase.GetProgress(); 150 | for (int i = 0; i < LoadDepends.Count; i++) 151 | { 152 | progress += LoadDepends[i].GetProgress(); 153 | loadCount++; 154 | } 155 | for (int i = 0; i < LoadDependFiles.Count; i++) 156 | { 157 | progress += LoadDependFiles[i].GetProgress(); 158 | loadCount++; 159 | } 160 | for (int i = 0; i < LoadDependGroups.Count; i++) 161 | { 162 | progress += LoadDependGroups[i].GetProgress(); 163 | loadCount++; 164 | } 165 | return progress / loadCount; 166 | } 167 | 168 | /// 169 | /// 卸载场景的AssetBundle包 170 | /// 171 | protected override void ClearAsset() 172 | { 173 | FileAssetBundle = null; 174 | foreach (LoadDepend loadDepends in LoadDepends) 175 | { 176 | loadDepends.SubRefCount(); 177 | } 178 | LoadDepends.Clear(); 179 | foreach (LoadFile loadDependFiles in LoadDependFiles) 180 | { 181 | loadDependFiles.SubRefCount(); 182 | } 183 | LoadDependFiles.Clear(); 184 | foreach (LoadGroup loadDependGroups in LoadDependGroups) 185 | { 186 | loadDependGroups.SubRefCount(); 187 | } 188 | LoadDependGroups.Clear(); 189 | LoadBase.SubRefCount(); 190 | LoadBase = null; 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /BundleMasterRuntime/UpdateBundleDataInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEngine; 5 | 6 | namespace BM 7 | { 8 | public class UpdateBundleDataInfo 9 | { 10 | /// 11 | /// 是否需要更新 12 | /// 13 | public bool NeedUpdate = false; 14 | 15 | /// 16 | /// 需要更新的总大小 17 | /// 18 | public long NeedUpdateSize = 0; 19 | 20 | /// 21 | /// 是否取消 22 | /// 23 | internal bool Cancel = false; 24 | 25 | /// 26 | /// 需要更新的Bundle的信息 27 | /// 28 | internal readonly Dictionary> PackageNeedUpdateBundlesInfos = new Dictionary>(); 29 | 30 | /// 31 | /// 分包对应的版本号 int[本地版本, 远程版本] 仅Build模式可用 32 | /// 33 | internal readonly Dictionary PackageToVersion = new Dictionary(); 34 | 35 | /// 36 | /// 分包以及对于的类型 37 | /// 38 | internal readonly Dictionary PackageToType = new Dictionary(); 39 | 40 | /// 41 | /// 客户端更新时间 42 | /// 43 | internal string UpdateTime = ""; 44 | 45 | /// 46 | /// CRC信息字典 47 | /// 48 | internal readonly Dictionary> PackageCRCDictionary = new Dictionary>(); 49 | 50 | /// 51 | /// CRC对应的写入流 52 | /// 53 | internal readonly Dictionary PackageCRCFile = new Dictionary(); 54 | 55 | /// 56 | /// 更新完成的大小 57 | /// 58 | public long FinishUpdateSize = 0; 59 | 60 | /// 61 | /// 是否更新完成 62 | /// 63 | public bool FinishUpdate = false; 64 | 65 | /// 66 | /// 更新进度(1 - 100) 67 | /// 68 | public float Progress 69 | { 70 | get 71 | { 72 | float progress = ((float)FinishUpdateSize / NeedUpdateSize) * 100.0f; 73 | return progress; 74 | } 75 | } 76 | 77 | /// 78 | /// 总共需要下载的Bundle的数量 79 | /// 80 | public int NeedDownLoadBundleCount = 0; 81 | 82 | /// 83 | /// 下载完成的Bundle的数量 84 | /// 85 | public int FinishDownLoadBundleCount = 0; 86 | 87 | /// 88 | /// 平滑进度 89 | /// 90 | internal float SmoothProgress = 0.0f; 91 | private Action progressCallback; 92 | /// 93 | /// 下载进度回调,每帧刷新,有插值 94 | /// 95 | public event Action ProgressCallback 96 | { 97 | add => progressCallback += value; 98 | remove => this.progressCallback -= value; 99 | } 100 | 101 | internal Action FinishCallback; 102 | 103 | /// 104 | /// 下载更新完成回调 105 | /// 106 | public event Action DownLoadFinishCallback 107 | { 108 | add => FinishCallback += value; 109 | remove => this.FinishCallback -= value; 110 | } 111 | 112 | /// 113 | /// 下载速度平均个数 114 | /// 115 | private int speedAvgCount = 5; 116 | /// 117 | /// 下载速度更新频率 118 | /// 119 | private long speedRate = 1000; 120 | /// 121 | /// 下载速度缓存队列 122 | /// 123 | private readonly Queue downLoadSpeedQueue = new Queue(); 124 | 125 | /// 126 | /// 下载速度 127 | /// 128 | public int DownLoadSpeed 129 | { 130 | get 131 | { 132 | int addSpeed = 0; 133 | int speedQueueCount = downLoadSpeedQueue.Count; 134 | if (speedQueueCount == 0) 135 | { 136 | return 0; 137 | } 138 | for (int i = 0; i < speedQueueCount; i++) 139 | { 140 | int tempSpeed = downLoadSpeedQueue.Dequeue(); 141 | addSpeed += tempSpeed; 142 | downLoadSpeedQueue.Enqueue(tempSpeed); 143 | } 144 | return addSpeed / speedQueueCount; 145 | } 146 | } 147 | 148 | private Action downLoadSpeedCallback; 149 | 150 | /// 151 | /// 下载速度改变回调,非每帧都刷新 单位byte每秒 152 | /// 153 | public event Action DownLoadSpeedCallback 154 | { 155 | add => downLoadSpeedCallback += value; 156 | remove => this.downLoadSpeedCallback -= value; 157 | } 158 | 159 | private void AddSpeedQueue(int speed) 160 | { 161 | if (downLoadSpeedQueue.Count >= speedAvgCount) 162 | { 163 | downLoadSpeedQueue.Dequeue(); 164 | AddSpeedQueue(speed); 165 | return; 166 | } 167 | downLoadSpeedQueue.Enqueue(speed); 168 | } 169 | 170 | /// 171 | /// 上一帧下载大小 172 | /// 173 | private long lastDownSize = 0; 174 | 175 | /// 176 | /// 上一次下载速度变化时间 177 | /// 178 | private long lastTime = 0; 179 | 180 | /// 181 | /// 更新下载进度和下载速度以及执行回调 182 | /// 183 | internal void UpdateProgressAndSpeedCallBack(float deltaTime) 184 | { 185 | //计算下载进度 186 | if (FinishUpdate) 187 | { 188 | SmoothProgress = 100; 189 | } 190 | else 191 | { 192 | SmoothProgress += (Progress - SmoothProgress) * ValueHelper.GetMinValue(deltaTime / 0.1f, 1.0f); 193 | } 194 | progressCallback?.Invoke(SmoothProgress); 195 | 196 | long nowTime = DateTime.Now.Ticks; 197 | long continueTime = nowTime - lastTime; 198 | //下载速度一秒才更新一次 199 | if (continueTime > 10000 * speedRate) 200 | { 201 | //计算下载速度(非UnityWebReq下载), 下载量有改变 202 | if (FinishUpdateSize != lastDownSize) 203 | { 204 | long blockTimeDownSize = FinishUpdateSize - lastDownSize; 205 | lastDownSize = FinishUpdateSize; 206 | if (lastTime != 0) 207 | { 208 | AddSpeedQueue((int)(blockTimeDownSize / ((nowTime - lastTime) / 10000000.0f))); 209 | } 210 | lastTime = nowTime; 211 | } 212 | downLoadSpeedCallback?.Invoke(DownLoadSpeed); 213 | } 214 | } 215 | 216 | /// 217 | /// 获取更新的分包的版本索引 int[本地版本, 远程版本] 218 | /// 219 | public int[] GetVersion(string bundlePackageName) 220 | { 221 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build) 222 | { 223 | AssetLogHelper.LogError("仅Build模式可用获取版本索引"); 224 | return null; 225 | } 226 | if (!PackageToVersion.TryGetValue(bundlePackageName, out int[] versionData)) 227 | { 228 | AssetLogHelper.LogError("获取索引号没有找到分包: " + bundlePackageName); 229 | return null; 230 | } 231 | return versionData; 232 | } 233 | 234 | /// 235 | /// 添加一个更新好的CRC文件信息 236 | /// 237 | internal void AddCRCFileInfo(string bundlePackageName, string fileName, uint crc) 238 | { 239 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build) 240 | { 241 | AssetLogHelper.LogError("AssetLoadMode != Build 不涉及更新"); 242 | return; 243 | } 244 | if (!PackageCRCDictionary.TryGetValue(bundlePackageName, out Dictionary crcDictionary)) 245 | { 246 | AssetLogHelper.LogError("获取索引号没有找到分包: " + bundlePackageName); 247 | return; 248 | } 249 | if (Cancel) 250 | { 251 | return; 252 | } 253 | if (!crcDictionary.ContainsKey(fileName)) 254 | { 255 | crcDictionary.Add(fileName, crc); 256 | } 257 | PackageCRCFile[bundlePackageName].WriteLine(fileName + "|" + crc.ToString() + "|" + UpdateTime); 258 | PackageCRCFile[bundlePackageName].Flush(); 259 | } 260 | 261 | private Action errorCancelCallback; 262 | 263 | /// 264 | /// 下载失败回调 265 | /// 266 | public event Action ErrorCancelCallback 267 | { 268 | add => errorCancelCallback += value; 269 | remove => this.errorCancelCallback -= value; 270 | } 271 | 272 | /// 273 | /// 取消更新 274 | /// 275 | public void CancelUpdate() 276 | { 277 | if (Cancel) 278 | { 279 | return; 280 | } 281 | Cancel = true; 282 | errorCancelCallback?.Invoke(); 283 | DestroySelf(); 284 | } 285 | 286 | private void DestroySelf() 287 | { 288 | foreach (StreamWriter sw in PackageCRCFile.Values) 289 | { 290 | sw.Close(); 291 | sw.Dispose(); 292 | } 293 | PackageCRCFile.Clear(); 294 | AssetComponent.DownLoadAction -= UpdateProgressAndSpeedCallBack; 295 | progressCallback = null; 296 | FinishCallback = null; 297 | downLoadSpeedCallback = null; 298 | errorCancelCallback = null; 299 | } 300 | 301 | /// 302 | /// 分包类型 303 | /// 304 | internal enum PackageType 305 | { 306 | /// 307 | /// 未加密 308 | /// 309 | Normal, 310 | /// 311 | /// 加密 312 | /// 313 | Encrypt, 314 | /// 315 | /// 原始资源 316 | /// 317 | Origin, 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /CoroutineLock/CoroutineLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | public class CoroutineLock : IDisposable 7 | { 8 | private bool _isDispose = false; 9 | private long _key; 10 | private CoroutineLockQueue _coroutineLockQueue; 11 | private ETTask _waitTask; 12 | 13 | internal void Init(long key, CoroutineLockQueue coroutineLockQueue) 14 | { 15 | _isDispose = false; 16 | this._key = key; 17 | this._coroutineLockQueue = coroutineLockQueue; 18 | _waitTask = ETTask.Create(true); 19 | } 20 | 21 | internal void Enable() 22 | { 23 | _waitTask.SetResult(); 24 | } 25 | 26 | internal ETTask Wait() 27 | { 28 | return _waitTask; 29 | } 30 | 31 | 32 | public void Dispose() 33 | { 34 | if (_isDispose) 35 | { 36 | //AssetLogHelper.LogError("协程锁重复释放"); 37 | return; 38 | } 39 | _waitTask = null; 40 | _isDispose = true; 41 | _coroutineLockQueue.CoroutineLockDispose(_key).Coroutine(); 42 | _coroutineLockQueue = null; 43 | CoroutineLockComponent.CoroutineLockQueue.Enqueue(this); 44 | 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /CoroutineLock/CoroutineLockComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | public static class CoroutineLockComponent 7 | { 8 | /// 9 | /// 协程锁类型以及对应的协程锁队列 10 | /// 11 | private static readonly Dictionary CoroutineLockTypeToQueue = new Dictionary(); 12 | 13 | /// 14 | /// 没有用到的CoroutineLock池 15 | /// 16 | internal static readonly Queue CoroutineLockQueue = new Queue(); 17 | 18 | /// 19 | /// 缓存池的池 20 | /// 21 | internal static readonly Queue> CoroutineLockQueuePool = new Queue>(); 22 | 23 | public static async ETTask Wait(CoroutineLockType coroutineLockType, long key) 24 | { 25 | if (!CoroutineLockTypeToQueue.TryGetValue(coroutineLockType, out CoroutineLockQueue coroutineLockQueue)) 26 | { 27 | coroutineLockQueue = new CoroutineLockQueue(coroutineLockType); 28 | CoroutineLockTypeToQueue.Add(coroutineLockType, coroutineLockQueue); 29 | } 30 | //取一个 CoroutineLock 31 | CoroutineLock coroutineLock = coroutineLockQueue.GetCoroutineLock(key); 32 | await coroutineLock.Wait(); 33 | return coroutineLock; 34 | } 35 | 36 | public static void UpDate() 37 | { 38 | for (int i = 0; i < TaskDic.Count; i++) 39 | { 40 | TaskDic[i].SetResult(); 41 | } 42 | TaskDic.Clear(); 43 | } 44 | 45 | private static readonly List TaskDic = new List(); 46 | 47 | /// 48 | /// 等待一个帧循环执行 49 | /// 50 | internal static ETTask WaitTask() 51 | { 52 | ETTask tcs = ETTask.Create(true); 53 | TaskDic.Add(tcs); 54 | return tcs; 55 | } 56 | 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /CoroutineLock/CoroutineLockQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | /// 7 | /// 每个协程锁类型与其对应的队列 8 | /// 9 | internal class CoroutineLockQueue 10 | { 11 | private CoroutineLockType _coroutineLockType; 12 | /// 13 | /// 每个key的以及其对应的队列 14 | /// 15 | private readonly Dictionary> _coroutineLockKeyToQueue = new Dictionary>(); 16 | 17 | /// 18 | /// 可以瞬间完成的锁循环的数量(与设备栈大小有关) 19 | /// 20 | private const int LoopCount = 200; 21 | 22 | /// 23 | /// 递归循环次数 24 | /// 25 | private readonly Dictionary _coroutineLockKeyToLoopCount = new Dictionary(); 26 | 27 | internal CoroutineLockQueue(CoroutineLockType coroutineLockType) 28 | { 29 | this._coroutineLockType = coroutineLockType; 30 | } 31 | 32 | internal async ETTask CoroutineLockDispose(long key) 33 | { 34 | Queue keyToQueue = _coroutineLockKeyToQueue[key]; 35 | if (keyToQueue.Count > 0) 36 | { 37 | if (_coroutineLockKeyToLoopCount[key] > 0) 38 | { 39 | _coroutineLockKeyToLoopCount[key]--; 40 | keyToQueue.Dequeue().Enable(); 41 | return; 42 | } 43 | _coroutineLockKeyToLoopCount[key] = LoopCount; 44 | await CoroutineLockComponent.WaitTask(); 45 | keyToQueue.Dequeue().Enable(); 46 | return; 47 | } 48 | keyToQueue.Clear(); 49 | CoroutineLockComponent.CoroutineLockQueuePool.Enqueue(keyToQueue); 50 | _coroutineLockKeyToQueue.Remove(key); 51 | _coroutineLockKeyToLoopCount.Remove(key); 52 | } 53 | 54 | /// 55 | /// 获取一个锁头 56 | /// 57 | internal CoroutineLock GetCoroutineLock(long key) 58 | { 59 | CoroutineLock coroutineLock; 60 | if (CoroutineLockComponent.CoroutineLockQueue.Count > 0) 61 | { 62 | coroutineLock = CoroutineLockComponent.CoroutineLockQueue.Dequeue(); 63 | } 64 | else 65 | { 66 | coroutineLock = new CoroutineLock(); 67 | } 68 | coroutineLock.Init(key, this); 69 | if (!_coroutineLockKeyToQueue.ContainsKey(key)) 70 | { 71 | Queue coroutineLockQueue; 72 | if (CoroutineLockComponent.CoroutineLockQueuePool.Count > 0) 73 | { 74 | coroutineLockQueue = CoroutineLockComponent.CoroutineLockQueuePool.Dequeue(); 75 | } 76 | else 77 | { 78 | coroutineLockQueue = new Queue(); 79 | } 80 | _coroutineLockKeyToLoopCount.Add(key, LoopCount); 81 | _coroutineLockKeyToQueue.Add(key, coroutineLockQueue); 82 | coroutineLock.Enable(); 83 | } 84 | else 85 | { 86 | _coroutineLockKeyToQueue[key].Enqueue(coroutineLock); 87 | } 88 | return coroutineLock; 89 | } 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /CoroutineLock/CoroutineLockType.cs: -------------------------------------------------------------------------------- 1 | namespace BM 2 | { 3 | public enum CoroutineLockType 4 | { 5 | /// 6 | /// BundleMaster资源加载用的标识符 7 | /// 8 | BundleMaster = 0, 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /CoroutineLock/LoadPathConvertHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BM 4 | { 5 | /// 6 | /// 负责将加载路径转化为唯一的long key 7 | /// 8 | public class LoadPathConvertHelper 9 | { 10 | /// 11 | /// 初始值 12 | /// 13 | private static long _originKey = 0; 14 | 15 | private static readonly Dictionary LoadPathToKey = new Dictionary(); 16 | 17 | public static long LoadPathConvert(string loadPath) 18 | { 19 | if (LoadPathToKey.TryGetValue(loadPath, out long key)) 20 | { 21 | return key; 22 | } 23 | _originKey++; 24 | key = _originKey; 25 | LoadPathToKey.Add(loadPath, key); 26 | return key; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/AssetLoadTable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEngine.Serialization; 6 | 7 | namespace BM 8 | { 9 | /// 10 | /// 用于配置所有分包的构建信息 11 | /// 12 | public class AssetLoadTable : ScriptableObject 13 | { 14 | [Header("相对构建路径文件夹名称")] 15 | [Tooltip("构建的资源的相对路径(Assets同级目录下的路径)")] 16 | public string BundlePath = "BuildBundles"; 17 | 18 | [Header("是否启用绝对构建路径")] 19 | [Tooltip("启用后使用绝对路径")] 20 | public bool EnableRelativePath = false; 21 | 22 | [Header("绝对路径")] 23 | [Tooltip("自己填的绝对路径,替换掉Assets同级路径")] 24 | public string RelativePath = ""; 25 | 26 | [Header("加密资源文件夹名称")] 27 | [Tooltip("加密的资源所在的和普通资源同级路径的文件夹名称")] 28 | public string EncryptPathFolder = "EncryptAssets"; 29 | 30 | [Header("初始场景")] 31 | [Tooltip("最后不打Bundle直接打进包体里的场景(Scene In Build 里填的场景)")] 32 | public List InitScene = new List(); 33 | 34 | [Header("是否生成路径字段代码脚本")] 35 | [Tooltip("字段匹配路径")] 36 | public bool GeneratePathCode = false; 37 | 38 | [Header("相对构建路径文件夹名称")] 39 | [Tooltip("构建的资源的相对路径(Assets同级目录下的路径)")] 40 | public string GenerateCodeScriptPath = ""; 41 | 42 | /// 43 | /// 返回打包路径 44 | /// 45 | public string BuildBundlePath 46 | { 47 | get 48 | { 49 | if (EnableRelativePath) 50 | { 51 | return Path.Combine(RelativePath, BundlePath); 52 | } 53 | string path = Path.Combine(Application.dataPath + "/../", BundlePath); 54 | DirectoryInfo info; 55 | if (!Directory.Exists(path)) 56 | { 57 | info = Directory.CreateDirectory(path); 58 | } 59 | else 60 | { 61 | info = new DirectoryInfo(path); 62 | } 63 | return info.FullName; 64 | } 65 | } 66 | 67 | [FormerlySerializedAs("AssetsLoadSettings")] 68 | [Header("所有分包配置信息")] 69 | [Tooltip("每一个分包的配置信息")] 70 | public List AssetsSettings = new List(); 71 | } 72 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/AssetsLoadSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | /// 8 | /// 用于配置单个Bundle包的构建信息 9 | /// 10 | public class AssetsLoadSetting : AssetsSetting 11 | { 12 | [Header("分包名字")] 13 | [Tooltip("当前分包的包名(建议英文)")] public string BuildName; 14 | 15 | [Header("版本索引")] 16 | [Tooltip("表示当前Bundle的索引")] public int BuildIndex; 17 | 18 | [Header("AssetBundle的后缀")] 19 | [Tooltip("AssetBundle资源的的后缀名(如'bundle')")] public string BundleVariant; 20 | 21 | [Header("是否启用Hash名")] 22 | [Tooltip("是否使用Hash名替换Bundle名称")] public bool NameByHash; 23 | 24 | [Header("构建选项")] 25 | public BuildAssetBundleOptions BuildAssetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle; 26 | 27 | #if !(Nintendo_Switch || BMWebGL) 28 | [Header("是否加密资源")] 29 | [Tooltip("加密启用后会多一步异或操作")] public bool EncryptAssets; 30 | 31 | [Header("加密密钥")] 32 | [Tooltip("进行异或操作的密钥")] public string SecretKey; 33 | #endif 34 | 35 | [Header("资源路径")] 36 | [Tooltip("需要打包的资源所在的路径(不需要包含依赖, 只包括需要主动加载的资源)")] 37 | public List AssetPath = new List(); 38 | 39 | [Header("一组资源路径")] 40 | [Tooltip("资源颗粒控制")] 41 | public List AssetGroupPaths = new List(); 42 | 43 | [Header("场景资源")] 44 | [Tooltip("需要通过Bundle加载的场景")] 45 | public List Scene = new List(); 46 | 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/AssetsOriginSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BM 6 | { 7 | /// 8 | /// 用于配置原生资源的更新 9 | /// 10 | public class AssetsOriginSetting : AssetsSetting 11 | { 12 | [Header("原生资源分包名字")] 13 | [Tooltip("原生资源的分包名(建议英文)")] public string BuildName; 14 | 15 | [Header("版本索引")] 16 | [Tooltip("表示当前原生的索引")] public int BuildIndex; 17 | 18 | [Header("资源路径")] 19 | [Tooltip("需要打包的资源所在的路径(不需要包含依赖, 只包括需要主动加载的资源)")] 20 | public string OriginFilePath = ""; 21 | } 22 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/AssetsSetting.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BM 4 | { 5 | public class AssetsSetting : ScriptableObject 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/BuildAssetsTools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using Directory = UnityEngine.Windows.Directory; 8 | 9 | namespace BM 10 | { 11 | public static class BuildAssetsTools 12 | { 13 | /// 14 | /// 获取一个目录下所有的子文件 15 | /// 16 | public static void GetChildFiles(string basePath, HashSet files) 17 | { 18 | DirectoryInfo basefolder = new DirectoryInfo(basePath); 19 | FileInfo[] basefil = basefolder.GetFiles(); 20 | for (int i = 0; i < basefil.Length; i++) 21 | { 22 | 23 | if (CantLoadType(basefil[i].FullName)) 24 | { 25 | files.Add(basePath + "/" + basefil[i].Name); 26 | } 27 | } 28 | Er(basePath); 29 | void Er(string subPath) 30 | { 31 | string[] subfolders = AssetDatabase.GetSubFolders(subPath); 32 | for (int i = 0; i < subfolders.Length; i++) 33 | { 34 | DirectoryInfo subfolder = new DirectoryInfo(subfolders[i]); 35 | FileInfo[] fil = subfolder.GetFiles(); 36 | for (int j = 0; j < fil.Length; j++) 37 | { 38 | 39 | if (CantLoadType(fil[j].FullName)) 40 | { 41 | files.Add(subfolders[i] + "/" + fil[j].Name); 42 | } 43 | } 44 | Er(subfolders[i]); 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// 获取一个文件目录下的所有资源以及路径 51 | /// 52 | public static void GetOriginsPath(string originPath, HashSet files, HashSet dirs) 53 | { 54 | DirectoryInfo buildBundlePath = new DirectoryInfo(originPath); 55 | FileSystemInfo[] fileSystemInfos = buildBundlePath.GetFileSystemInfos(); 56 | foreach (FileSystemInfo fileSystemInfo in fileSystemInfos) 57 | { 58 | if (fileSystemInfo is DirectoryInfo) 59 | { 60 | dirs.Add(fileSystemInfo.FullName); 61 | GetOriginsPath(fileSystemInfo.FullName, files, dirs); 62 | } 63 | else 64 | { 65 | files.Add(fileSystemInfo.FullName); 66 | } 67 | } 68 | } 69 | 70 | /// 71 | /// 创建加密的AssetBundle 72 | /// 73 | public static void CreateEncryptAssets(string bundlePackagePath, string encryptAssetPath, AssetBundleManifest manifest, string secretKey) 74 | { 75 | string[] assetBundles = manifest.GetAllAssetBundles(); 76 | foreach (string assetBundle in assetBundles) 77 | { 78 | string bundlePath = Path.Combine(bundlePackagePath, assetBundle); 79 | if (!Directory.Exists(encryptAssetPath)) 80 | { 81 | Directory.CreateDirectory(encryptAssetPath); 82 | } 83 | using (FileStream fs = new FileStream(Path.Combine(encryptAssetPath, assetBundle), FileMode.OpenOrCreate)) 84 | { 85 | byte[] encryptBytes = CreateEncryptData(bundlePath, secretKey); 86 | fs.Write(encryptBytes, 0, encryptBytes.Length); 87 | } 88 | } 89 | } 90 | 91 | /// 92 | /// 创建加密过的数据 93 | /// 94 | private static byte[] CreateEncryptData(string filePath, string secretKey) 95 | { 96 | byte[] encryptData; 97 | char[] key = secretKey.ToCharArray(); 98 | using (FileStream fs = new FileStream(filePath, FileMode.Open)) 99 | { 100 | encryptData = new byte[fs.Length]; 101 | fs.Read(encryptData, 0, encryptData.Length); 102 | for (int i = 0; i < encryptData.Length; i++) 103 | { 104 | encryptData[i] = (byte)(encryptData[i] ^ key[i % key.Length]); 105 | } 106 | } 107 | return encryptData; 108 | } 109 | 110 | /// 111 | /// 需要忽略加载的格式 112 | /// 113 | public static bool CantLoadType(string fileFullName) 114 | { 115 | if (!CantLoadFile(fileFullName)) 116 | { 117 | return false; 118 | } 119 | string suffix = Path.GetExtension(fileFullName); 120 | switch (suffix) 121 | { 122 | case ".dll": 123 | return false; 124 | case ".cs": 125 | return false; 126 | case ".meta": 127 | return false; 128 | case ".js": 129 | return false; 130 | case ".boo": 131 | return false; 132 | } 133 | return true; 134 | } 135 | 136 | /// 137 | /// 不能加载的文件 138 | /// 139 | public static bool CantLoadFile(string fillPathOrName) 140 | { 141 | if (fillPathOrName.Contains("LightingData.asset")) 142 | { 143 | return false; 144 | } 145 | // if (fillPathOrName.Contains("Lightmap-")) 146 | // { 147 | // return false; 148 | // } 149 | // if (fillPathOrName.Contains("ReflectionProbe-")) 150 | // { 151 | // return false; 152 | // } 153 | return true; 154 | } 155 | 156 | /// 157 | /// 是Shader资源 158 | /// 159 | public static bool IsShaderAsset(string fileFullName) 160 | { 161 | string suffix = Path.GetExtension(fileFullName); 162 | switch (suffix) 163 | { 164 | case ".shader": 165 | return true; 166 | case ".shadervariants": 167 | return true; 168 | } 169 | return false; 170 | } 171 | 172 | public static bool IsGroupAsset(string fileFullName, AssetsLoadSetting assetsLoadSetting) 173 | { 174 | foreach (string assetGroupPath in assetsLoadSetting.AssetGroupPaths) 175 | { 176 | if (fileFullName.Contains(assetGroupPath)) 177 | { 178 | return true; 179 | } 180 | } 181 | return false; 182 | } 183 | 184 | public static string GetGroupAssetPath(string fileFullName, AssetsLoadSetting assetsLoadSetting) 185 | { 186 | foreach (string assetGroupPath in assetsLoadSetting.AssetGroupPaths) 187 | { 188 | if (fileFullName.Contains(assetGroupPath)) 189 | { 190 | return assetGroupPath; 191 | } 192 | } 193 | return null; 194 | } 195 | 196 | /// 197 | /// 是否生成路径字段代码脚本 198 | /// 199 | public static void GeneratePathCode(HashSet allAssetPaths, string scriptFilePaths) 200 | { 201 | using (StreamWriter sw = new StreamWriter(Path.Combine(Application.dataPath, scriptFilePaths, "BPath.cs"))) 202 | { 203 | StringBuilder sb = new StringBuilder(); 204 | sb.Append("// ReSharper disable All\n"); 205 | sb.Append("namespace BM\n"); 206 | sb.Append("{\n"); 207 | sb.Append("\tpublic class BPath\n"); 208 | sb.Append("\t{\n"); 209 | foreach (string assetPath in allAssetPaths) 210 | { 211 | string name = assetPath.Replace("/", "_"); 212 | name = name.Replace(".", "__"); 213 | sb.Append("\t\tpublic const string " + name + " = \"" + assetPath + "\";\n"); 214 | } 215 | sb.Append("\t}\n"); 216 | sb.Append("}"); 217 | sw.WriteLine(sb.ToString()); 218 | } 219 | } 220 | 221 | /// 222 | /// 得到一个路径下文件的大小 223 | /// 224 | public static long GetFileLength(string filePath) 225 | { 226 | long fileLength; 227 | using (FileStream fs = new FileStream(filePath, FileMode.Open)) 228 | { 229 | fileLength = fs.Length; 230 | } 231 | return fileLength; 232 | } 233 | 234 | public static void DeleteDir(string srcPath) 235 | { 236 | try 237 | { 238 | DirectoryInfo buildBundlePath = new DirectoryInfo(srcPath); 239 | FileSystemInfo[] fileSystemInfos = buildBundlePath.GetFileSystemInfos(); 240 | foreach (FileSystemInfo fileSystemInfo in fileSystemInfos) 241 | { 242 | if (fileSystemInfo is DirectoryInfo) 243 | { 244 | DirectoryInfo subDir = new DirectoryInfo(fileSystemInfo.FullName); 245 | subDir.Delete(true); 246 | } 247 | else 248 | { 249 | File.Delete(fileSystemInfo.FullName); 250 | } 251 | } 252 | } 253 | catch (Exception e) 254 | { 255 | AssetLogHelper.LogError(e.ToString()); 256 | throw; 257 | } 258 | } 259 | } 260 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/BundleMasterInterface/BundleMasterOriginLoadWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace BM 9 | { 10 | public partial class BundleMasterWindow 11 | { 12 | private void OriginLoadSettingRender() 13 | { 14 | GUILayout.BeginHorizontal(); 15 | GUILayout.Label("--- <选中的原生资源配置信息> --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------", GUILayout.ExpandWidth(false)); 16 | GUILayout.EndHorizontal(); 17 | GUILayout.BeginHorizontal(); 18 | GUILayout.Label("当前选择的分包配置信息文件: ", GUILayout.ExpandWidth(false)); 19 | AssetsOriginSetting selectAssetsOriginSetting = _selectAssetsSetting as AssetsOriginSetting; 20 | selectAssetsOriginSetting = (AssetsOriginSetting)EditorGUILayout.ObjectField(selectAssetsOriginSetting, typeof(AssetsOriginSetting), true, GUILayout.Width(_w / 3),GUILayout.ExpandHeight(false)); 21 | bool noLoadSetting = selectAssetsOriginSetting == null; 22 | EditorGUI.BeginDisabledGroup(noLoadSetting); 23 | GUILayout.Label("分包名: ", GUILayout.Width(_w / 20), GUILayout.ExpandWidth(false)); 24 | string buildName = ""; 25 | if (!noLoadSetting) 26 | { 27 | buildName = selectAssetsOriginSetting.BuildName; 28 | } 29 | buildName = EditorGUILayout.TextField(buildName, GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false)); 30 | if (!noLoadSetting) 31 | { 32 | if (!string.Equals(selectAssetsOriginSetting.BuildName, buildName, StringComparison.Ordinal)) 33 | { 34 | selectAssetsOriginSetting.BuildName = buildName; 35 | needFlush = true; 36 | } 37 | } 38 | GUILayout.Label("版本索引: ", GUILayout.Width(_w / 17), GUILayout.ExpandWidth(false)); 39 | int buildIndex = 0; 40 | if (!noLoadSetting) 41 | { 42 | buildIndex = selectAssetsOriginSetting.BuildIndex; 43 | } 44 | buildIndex = EditorGUILayout.IntField(buildIndex, GUILayout.Width(_w / 20), GUILayout.ExpandWidth(false)); 45 | if (!noLoadSetting) 46 | { 47 | if (buildIndex != selectAssetsOriginSetting.BuildIndex) 48 | { 49 | selectAssetsOriginSetting.BuildIndex = buildIndex; 50 | needFlush = true; 51 | } 52 | } 53 | GUI.color = new Color(0.9921569F, 0.2745098F, 0.282353F); 54 | if (GUILayout.Button("删除当前选择的分包配置")) 55 | { 56 | _assetLoadTable.AssetsSettings.Remove(selectAssetsOriginSetting); 57 | AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(selectAssetsOriginSetting)); 58 | _viewSub = false; 59 | needFlush = true; 60 | } 61 | GUI.color = Color.white; 62 | GUILayout.EndHorizontal(); 63 | 64 | GUILayout.Space(_h / 15); 65 | 66 | GUILayout.BeginHorizontal(); 67 | string originAssetPath = selectAssetsOriginSetting.OriginFilePath; 68 | originAssetPath = EditorGUILayout.TextField(originAssetPath); 69 | if (!string.Equals(selectAssetsOriginSetting.OriginFilePath, originAssetPath, StringComparison.Ordinal)) 70 | { 71 | selectAssetsOriginSetting.OriginFilePath = originAssetPath; 72 | needFlush = true; 73 | } 74 | GUILayout.EndHorizontal(); 75 | 76 | GUILayout.Space(_h / 8); 77 | 78 | GUILayout.BeginHorizontal(); 79 | GUI.color = new Color(0.9921569F, 0.7960784F, 0.509804F); 80 | if (GUILayout.Button("分包编辑完成", GUILayout.Height(_h / 10))) 81 | { 82 | _viewSub = false; 83 | } 84 | GUI.color = Color.white; 85 | GUILayout.EndHorizontal(); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/BundleMasterInterface/BundleMasterWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace BM 7 | { 8 | [SuppressMessage("ReSharper", "PossibleLossOfFraction")] 9 | public partial class BundleMasterWindow : EditorWindow 10 | { 11 | private static BundleMasterWindow _instance = null; 12 | 13 | /// 14 | /// 自动刷新配置界面 15 | /// 16 | private static bool _autoFlush = true; 17 | 18 | /// 19 | /// 需要刷新 20 | /// 21 | private bool needFlush = false; 22 | 23 | /// 24 | /// 运行时配置文件 25 | /// 26 | private static BundleMasterRuntimeConfig _bundleMasterRuntimeConfig = null; 27 | 28 | private static bool _runtimeConfigLoad = false; 29 | 30 | private static int _w = 960; 31 | private static int _h = 540; 32 | 33 | /// 34 | /// 运行时配置文件的路径 35 | /// 36 | public static string RuntimeConfigPath = "Assets/Resources/BMConfig.asset"; 37 | 38 | /// 39 | /// 分包文件资源索引配置 40 | /// 41 | public static string AssetLoadTablePath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetLoadTable.asset"; 42 | 43 | /// 44 | /// 分包配置信息 45 | /// 46 | public static string AssetsLoadSettingPath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetsLoadSetting"; 47 | 48 | /// 49 | /// 原生资源包配置信息 50 | /// 51 | public static string AssetsOriginSettingPath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetsOriginSetting"; 52 | 53 | private static AssetLoadTable _assetLoadTable = null; 54 | 55 | /// 56 | /// 选中查看的分包信息 57 | /// 58 | private static AssetsSetting _selectAssetsSetting = null; 59 | 60 | /// 61 | /// 是否查看子页面 62 | /// 63 | private static bool _viewSub = false; 64 | 65 | [MenuItem("Tools/BuildAsset/打开配置界面")] 66 | public static void Init() 67 | { 68 | Open(true); 69 | } 70 | 71 | Vector2 scrollScenePos = Vector2.zero; 72 | Vector2 scrollPos = Vector2.zero; 73 | Vector2 scrollBundleScenePos = Vector2.zero; 74 | Vector2 scrollPathPos = Vector2.zero; 75 | 76 | private static void Open(bool focus) 77 | { 78 | if (_instance != null) 79 | { 80 | return; 81 | } 82 | _viewSub = false; 83 | _instance = (BundleMasterWindow)EditorWindow.GetWindow(typeof(BundleMasterWindow), true, "BundleMasterEditor", focus); 84 | //_instance.position = new Rect(_w / 2, _h / 2, _w, _h); 85 | _instance.maxSize = new Vector2(_w, _h); 86 | _instance.minSize = new Vector2(_w, _h); 87 | //加载配置文件 88 | _bundleMasterRuntimeConfig = AssetDatabase.LoadAssetAtPath(RuntimeConfigPath); 89 | _runtimeConfigLoad = false; 90 | if (_bundleMasterRuntimeConfig != null) 91 | { 92 | _runtimeConfigLoad = true; 93 | } 94 | } 95 | public void OnGUI() 96 | { 97 | Open(false); 98 | if (!_runtimeConfigLoad) 99 | { 100 | GUILayout.BeginArea(new Rect(_w / 4, _h / 8, _w / 2, _h / 4)); 101 | if (GUILayout.Button("创建运行时配置文件", GUILayout.Width(_w / 2), GUILayout.Height(_h / 4), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false))) 102 | { 103 | _bundleMasterRuntimeConfig = ScriptableObject.CreateInstance(); 104 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Develop; 105 | _bundleMasterRuntimeConfig.MaxDownLoadCount = 8; 106 | _bundleMasterRuntimeConfig.ReDownLoadCount = 3; 107 | if (!Directory.Exists(Path.Combine(Application.dataPath, "Resources"))) 108 | { 109 | Directory.CreateDirectory(Path.Combine(Application.dataPath, "Resources")); 110 | } 111 | AssetDatabase.CreateAsset(_bundleMasterRuntimeConfig, RuntimeConfigPath); 112 | AssetDatabase.Refresh(); 113 | _runtimeConfigLoad = true; 114 | } 115 | GUILayout.EndArea(); 116 | return; 117 | } 118 | 119 | GUILayout.BeginHorizontal(); 120 | GUILayout.Label("当前资源加载模式: \t" + _bundleMasterRuntimeConfig.AssetLoadMode, GUILayout.Width(_w / 4), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(false)); 121 | if (GUILayout.Button("开发模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true))) 122 | { 123 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Develop; 124 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode); 125 | needFlush = true; 126 | } 127 | if (GUILayout.Button("本地模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true))) 128 | { 129 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Local; 130 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode); 131 | needFlush = true; 132 | } 133 | if (GUILayout.Button("构建模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true))) 134 | { 135 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Build; 136 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode); 137 | needFlush = true; 138 | } 139 | GUILayout.EndHorizontal(); 140 | GUILayout.BeginHorizontal(GUILayout.ExpandHeight(false)); 141 | GUILayout.Label("最大同时下载资源数: ", GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false)); 142 | int maxDownLoadCount = _bundleMasterRuntimeConfig.MaxDownLoadCount; 143 | maxDownLoadCount = EditorGUILayout.IntField(maxDownLoadCount, GUILayout.Width(_w / 16), GUILayout.ExpandWidth(false)); 144 | if (_bundleMasterRuntimeConfig.MaxDownLoadCount != maxDownLoadCount) 145 | { 146 | _bundleMasterRuntimeConfig.MaxDownLoadCount = maxDownLoadCount; 147 | needFlush = true; 148 | } 149 | GUILayout.Label("下载失败重试数: ", GUILayout.Width(_w / 10), GUILayout.ExpandWidth(false)); 150 | int reDownLoadCount = _bundleMasterRuntimeConfig.ReDownLoadCount; 151 | reDownLoadCount = EditorGUILayout.IntField(reDownLoadCount, GUILayout.Width(_w / 16), GUILayout.ExpandWidth(false)); 152 | if (_bundleMasterRuntimeConfig.ReDownLoadCount != reDownLoadCount) 153 | { 154 | _bundleMasterRuntimeConfig.ReDownLoadCount = reDownLoadCount; 155 | needFlush = true; 156 | } 157 | _autoFlush = GUILayout.Toggle(_autoFlush, "是否自动应用更新配置界面数据"); 158 | if (!_autoFlush) 159 | { 160 | if (GUILayout.Button("应用更新")) 161 | { 162 | needFlush = false; 163 | Flush(); 164 | } 165 | } 166 | GUILayout.EndHorizontal(); 167 | GUILayout.BeginHorizontal(); 168 | GUILayout.Label("--- <构建AssetBundle配置> ----------------------------------------------------------------------------------------------------------------------------------------------------------------", GUILayout.ExpandWidth(false)); 169 | GUILayout.EndHorizontal(); 170 | GUILayout.BeginHorizontal(); 171 | GUILayout.Label("分包配置总索引文件: ", GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false)); 172 | _assetLoadTable = (AssetLoadTable)EditorGUILayout.ObjectField(_assetLoadTable, typeof(AssetLoadTable), true, GUILayout.Width(_w / 3), GUILayout.ExpandWidth(false)); 173 | bool noTable = _assetLoadTable == null; 174 | if (GUILayout.Button("查找或创建分包配置索引文件", GUILayout.Width(_w / 5.5f), GUILayout.ExpandWidth(true))) 175 | { 176 | _assetLoadTable = AssetDatabase.LoadAssetAtPath(AssetLoadTablePath); 177 | if (_assetLoadTable == null) 178 | { 179 | _assetLoadTable = ScriptableObject.CreateInstance(); 180 | AssetDatabase.CreateAsset(_assetLoadTable, BundleMasterWindow.AssetLoadTablePath); 181 | needFlush = true; 182 | } 183 | } 184 | EditorGUI.BeginDisabledGroup(noTable); 185 | GUI.color = new Color(0.654902F, 0.9921569F, 0.2784314F); 186 | if (GUILayout.Button("添加一个分包配置", GUILayout.Width(_w / 6), GUILayout.ExpandWidth(true))) 187 | { 188 | int index = 0; 189 | while (true) 190 | { 191 | AssetsLoadSetting assetLoadTable = AssetDatabase.LoadAssetAtPath(AssetsLoadSettingPath + "_" + index + ".asset"); 192 | if (assetLoadTable == null) 193 | { 194 | AssetDatabase.CreateAsset(ScriptableObject.CreateInstance(), AssetsLoadSettingPath + "_" + index + ".asset"); 195 | break; 196 | } 197 | else 198 | { 199 | index++; 200 | } 201 | } 202 | needFlush = true; 203 | } 204 | GUI.color = Color.white; 205 | GUI.color = new Color(0.9921569F, 0.7960784F, 0.509804F); 206 | if (GUILayout.Button("添加一个原生资源包配置", GUILayout.Width(_w / 6), GUILayout.ExpandWidth(true))) 207 | { 208 | int index = 0; 209 | while (true) 210 | { 211 | AssetsOriginSetting assetsOriginSetting = AssetDatabase.LoadAssetAtPath(AssetsOriginSettingPath + "_" + index + ".asset"); 212 | if (assetsOriginSetting == null) 213 | { 214 | AssetDatabase.CreateAsset(ScriptableObject.CreateInstance(), AssetsOriginSettingPath + "_" + index + ".asset"); 215 | break; 216 | } 217 | else 218 | { 219 | index++; 220 | } 221 | } 222 | needFlush = true; 223 | } 224 | GUI.color = Color.white; 225 | GUILayout.EndHorizontal(); 226 | 227 | if (!_viewSub) 228 | { 229 | AssetsLoadMainRender(noTable); 230 | } 231 | else 232 | { 233 | if (_selectAssetsSetting is AssetsLoadSetting) 234 | { 235 | AssetsLoadSettingRender(); 236 | } 237 | else 238 | { 239 | OriginLoadSettingRender(); 240 | } 241 | } 242 | 243 | if (needFlush && _autoFlush) 244 | { 245 | needFlush = false; 246 | Flush(); 247 | } 248 | } 249 | 250 | public void OnDestroy() 251 | { 252 | _instance = null; 253 | } 254 | } 255 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/BundleUsefulTool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace BM 7 | { 8 | /// 9 | /// 一些打包会用到的实用的功能 10 | /// 11 | public class BundleUsefulTool : EditorWindow 12 | { 13 | [MenuItem("Tools/BuildAsset/Copy资源到StreamingAssets")] 14 | public static void CopyToStreamingAssets() 15 | { 16 | if (!Directory.Exists(Application.streamingAssetsPath)) 17 | { 18 | Directory.CreateDirectory(Application.streamingAssetsPath); 19 | } 20 | BuildAssetsTools.DeleteDir(Application.streamingAssetsPath); 21 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath); 22 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings) 23 | { 24 | if (!(assetsSetting is AssetsLoadSetting assetsLoadSetting)) 25 | { 26 | continue; 27 | } 28 | string assetPathFolder; 29 | #if (Nintendo_Switch || BMWebGL) 30 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsLoadSetting.BuildName); 31 | #else 32 | if (assetsLoadSetting.EncryptAssets) 33 | { 34 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath + "/../", assetLoadTable.EncryptPathFolder, assetsLoadSetting.BuildName); 35 | } 36 | else 37 | { 38 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsLoadSetting.BuildName); 39 | } 40 | #endif 41 | string directoryPath = Path.Combine(Application.streamingAssetsPath, assetsLoadSetting.BuildName); 42 | if (!Directory.Exists(directoryPath)) 43 | { 44 | Directory.CreateDirectory(directoryPath); 45 | } 46 | DirectoryInfo subBundlePath = new DirectoryInfo(assetPathFolder); 47 | FileInfo[] fileInfos = subBundlePath.GetFiles(); 48 | foreach (FileInfo fileInfo in fileInfos) 49 | { 50 | if (fileInfo.DirectoryName == null) 51 | { 52 | AssetLogHelper.LogError("找不到文件的路径: " + fileInfo.Name); 53 | continue; 54 | } 55 | string filePath = Path.Combine(fileInfo.DirectoryName, fileInfo.Name); 56 | string suffix = Path.GetExtension(filePath); 57 | if ((!fileInfo.Name.StartsWith("shader_") && string.IsNullOrWhiteSpace(suffix)) || suffix == ".manifest") 58 | { 59 | continue; 60 | } 61 | File.Copy(filePath, Path.Combine(directoryPath, fileInfo.Name)); 62 | } 63 | } 64 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings) 65 | { 66 | if (!(assetsSetting is AssetsOriginSetting assetsOriginSetting)) 67 | { 68 | continue; 69 | } 70 | string assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsOriginSetting.BuildName); 71 | string directoryPath = Path.Combine(Application.streamingAssetsPath, assetsOriginSetting.BuildName); 72 | if (!Directory.Exists(directoryPath)) 73 | { 74 | Directory.CreateDirectory(directoryPath); 75 | } 76 | //获取所有资源目录 77 | HashSet files = new HashSet(); 78 | HashSet dirs = new HashSet(); 79 | BuildAssetsTools.GetOriginsPath(assetPathFolder, files, dirs); 80 | //Copy资源 81 | foreach (string dir in dirs) 82 | { 83 | Directory.CreateDirectory(dir.Replace(assetPathFolder, directoryPath)); 84 | } 85 | foreach (string file in files) 86 | { 87 | File.Copy(file, file.Replace(assetPathFolder, directoryPath), true); 88 | } 89 | } 90 | AssetDatabase.Refresh(); 91 | AssetLogHelper.Log("已将资源复制到StreamingAssets"); 92 | } 93 | 94 | [MenuItem("Tools/BuildAsset/实用工具/清空热更目录下的文件")] 95 | public static void ClearHotfixPathPath() 96 | { 97 | BuildAssetsTools.DeleteDir(AssetComponentConfig.HotfixPath); 98 | AssetDatabase.Refresh(); 99 | } 100 | 101 | [MenuItem("Tools/BuildAsset/实用工具/清空本地目录下的文件")] 102 | public static void ClearLocalBundlePath() 103 | { 104 | BuildAssetsTools.DeleteDir(AssetComponentConfig.LocalBundlePath); 105 | AssetDatabase.Refresh(); 106 | } 107 | 108 | [MenuItem("Tools/BuildAsset/实用工具/清空打包目录下的文件")] 109 | public static void ClearLocalBuildPath() 110 | { 111 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath); 112 | BuildAssetsTools.DeleteDir(assetLoadTable.BundlePath); 113 | } 114 | 115 | [MenuItem("Tools/BuildAsset/实用工具/清空加密目录下的文件")] 116 | public static void ClearLocalEncryptPath() 117 | { 118 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath); 119 | BuildAssetsTools.DeleteDir(assetLoadTable.EncryptPathFolder); 120 | } 121 | } 122 | 123 | 124 | } -------------------------------------------------------------------------------- /Editor/BundleMasterEditor/DevelopSceneChange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEditor; 4 | 5 | namespace BM 6 | { 7 | public static class DevelopSceneChange 8 | { 9 | /// 10 | /// 用于检测在Develop模式下将场景加入BuildSettings 11 | /// 12 | public static void CheckSceneChange(AssetLoadMode assetLoadMode) 13 | { 14 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath); 15 | List assetsLoadSettings = new List(); 16 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings) 17 | { 18 | if (assetsSetting is AssetsLoadSetting) 19 | { 20 | assetsLoadSettings.Add(assetsSetting as AssetsLoadSetting); 21 | } 22 | } 23 | Dictionary editorBuildSettingsScenes = new Dictionary(); 24 | for (int i = 0; i < assetLoadTable.InitScene.Count; i++) 25 | { 26 | string scenePath = AssetDatabase.GetAssetPath(assetLoadTable.InitScene[i]); 27 | if (!editorBuildSettingsScenes.ContainsKey(scenePath)) 28 | { 29 | editorBuildSettingsScenes.Add(scenePath, new EditorBuildSettingsScene(scenePath, true)); 30 | } 31 | } 32 | if (assetLoadMode == AssetLoadMode.Develop) 33 | { 34 | foreach (AssetsLoadSetting assetsLoadSetting in assetsLoadSettings) 35 | { 36 | if (assetsLoadSetting == null) 37 | { 38 | continue; 39 | } 40 | foreach (SceneAsset sceneAsset in assetsLoadSetting.Scene) 41 | { 42 | if (sceneAsset == null) 43 | { 44 | continue; 45 | } 46 | string scenePath = AssetDatabase.GetAssetPath(sceneAsset); 47 | if (!editorBuildSettingsScenes.ContainsKey(scenePath)) 48 | { 49 | editorBuildSettingsScenes.Add(scenePath, new EditorBuildSettingsScene(scenePath, true)); 50 | } 51 | } 52 | } 53 | } 54 | EditorBuildSettings.scenes = editorBuildSettingsScenes.Values.ToArray(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/BundleMaster.LMTD.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BundleMaster.LMTD", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Android", 7 | "Editor", 8 | "iOS", 9 | "LinuxStandalone64", 10 | "macOSStandalone", 11 | "Switch", 12 | "WindowsStandalone32", 13 | "WindowsStandalone64" 14 | ], 15 | "excludePlatforms": [], 16 | "allowUnsafeCode": false, 17 | "overrideReferences": false, 18 | "precompiledReferences": [], 19 | "autoReferenced": true, 20 | "defineConstraints": [], 21 | "versionDefines": [], 22 | "noEngineReferences": false 23 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/BundleMaster.LMTD.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3bc86b810ed97834e8d5d8df82632a59 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/ILiteThreadAction.cs: -------------------------------------------------------------------------------- 1 | namespace LMTD 2 | { 3 | public interface ILiteThreadAction 4 | { 5 | public void Logic(); 6 | } 7 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/LMTDProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace LMTD 5 | { 6 | public class LMTDProxy 7 | { 8 | private static readonly object LockObj = new object(); 9 | private static WebProxy _proxy = null; 10 | 11 | public static WebProxy GetProxy() 12 | { 13 | lock (LockObj) 14 | { 15 | _proxy = new WebProxy(); 16 | return _proxy; 17 | } 18 | } 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/LMTDownLoad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | 6 | namespace LMTD 7 | { 8 | public class LMTDownLoad : ILiteThreadAction, IDisposable 9 | { 10 | private static readonly Queue LmtDownLoadQueue = new Queue(); 11 | 12 | private Action _completeCallback; 13 | 14 | /// 15 | /// 下载完成后的回调 16 | /// 17 | public event Action Completed 18 | { 19 | add => _completeCallback += value; 20 | remove => this._completeCallback -= value; 21 | } 22 | 23 | private Action _upDateInfoCallback; 24 | 25 | /// 26 | /// 下载更新循环 27 | /// 28 | public event Action UpDateInfo 29 | { 30 | add => _upDateInfoCallback += value; 31 | remove => this._upDateInfoCallback -= value; 32 | } 33 | 34 | /// 35 | /// 创建一个下载器 36 | /// 37 | public static LMTDownLoad Create(string url, string filePath) 38 | { 39 | LMTDownLoad lmtDownLoad; 40 | lock (LmtDownLoadQueue) 41 | { 42 | if (LmtDownLoadQueue.Count > 0) 43 | { 44 | lmtDownLoad = LmtDownLoadQueue.Dequeue(); 45 | lmtDownLoad.url = url; 46 | lmtDownLoad.filePath = filePath; 47 | } 48 | else 49 | { 50 | lmtDownLoad = new LMTDownLoad(url, filePath); 51 | } 52 | } 53 | lmtDownLoad.CancelLock = false; 54 | return lmtDownLoad; 55 | } 56 | 57 | private LMTDownLoad(string url, string filePath) 58 | { 59 | this.url = url; 60 | this.filePath = filePath; 61 | } 62 | 63 | public bool CancelLock = false; 64 | 65 | /// 66 | /// 下载地址 67 | /// 68 | private string url = null; 69 | 70 | /// 71 | /// 文件存储路径 72 | /// 73 | private string filePath = null; 74 | 75 | /// 76 | /// 下载信息 77 | /// 78 | public LmtDownloadInfo LmtDownloadInfo; 79 | 80 | /// 81 | /// 返回下载文件的信息 82 | /// 83 | public LmtDownloadInfo DownLoad() 84 | { 85 | //需要返回的数据 86 | LmtDownloadInfo.DownLoadFileCRC = 0xFFFFFFFF; 87 | LmtDownloadInfo.downLoadSizeValue = 0; 88 | //创建下载请求 89 | HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url); 90 | httpWebRequest.Proxy = LMTDProxy.GetProxy(); 91 | HttpWebResponse httpWebResponse; 92 | try 93 | { 94 | httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); 95 | } 96 | catch 97 | { 98 | #if UNITY_5_3_OR_NEWER 99 | UnityEngine.Debug.LogError("下载资源失败\n" + url + "\n"); 100 | #else 101 | Console.WriteLine("下载失败资源失败\n" + url + "\n"); 102 | #endif 103 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.ResponseFail; 104 | return LmtDownloadInfo; 105 | } 106 | //创建一块存储的大小 1mb 107 | byte[] blockBytes = new byte[1048576]; 108 | using FileStream fileStream = new FileStream(filePath, FileMode.Create); 109 | try 110 | { 111 | //获取文件流 112 | using Stream receiveStream = httpWebResponse.GetResponseStream(); 113 | // ReSharper disable once PossibleNullReferenceException 114 | int blockSize = receiveStream.Read(blockBytes, 0, blockBytes.Length); 115 | while (blockSize > 0) 116 | { 117 | if (CancelLock) 118 | { 119 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.CancelDownLoad; 120 | return LmtDownloadInfo; 121 | } 122 | //计算CRC 123 | for (uint i = 0; i < blockSize; i++) 124 | { 125 | LmtDownloadInfo.DownLoadFileCRC = (LmtDownloadInfo.DownLoadFileCRC << 8) ^ LmtdTable.CRCTable[(LmtDownloadInfo.DownLoadFileCRC >> 24) ^ blockBytes[i]]; 126 | } 127 | LmtDownloadInfo.downLoadSizeValue += blockSize; 128 | _upDateInfoCallback?.Invoke(); 129 | //循环写入读取数据 130 | fileStream.Write(blockBytes, 0, blockSize); 131 | blockSize = receiveStream.Read(blockBytes, 0, blockBytes.Length); 132 | } 133 | receiveStream.Close(); 134 | } 135 | catch 136 | { 137 | #if UNITY_5_3_OR_NEWER 138 | UnityEngine.Debug.LogError("下载资源中断\n" + url + "\n"); 139 | #else 140 | Console.WriteLine("下载资源中断\n" + url + "\n"); 141 | #endif 142 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.DownLoadFail; 143 | return LmtDownloadInfo; 144 | } 145 | finally 146 | { 147 | fileStream.Close(); 148 | httpWebResponse.Close(); 149 | httpWebResponse.Dispose(); 150 | } 151 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.Success; 152 | return LmtDownloadInfo; 153 | } 154 | 155 | public void Logic() 156 | { 157 | LmtDownloadInfo lmtDownloadInfo = DownLoad(); 158 | _completeCallback?.Invoke(lmtDownloadInfo); 159 | } 160 | 161 | public void Dispose() 162 | { 163 | CancelLock = false; 164 | url = null; 165 | filePath = null; 166 | _completeCallback = null; 167 | _upDateInfoCallback = null; 168 | lock (LmtDownLoadQueue) 169 | { 170 | LmtDownLoadQueue.Enqueue(this); 171 | } 172 | } 173 | 174 | } 175 | 176 | /// 177 | /// 下载完成后的回调信息 178 | /// 179 | public struct LmtDownloadInfo 180 | { 181 | public LmtDownloadResult LmtDownloadResult; 182 | public uint DownLoadFileCRC; 183 | 184 | internal long downLoadSizeValue; 185 | /// 186 | /// 已经下载了多少 187 | /// 188 | public long DownLoadSize 189 | { 190 | get { return downLoadSizeValue; } 191 | } 192 | 193 | } 194 | 195 | public enum LmtDownloadResult 196 | { 197 | /// 198 | /// 成功 199 | /// 200 | Success = 0, 201 | /// 202 | /// 请求连接失败 203 | /// 204 | ResponseFail = 1, 205 | /// 206 | /// 下载失败 207 | /// 208 | DownLoadFail = 2, 209 | /// 210 | /// 取消下载 211 | /// 212 | CancelDownLoad = 3 213 | } 214 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/LiteThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace LMTD 6 | { 7 | internal class LiteThread 8 | { 9 | private readonly Thread thread; 10 | private ILiteThreadAction _liteThreadAction = null; 11 | 12 | internal LiteThread() 13 | { 14 | ThreadFactory.ThreadCount++; 15 | thread = new Thread(Run); 16 | thread.Start(); 17 | } 18 | 19 | internal void Action(ILiteThreadAction liteThreadAction) 20 | { 21 | this._liteThreadAction = liteThreadAction; 22 | } 23 | 24 | private void Run() 25 | { 26 | while (!ThreadFactory.RecoverKey) 27 | { 28 | Thread.Sleep(1); 29 | if (_liteThreadAction != null) 30 | { 31 | _liteThreadAction.Logic(); 32 | _liteThreadAction = null; 33 | lock (ThreadFactory.ThreadPool) 34 | { 35 | //执行完逻辑后自己进池 36 | ThreadFactory.ThreadPool.Enqueue(this); 37 | } 38 | } 39 | 40 | } 41 | Recovery(); 42 | } 43 | 44 | /// 45 | /// 回收这个线程 46 | /// 47 | internal void Recovery() 48 | { 49 | ThreadFactory.ThreadCount--; 50 | thread.Abort(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/LmtdTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LMTD 4 | { 5 | internal static class LmtdTable 6 | { 7 | /// 8 | /// 注意此Table和多线程下载的Table以及打AssetBundle加密用的Table需要一样,其中在BM的代码里还有一份拷贝 9 | /// 10 | internal static readonly UInt32[] CRCTable = 11 | { 12 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 13 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 14 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 15 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 16 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 17 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 18 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 19 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 20 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 21 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 22 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 23 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 24 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 25 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 26 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 27 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 28 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 29 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 30 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 31 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 32 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 33 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 34 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 35 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 36 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 37 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 38 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 39 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 40 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 41 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 42 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 43 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /LiteMultiThreadDownLoad/ThreadFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace LMTD 6 | { 7 | public static class ThreadFactory 8 | { 9 | /// 10 | /// 所有线程的数量 11 | /// 12 | internal static uint ThreadCount = 0; 13 | 14 | /// 15 | /// 所有线程的池 16 | /// 17 | internal static readonly Queue ThreadPool = new Queue(); 18 | 19 | private static readonly HashSet AllLiteThread = new HashSet(); 20 | 21 | /// 22 | /// 是否开启回收进程,默认不开启 23 | /// 24 | internal static bool RecoverKey 25 | { 26 | set 27 | { 28 | if (value == false) 29 | { 30 | //说明所有进程都已经被回收 31 | } 32 | } 33 | get => recoverKey; 34 | } 35 | private static bool recoverKey = false; 36 | 37 | /// 38 | /// 生命周期 39 | /// 40 | private static Thread _threadFactoryLife = null; 41 | 42 | /// 43 | /// 执行一个逻辑 44 | /// 45 | public static void ThreadAction(ILiteThreadAction liteThreadAction) 46 | { 47 | if (_threadFactoryLife == null) 48 | { 49 | _threadFactoryLife = new Thread(ThreadUpdate); 50 | _threadFactoryLife.Start(); 51 | } 52 | LiteThread liteThread; 53 | lock (ThreadPool) 54 | { 55 | if (ThreadPool.Count > 0) 56 | { 57 | liteThread = ThreadPool.Dequeue(); 58 | } 59 | else 60 | { 61 | liteThread = new LiteThread(); 62 | AllLiteThread.Add(liteThread); 63 | } 64 | } 65 | AllLiteThread.Add(liteThread); 66 | liteThread.Action(liteThreadAction); 67 | } 68 | 69 | 70 | private static long _lastTime = 0; 71 | /// 72 | /// 线程池清空标志位(如果5-10秒内池有多余的线程线程就清空) 73 | /// 74 | private static bool _poolClear = false; 75 | /// 76 | /// 线程自动回收机制 77 | /// 78 | private static void ThreadUpdate() 79 | { 80 | _lastTime = DateTime.Now.Ticks; 81 | while (true) 82 | { 83 | long nowTime = DateTime.Now.Ticks; 84 | if (nowTime - _lastTime > 50000000) 85 | { 86 | _lastTime = nowTime; 87 | //每隔5s一次循环 88 | lock (ThreadPool) 89 | { 90 | if (ThreadPool.Count == 0) 91 | { 92 | continue; 93 | } 94 | if (!_poolClear) 95 | { 96 | _poolClear = true; 97 | continue; 98 | } 99 | LiteThread liteThread = ThreadPool.Dequeue(); 100 | AllLiteThread.Remove(liteThread); 101 | liteThread.Recovery(); 102 | _poolClear = false; 103 | if (ThreadCount == 0) 104 | { 105 | _lastTime = 0; 106 | _threadFactoryLife?.Abort(); 107 | _threadFactoryLife = null; 108 | } 109 | } 110 | } 111 | Thread.Sleep(1); 112 | } 113 | } 114 | 115 | /// 116 | /// 线程池销毁 117 | /// 118 | public static void Destroy() 119 | { 120 | foreach (LiteThread liteThread in AllLiteThread) 121 | { 122 | liteThread.Recovery(); 123 | } 124 | AllLiteThread.Clear(); 125 | lock (ThreadPool) 126 | { 127 | ThreadPool.Clear(); 128 | } 129 | ThreadCount = 0; 130 | _lastTime = 0; 131 | _poolClear = false; 132 | _threadFactoryLife?.Abort(); 133 | _threadFactoryLife = null; 134 | } 135 | 136 | } 137 | } -------------------------------------------------------------------------------- /NintendoAdapter/AssetComponentInit_Nintendo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | using UnityEngine; 5 | using ET; 6 | using UnityEngine.Networking; 7 | 8 | namespace BM 9 | { 10 | public static partial class AssetComponent 11 | { 12 | #if Nintendo_Switch 13 | /// 14 | /// 初始化 15 | /// 16 | /// Switch模式下 secretKey 参数失效,仅作占位使用 17 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null) 18 | { 19 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop) 20 | { 21 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件"); 22 | return false; 23 | } 24 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName)) 25 | { 26 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化"); 27 | return false; 28 | } 29 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName); 30 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo); 31 | 32 | string filePath = BundleFileExistPath_Nintendo(bundlePackageName, "FileLogs.txt"); 33 | string fileLogs = await LoadNintendoFileText(filePath); 34 | { 35 | Regex reg = new Regex(@"\<(.+?)>"); 36 | MatchCollection matchCollection = reg.Matches(fileLogs); 37 | List dependFileName = new List(); 38 | foreach (Match m in matchCollection) 39 | { 40 | string[] fileLog = m.Groups[1].Value.Split('|'); 41 | LoadFile loadFile = new LoadFile(); 42 | loadFile.FilePath = fileLog[0]; 43 | loadFile.AssetBundleName = fileLog[1]; 44 | 45 | if (fileLog.Length > 2) 46 | { 47 | for (int i = 2; i < fileLog.Length; i++) 48 | { 49 | dependFileName.Add(fileLog[i]); 50 | } 51 | } 52 | loadFile.DependFileName = dependFileName.ToArray(); 53 | dependFileName.Clear(); 54 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile); 55 | } 56 | } 57 | 58 | string dependPath = BundleFileExistPath_Nintendo(bundlePackageName, "DependLogs.txt"); 59 | string dependLogs = await LoadNintendoFileText(dependPath); 60 | { 61 | Regex reg = new Regex(@"\<(.+?)>"); 62 | MatchCollection matchCollection = reg.Matches(dependLogs); 63 | foreach (Match m in matchCollection) 64 | { 65 | string[] dependLog = m.Groups[1].Value.Split('|'); 66 | LoadDepend loadDepend = new LoadDepend(); 67 | loadDepend.FilePath = dependLog[0]; 68 | loadDepend.AssetBundleName = dependLog[1]; 69 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend); 70 | } 71 | } 72 | 73 | ETTask groupTcs = ETTask.Create(); 74 | string groupPath = BundleFileExistPath_Nintendo(bundlePackageName, "GroupLogs.txt"); 75 | string groupLogs = await LoadNintendoFileText(groupPath); 76 | { 77 | Regex reg = new Regex(@"\<(.+?)>"); 78 | MatchCollection matchCollection = reg.Matches(groupLogs); 79 | foreach (Match m in matchCollection) 80 | { 81 | string[] groupLog = m.Groups[1].Value.Split('|'); 82 | LoadGroup loadGroup = new LoadGroup(); 83 | loadGroup.FilePath = groupLog[0]; 84 | loadGroup.AssetBundleName = groupLog[1]; 85 | if (groupLog.Length > 2) 86 | { 87 | for (int i = 2; i < groupLog.Length; i++) 88 | { 89 | loadGroup.DependFileName.Add(groupLog[i]); 90 | } 91 | } 92 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup); 93 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath); 94 | } 95 | } 96 | 97 | //加载当前分包的shader 98 | await LoadShader_Nintendo(bundlePackageName); 99 | return true; 100 | } 101 | 102 | private static async ETTask LoadShader_Nintendo(string bundlePackageName) 103 | { 104 | ETTask tcs = ETTask.Create(); 105 | string shaderPath = BundleFileExistPath_Nintendo(bundlePackageName, "shader_" + bundlePackageName.ToLower()); 106 | //判断文件是否存在 107 | nn.fs.EntryType entryType = nn.fs.EntryType.File; 108 | nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, shaderPath); 109 | if (nn.fs.FileSystem.ResultPathNotFound.Includes(result)) 110 | { 111 | AssetLogHelper.Log("此分包没有Shader: \n" + shaderPath); 112 | result.abortUnlessSuccess(); 113 | return; 114 | } 115 | result.abortUnlessSuccess(); 116 | //读取Shader 117 | AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(shaderPath); 118 | request.completed += operation => 119 | { 120 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle; 121 | tcs.SetResult(); 122 | }; 123 | 124 | await tcs; 125 | } 126 | #endif 127 | 128 | } 129 | } -------------------------------------------------------------------------------- /NintendoAdapter/AssetComponentTools_Nintendo.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | #if Nintendo_Switch 8 | public static partial class AssetComponent 9 | { 10 | /// 11 | /// NintendoSwitch获取Bundle信息文件的路径 12 | /// 13 | internal static string BundleFileExistPath_Nintendo(string bundlePackageName, string fileName) 14 | { 15 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName); 16 | //string path = "rom:/Data/StreamingAssets/" + bundlePackageName + "/" + fileName; 17 | return path; 18 | } 19 | 20 | internal static async ETTask LoadNintendoFileText(string filePath) 21 | { 22 | nn.fs.FileHandle fileHandle = new nn.fs.FileHandle(); 23 | nn.fs.EntryType entryType = nn.fs.EntryType.File; 24 | 25 | nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath); 26 | if (nn.fs.FileSystem.ResultPathNotFound.Includes(result)) 27 | { 28 | AssetLogHelper.LogError("初始化分包未找到要读取的文件: \t" + filePath); 29 | result.abortUnlessSuccess(); 30 | return ""; 31 | } 32 | result.abortUnlessSuccess(); 33 | 34 | 35 | result = nn.fs.File.Open(ref fileHandle, filePath, nn.fs.OpenFileMode.Read); 36 | result.abortUnlessSuccess(); 37 | 38 | long fileSize = 0; 39 | result = nn.fs.File.GetSize(ref fileSize, fileHandle); 40 | result.abortUnlessSuccess(); 41 | 42 | byte[] data = new byte[fileSize]; 43 | result = nn.fs.File.Read(fileHandle, 0, data, fileSize); 44 | result.abortUnlessSuccess(); 45 | 46 | nn.fs.File.Close(fileHandle); 47 | 48 | MemoryStream memoryStream = new MemoryStream(data); 49 | StreamReader streamReader = new StreamReader(memoryStream); 50 | 51 | //异步读取内容 52 | string str = await streamReader.ReadToEndAsync(); 53 | 54 | //关闭引用 55 | streamReader.Close(); 56 | streamReader.Dispose(); 57 | memoryStream.Close(); 58 | await memoryStream.DisposeAsync(); 59 | 60 | return str; 61 | } 62 | 63 | 64 | } 65 | #endif 66 | } 67 | -------------------------------------------------------------------------------- /NintendoAdapter/AssetComponentUpdate_Nintendo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ET; 3 | 4 | namespace BM 5 | { 6 | public static partial class AssetComponent 7 | { 8 | #if Nintendo_Switch 9 | /// 10 | /// 检查分包是否需要更新 11 | /// 12 | /// 所有分包的名称以及是否验证文件CRC 13 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 14 | public static async ETTask CheckAllBundlePackageUpdate(Dictionary bundlePackageNames) 15 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 16 | { 17 | UpdateBundleDataInfo updateBundleDataInfo = new UpdateBundleDataInfo(); 18 | updateBundleDataInfo.NeedUpdate = false; 19 | 20 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build) 21 | { 22 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local) 23 | { 24 | AssetComponentConfig.HotfixPath = AssetComponentConfig.LocalBundlePath; 25 | } 26 | else 27 | { 28 | #if !UNITY_EDITOR 29 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Develop 只能在编辑器下运行"); 30 | #endif 31 | } 32 | } 33 | else 34 | { 35 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Build Nintendo_Switch无需更新,请用Local模式"); 36 | } 37 | return updateBundleDataInfo; 38 | } 39 | 40 | /// 41 | /// 下载更新 42 | /// 43 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 44 | public static async ETTask DownLoadUpdate(UpdateBundleDataInfo updateBundleDataInfo) 45 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行 46 | { 47 | AssetLogHelper.LogError("Nintendo_Switch无需更新"); 48 | } 49 | 50 | #endif 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /NintendoAdapter/LoadBaseRuntimeLoad_Nintendo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using ET; 4 | 5 | namespace BM 6 | { 7 | public partial class LoadBase 8 | { 9 | #if Nintendo_Switch 10 | internal void LoadAssetBundle(string bundlePackageName) 11 | { 12 | AddRefCount(); 13 | if (_loadState == LoadState.Finish) 14 | { 15 | return; 16 | } 17 | if (_loadState == LoadState.Loading) 18 | { 19 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath + 20 | "\nAssetBundle包名: " + AssetBundleName); 21 | if (_assetBundleCreateRequest != null) 22 | { 23 | AssetBundle = _assetBundleCreateRequest.assetBundle; 24 | return; 25 | } 26 | } 27 | //资源没有加载过也没有正在加载就同步加载出来 28 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName); 29 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath); 30 | _loadState = LoadState.Finish; 31 | } 32 | 33 | /// 34 | /// 异步加载LoadBase的AssetBundle 35 | /// 36 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName) 37 | { 38 | AddRefCount(); 39 | if (_loadState == LoadState.Finish) 40 | { 41 | return; 42 | } 43 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName]; 44 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName); 45 | //获取一个协程锁 46 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath)); 47 | if (_loadState == LoadState.NoLoad) 48 | { 49 | _loadState = LoadState.Loading; 50 | await LoadBundleFinish(assetBundlePath); 51 | _loadState = LoadState.Finish; 52 | } 53 | //协程锁解锁 54 | coroutineLock.Dispose(); 55 | } 56 | 57 | /// 58 | /// 通过路径直接加载硬盘上的AssetBundle 59 | /// 60 | private async ETTask LoadBundleFinish(string assetBundlePath) 61 | { 62 | if (_loadState == LoadState.Finish) 63 | { 64 | return; 65 | } 66 | ETTask tcs = ETTask.Create(true); 67 | _assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(assetBundlePath); 68 | _assetBundleCreateRequest.completed += operation => 69 | { 70 | AssetBundle = _assetBundleCreateRequest.assetBundle; 71 | tcs.SetResult(); 72 | //判断是否还需要 73 | if (_refCount <= 0) 74 | { 75 | AssetComponent.AddPreUnLoadPool(this); 76 | } 77 | }; 78 | await tcs; 79 | } 80 | 81 | /// 82 | /// 强制加载完成 83 | /// 84 | internal void ForceLoadFinish(string bundlePackageName) 85 | { 86 | if (_loadState == LoadState.Finish) 87 | { 88 | return; 89 | } 90 | if (_assetBundleCreateRequest != null) 91 | { 92 | AssetLogHelper.LogError("触发强制加载, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath + 93 | "\nAssetBundle包名: " + AssetBundleName); 94 | AssetBundle = _assetBundleCreateRequest.assetBundle; 95 | return; 96 | } 97 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName); 98 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath); 99 | _loadState = LoadState.Finish; 100 | //判断是否还需要 101 | if (_refCount <= 0) 102 | { 103 | AssetComponent.AddPreUnLoadPool(this); 104 | } 105 | } 106 | 107 | /// 108 | /// 打开进度统计 Switch模式下仅做占位使用 109 | /// 110 | internal void OpenProgress(){} 111 | 112 | /// 113 | /// 获取当前资源加载进度 114 | /// 115 | internal float GetProgress() 116 | { 117 | if (_loadState == LoadState.Finish) 118 | { 119 | return 1; 120 | } 121 | if (_loadState == LoadState.NoLoad) 122 | { 123 | return 0; 124 | } 125 | if (_assetBundleCreateRequest == null) 126 | { 127 | AssetLogHelper.LogError("资源加载中但加载请求为Null"); 128 | return 0; 129 | } 130 | return _assetBundleCreateRequest.progress;; 131 | } 132 | 133 | #endif 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BundleMaster 2 |
Unity资源加载大师(纯代码ET框架集成用)
3 | 4 |
网站地址: https://www.unitybundlemaster.com
5 |
视频教程
6 |
YouTube : https://www.youtube.com/watch?v=3P7yJu01j0I
7 |
B站 : https://www.bilibili.com/video/BV19341177Ek
8 |
QQ讨论群: 787652036
9 |
非ET框架集成版本: https://github.com/mister91jiao/BundleMaster_IntegrateETTask
10 | 11 |
注意事项:
12 |
WebGL 平台需要加上 BMWebGL 宏,部署使用 Local 模式运行(网页直接刷新就行所以不需要更新),注意避免正在异步加载一个资源的时候有同步加载同一个资源。
13 |
Switch 平台需要加上 Nintendo_Switch 宏,理论上可以进行热更但因为政策原因所以没有对更新功能进行适配,因此部署依然需要在 Local 模式 下运行,除此之外还要加上 NintendoSDKPlugin,不然会报错,政策原因不上传这部分内容,有需要switch开发需要可以找我联系
14 | 15 |
代码示例:
16 | 17 | //初始化流程 18 | public async ETTask Init() 19 | { 20 | //定义要检查的资源包名 21 | Dictionary updatePackageBundle = new Dictionary() 22 | { 23 | {"MainAssets", false}, 24 | }; 25 | //检查是否需要更新 26 | UpdateBundleDataInfo _updateBundleDataInfo = await AssetComponent.CheckAllBundlePackageUpdate(updatePackageBundle); 27 | if (_updateBundleDataInfo.NeedUpdate) 28 | { 29 | //增量更新更新 30 | await AssetComponent.DownLoadUpdate(_updateBundleDataInfo); 31 | } 32 | //初始化资源包 33 | await AssetComponent.Initialize("MainAssets"); 34 | } 35 | 36 | //加载资源代码示例 37 | public async ETTask Example() 38 | { 39 | //同步加载资源 40 | GameObject playerAsset1 = AssetComponent.Load(out LoadHandler handler, "Assets/Bundles/Role/Player1.prefab"); 41 | GameObject player1 = UnityEngine.Object.Instantiate(playerAsset1); 42 | 43 | //异步加载资源 44 | GameObject playerAsset2 = await AssetComponent.LoadAsync("Assets/Bundles/Role/Player2.prefab"); 45 | GameObject player2 = UnityEngine.Object.Instantiate(playerAsset2); 46 | 47 | //卸载资源 48 | handler.UnLoad(); 49 | AssetComponent.UnLoadByPath("Assets/Bundles/Role/Player2.prefab"); 50 | } 51 | 52 | //别忘了配置生命周期 53 | void Update() 54 | { 55 | AssetComponent.Update(); 56 | } 57 | void OnLowMemory() 58 | { 59 | //移动平台低内存时候调用,可选 60 | //AssetComponent.ForceUnLoadAll(); 61 | } 62 | void OnDestroy() 63 | { 64 | //如果当前正在更新资源,取消更新 65 | _updateBundleDataInfo?.CancelUpdate(); 66 | //游戏销毁调用 67 | AssetComponent.Destroy(); 68 | } 69 | 70 |
友情链接:
71 |
JEngine 一款不错的客户端框架: https://github.com/JasonXuDeveloper/JEngine
72 |
HybridCLR 革命性的热更新解决方案: https://github.com/focus-creative-games/hybridclr
73 | --------------------------------------------------------------------------------