//---加载模块--- //流程:本地加载(编辑器)=》基础系统ab加载(base)=》xid ab加载(xid) //xid--DownloadServer--local //XWorld资源(game.xworld.ren/60s/)和xid资源的路径问题 //回调加载 //LoadScene(Path, function OnFinish(gameObj){...}) //协程后台加载 // coLoadScene("res/scene/10011/scene_10011") //GameObj obj = coLoadPrefab("res/model/actor/10101") //Webgl小游戏不允许阻塞式加载,只能协程加载 :全部LoadFromCacheOrDownload直接加载资源? //AddScene 和 LoadRes 1、Editor读本地资源;2、读更新目录资源;3、WebGL动态下载对应资源;4、app版(pc,安卓,ios)读取包内文件 //场景加载切换:StartCoroutine(Loading(ScenePath));在中间场景(LoadingScene永远存在)中过度(loading过程),删掉前一个场景(DelScene), 后台添加场景(AddScene),() using System; using UnityEngine; using UnityEngine.Networking; using XLua; using UObject = UnityEngine.Object; using System.IO; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine.SceneManagement; namespace XGame { public class AssetBundleInfo { public AssetBundle m_AssetBundle; public int m_ReferencedCount; public AssetBundleInfo(AssetBundle assetBundle) { m_AssetBundle = assetBundle; m_ReferencedCount = 1;//默认创建的时候一定是1个引用了,减到0就是要删除 } } [LuaCallCSharp] public static class XResLoader { class LoadAssetRequest { public Type assetType; public string[] assetNames; //public LuaFunction luaFunc; public Action sharpFunc; public string orginPath; } static XWCoroutine m_Base; public static string m_Postfix = ".unity3d"; #if UNITY_EDITOR public static int m_ABMode = 0;//0 self res ; 1 ab res #else public static int m_ABMode = 1;//0 self res ; 1 ab res #endif static string m_Path; static int m_UpdatePathLength = 0; //static int m_XidResMidPathLength = 0; //static string[] m_AllManifest = null; static AssetBundleManifest m_AssetBundleManifest = null; //static AssetBundleManifest m_tempManifest = null; static UObject[] m_CurLoadedObjs; //static AssetBundleManifest m_ABManifest3D = null; // //static Dictionary m_dicFilelist = new Dictionary(); static Dictionary m_dicXWorldFilelist = new Dictionary(); static Dictionary m_dicTempFilelist = new Dictionary(); static Dictionary m_dicXidCDN = new Dictionary(); static Dictionary m_dicXidServer = new Dictionary(); static Dictionary> m_dicXidFileList = new Dictionary>(); static Dictionary m_dicXidManaifest = new Dictionary(); static string m_sCurXid = "XWorld"; //static string m_sCurXidServerPath = "http://127.0.0.1:15081"; //Scene static Dictionary m_SceneLoaded = new Dictionary(); static AsyncOperation m_Async; public static int m_displayProgress = 0; static int m_toProgress = 0; static string m_dllMd5, m_filelistMd5; //读取场景的进度,它的取值范围在0 - 1 之间。 //static int m_progress = 0; //资源hash128 public static Dictionary m_ResHash = new Dictionary(); /// /// 包依赖关系 /// static Dictionary m_Dependencies = new Dictionary(); /// /// 已加载的AB包信息 /// static Dictionary m_LoadedAssetBundles = new Dictionary(); static HashSet m_LoadingAssetBundles = new HashSet(); /// /// 包加载队列列表 /// static Dictionary> m_LoadRequests = new Dictionary>(); static Dictionary m_LoadDependenciesReq = new Dictionary(); /// /// 对象缓存 /// static Dictionary> objDic = new Dictionary>(); static string m_sUpdatePath; static string m_sLocalPath; static string m_sProductPath; static string m_sServerPath; public delegate void LoadAssetCallback(UnityEngine.GameObject[] resultObjects);//Action action public delegate void LoadFileCallback(byte[] data); public static string GetFileMd5(string xid, string filename) { if (m_dicXidFileList.ContainsKey(xid) == false) return null; m_dicXidFileList[xid].TryGetValue(filename, out string md5); return md5; } public static void SetXIDCDN(string xid, string cdn) { m_dicXidCDN[xid] = NormalizeDownloadServer(cdn, GlobalData.CurCDN); } public static IEnumerator Init() { //获得当前的Game GameObject game = GameObject.Find("Main"); if (game == null) { game = new GameObject("Main"); } m_Base = XWCoroutine.Instance;//game.GetComponent(); //if (m_Base == null) // m_Base = game.AddComponent(); #if !UNITY_WEBGL m_sUpdatePath = Application.persistentDataPath + "/"; m_sProductPath = Application.dataPath + "/"; m_sServerPath = AppConst.UpdateServer; #else m_sUpdatePath = GlobalData.ProductionResBase; //生产 CDN 根(HTTPS,经 Caddy;不再用明文+内部端口 :15081) m_sProductPath = m_sUpdatePath;//Application.streamingAssetsPath + "/"; #if UNITY_EDITOR m_sServerPath = GlobalData.ProductionResBase; #else m_sServerPath = GlobalData.ProductionResBase; #endif #endif m_sLocalPath = Application.streamingAssetsPath + "/";// m_UpdatePathLength = m_sUpdatePath.Length; GlobalData.CurCDN = NormalizeDownloadServer(GlobalData.CurCDN, "http://127.0.0.1:15081/"); Debug.Log($"UpdatePath = {m_sUpdatePath} (cdn:{GlobalData.CurCDN})"); //XWorld的服务器下载路径 //大厅资源 base:编辑器本地(CurCDN),其它 app 端生产;XWorld// 由 coLoadXIDFile/LoadManifest 路径拼 m_dicXidCDN["XWorld"] = GlobalData.bEditor ? GlobalData.CurCDN : GlobalData.ProductionResBase; //基础资源editor 也资源用ab加载 yield return LoadManifest("XWorld"); //加载shader //加载TextMeshPro设置 //LoadTMPSettings(); yield break; } static IEnumerator coLoadXIDFile(string xid, string filepath, Action action) { string cdn; if (!m_dicXidCDN.TryGetValue(xid, out cdn)) { action(null); yield break; } if(m_dicXidFileList.ContainsKey(xid) == false) { action(null); yield break; } string md5 = m_dicXidFileList[xid][filepath]; string path = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(filepath, md5)}"; byte[] data = null; yield return GetFileData(cdn, path, (uwr) => { if (uwr != null) { data = uwr.downloadHandler.data; } }); action(data); } public static IEnumerator coLoadFile(string path, Action action) { byte[] ret = null; yield return coLoadXIDFile("XWorld", path, (datafile) => { ret = datafile; }); if (ret != null) { action(ret); yield break; } yield return coLoadXIDFile(m_sCurXid, path, (datafile) => { ret = datafile; }); if (ret != null) { action(ret); yield break; } action(null); } public static IEnumerator GetFileDataOrigin(string xid, string path, Action onFinish) { string cdn; if (!m_dicXidCDN.TryGetValue(xid, out cdn)) { Debug.LogError($"No XID({m_sCurXid}) CDN : "); yield break; } string url = $"{cdn}{path}"; string md5 = GetFileMd5(xid, path); string finalPath; if (xid.Equals("XWorld", StringComparison.OrdinalIgnoreCase)) finalPath = $"{GlobalData.CurPlatform}/{GetFileMd5Name(path, md5)}"; else finalPath = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(path, md5)}"; yield return GetFileData(cdn, finalPath, (uwr) => { onFinish(uwr); }); } static IEnumerator GetFileData(string downloadServer, string path, Action onFinish) { string url = $"{downloadServer}{path}"; #if UNITY_WEBGL using (UnityWebRequest uwr = UnityWebRequest.Get(url)) { yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { onFinish(uwr); } else { onFinish(null); } } #else string fullpath = $"{m_sUpdatePath}{path}"; if (File.Exists(fullpath)) { fullpath = "file://" + fullpath; //先加载更新文件 using (UnityWebRequest uwr = UnityWebRequest.Get(fullpath)) { yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { onFinish(uwr); } else { Debug.Log($"GetFileData m_sUpdatePath not exist {fullpath}"); onFinish(null); } } yield break; } else { //下载 bool isLoad = false; yield return DownloadFile(downloadServer, path, (uwr) => { if (uwr != null) { onFinish(uwr); isLoad = true; } else { Debug.Log($"DownloadFile not exist {downloadServer}{path}"); } }); if (isLoad) { yield break; } //加载包内文件 string localPath = $"file://{Application.streamingAssetsPath}/{path}"; using (UnityWebRequest request = UnityWebRequest.Get(localPath)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { isLoad = true; onFinish(request); } } } #endif } static IEnumerator GetAssetsBundleData(string downloadServer, string path, Action onFinish) { string url = $"{downloadServer}{path}"; #if UNITY_EDITOR //editor下直接加载本地文件 string resPath; if (!path.StartsWith("Assets/")) resPath = $"Assets/{path}"; else resPath = path; if (File.Exists(resPath)) { string filepath = "file://" + path; using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(filepath)) { yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { onFinish(uwr, true); yield break; } } } #endif #if UNITY_WEBGL using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(url)) { yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { onFinish(uwr, true); } else { onFinish(null,true); } } #else string fullpath = $"{m_sUpdatePath}{path}"; bool localExists = File.Exists(fullpath); if (localExists) { string localUrl = new Uri(fullpath).AbsoluteUri; using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(localUrl)) { yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { onFinish(uwr, true); } else { Debug.LogWarning($"GetAssetsBundleData local load failed path={path}, localUrl={localUrl}, error={uwr.error}, result={uwr.result}"); onFinish(null, true); } } } else { //下载 yield return DownloadFile(downloadServer, path, (uwr) => { onFinish(uwr, false); }); } #endif } public static IEnumerator EnterXid(string xid) { UnloadManifest(m_sCurXid); m_sCurXid = xid; LoadManifest(xid); //string temp = $"Assets/game/"; //$"Assets/{xid}/{GlobalData.CurPlatform}/"; //m_XidResMidPathLength = 12;// yield break; } //加载md5列表和依赖关系manifest static IEnumerator LoadManifest(string xid) { //m_dicXidFileList m_dicXidManaifest string path; if (!m_dicXidFileList.ContainsKey(xid)) { string md5 = null; //加载 update.ver string cdn; if (!m_dicXidCDN.TryGetValue(xid, out cdn)) { Debug.LogError($"No XID({m_sCurXid}) CDN : "); //cdn = ""; yield break; } //统一 ///...,与 coLoadXIDFile/coLoadXIDAB 一致(XWorld 也走 XWorld/ 子目录) path = $"{xid}/{GlobalData.CurPlatform}/updatever.txt?{DateTime.Now.Ticks}"; yield return DownloadFile(cdn, path, (uwr) => { if(uwr != null) md5 = uwr.downloadHandler.text; }); if (md5 == null) { Debug.LogError($"updatever.txt not exist! ({cdn}{path})"); yield break; } string [] lines = md5.Split(',');//dllmd5, filelistmd5 m_dllMd5 = lines[0]; m_filelistMd5 = lines[1]; //加载 files.txt_ path = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name("files.txt", lines[1])}"; yield return GetFileData(cdn, path, (uwr) => { if (uwr != null) { string txt = uwr.downloadHandler.text; string[] lines = txt.Split('\n'); int lineCount = lines.Length; m_dicTempFilelist = new Dictionary(); for (int i = 0; i < lineCount; ++i) { string line = lines[i].Trim().Trim('\uFEFF').Trim(); if (line.Length < 3) continue; if (line[0] == '{' && line[line.Length - 1] == '}') line = line.Substring(1, line.Length - 2); string[] datas = line.Split(','); if (datas.Length < 2) { Debug.LogWarning($"Invalid files.txt line: {lines[i]}"); continue; } string fileName = datas[0].Trim(); string fileMd5 = datas[1].Trim(); if (fileName.Length == 0 || fileMd5.Length == 0) continue; m_dicTempFilelist[fileName] = fileMd5; //Debug.Log($"{datas[0]} => {datas[1]}"); } m_dicXidFileList[xid] = m_dicTempFilelist; Debug.Log($"Load {path} files.txt success! ({txt.Length} {m_dicTempFilelist.Count})"); } else { Debug.LogError($"files.txt not exist! ({cdn}{path})"); } }); //加载 StreamingAssets_ m_dicTempFilelist.TryGetValue("StreamingAssets.unity3d", out md5); //md5 = m_dicTempFilelist["StreamingAssets.unity3d"]; if (md5 == null) { Debug.LogError($"StreamingAssets not exist! ({cdn}StreamingAssets.unity3d ) ({m_dicTempFilelist.Count})"); yield break; } path = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name("StreamingAssets.unity3d", md5)}"; yield return GetFileData(cdn, path, (uwr) => { if (uwr != null) { byte[] assetData; try { assetData = AESManager.Decrypt(uwr.downloadHandler.data); } catch (Exception e) { Debug.LogError($"Decrypt StreamingAssets manifest failed: {path}, {e}"); return; } AssetBundle manAB = AssetBundle.LoadFromMemory(assetData);//AESManager.Decrypt(assetData) if (manAB == null) { Debug.LogError($"Load StreamingAssets manifest AssetBundle failed: {path}"); return; } m_AssetBundleManifest = manAB.LoadAsset("AssetBundleManifest"); m_dicXidManaifest[xid] = m_AssetBundleManifest; manAB.Unload(false); } }); if(!m_dicXidManaifest.ContainsKey(xid)) { Debug.LogError($"StreamingAssets not exist! ({cdn}/StreamingAssets_)"); yield break; } } } static void UnloadManifest(string xid) { m_dicXidFileList[xid] = null; m_dicXidManaifest[xid] = null; } static IEnumerator DownloadFile(string downloadServer, string fileName, System.Action onFinish) { UnityWebRequest uwr; downloadServer = NormalizeDownloadServer(downloadServer, GlobalData.CurCDN); string url = $"{downloadServer}{fileName}"; int npos = fileName.LastIndexOf('?'); if (npos > 0) { fileName = fileName.Substring(0, npos); } Debug.Log($"download ServerList {url}"); if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps && uri.Scheme != Uri.UriSchemeFile)) { Debug.LogError($"Download url invalid: {url}"); onFinish(null); yield break; } using (uwr = UnityWebRequest.Get(url)) { // uwr.timeout = m_httpRequestTimeout; yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { string outfile = m_sUpdatePath + fileName; string dir = Path.GetDirectoryName(outfile); //创建目录 Utility.CreateMulDirectory(dir); if (File.Exists(outfile)) File.Delete(outfile); if (uwr.downloadHandler.data == null) { Debug.LogError($"Data = null ({fileName})"); } else File.WriteAllBytes(outfile, uwr.downloadHandler.data); onFinish(uwr); } else { Debug.LogError($"Download Failed {url}"); onFinish(null); //if (RequestErrorCallback != null) { RequestErrorCallback("DownloadFile", uwr.error); } } } } static string NormalizeDownloadServer(string downloadServer, string fallback) { string server = string.IsNullOrWhiteSpace(downloadServer) ? fallback : downloadServer.Trim(); if (string.IsNullOrWhiteSpace(server)) { server = "http://127.0.0.1:15081/"; } if (server.StartsWith("http://file://", StringComparison.OrdinalIgnoreCase)) { server = server.Substring("http://".Length); } else if (server.StartsWith("https://file://", StringComparison.OrdinalIgnoreCase)) { server = server.Substring("https://".Length); } if (server.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { server = NormalizeFileUrl(server); } if (!server.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !server.StartsWith("https://", StringComparison.OrdinalIgnoreCase) && !server.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { server = "http://" + server; } if (!server.EndsWith("/")) { server += "/"; } return server; } static string NormalizeFileUrl(string url) { string rawPath = url.Substring("file://".Length).Replace('\\', '/'); while (rawPath.StartsWith("/") && rawPath.Length > 2 && rawPath[2] == ':') { rawPath = rawPath.Substring(1); } string localPath = Uri.UnescapeDataString(rawPath).Replace('/', Path.DirectorySeparatorChar); try { return new Uri(localPath).AbsoluteUri; } catch { return "file:///" + rawPath.Replace("#", "%23"); } } static string GetPathMd5Name(string path) { string ret, md5; if (path.Contains("XWorld/")) {//基础资源 int pos = path.LastIndexOf("/")+1; ret = path.Substring(pos); if (m_dicXidFileList.ContainsKey("XWorld")) { if (m_dicXidFileList["XWorld"].ContainsKey(ret)) { md5 = m_dicXidFileList["XWorld"][ret]; ret = GetFileMd5Name(path, md5);//$"{path}_{md5}"; } else { string rawRet = GetUnversionedAssetBundleName(ret); if (m_dicXidFileList["XWorld"].ContainsKey(rawRet)) { md5 = m_dicXidFileList["XWorld"][rawRet]; string rawPath = path.Substring(0, path.Length - ret.Length) + rawRet; ret = GetFileMd5Name(rawPath, md5); } else { Debug.LogWarning($"File not found in XWorld: {ret} {m_dicXidFileList["XWorld"].Count}"); ret = $"{path}"; } } } else { ret = $"{path}"; } } else {//xid 资源 m_sCurXid int pos = path.LastIndexOf("/")+1; ret = path.Substring(pos); if(m_dicXidFileList.ContainsKey(m_sCurXid)) { if (m_dicXidFileList[m_sCurXid].ContainsKey(ret)) { md5 = m_dicXidFileList[m_sCurXid][ret]; ret = GetFileMd5Name(path, md5);//$"{path}_{md5}"; } else { string rawRet = GetUnversionedAssetBundleName(ret); if (m_dicXidFileList[m_sCurXid].ContainsKey(rawRet)) { md5 = m_dicXidFileList[m_sCurXid][rawRet]; string rawPath = path.Substring(0, path.Length - ret.Length) + rawRet; ret = GetFileMd5Name(rawPath, md5); } else { Debug.LogWarning($"File not found in {m_sCurXid}: {ret}"); ret = $"{path}"; } } } else { ret = $"{path}"; } } return ret; } public static string GetFileMd5Name(string filename, string md5) { if (string.IsNullOrEmpty(md5)) return filename; int pos = filename.LastIndexOf('.'); if (pos != -1) { return $"{filename.Substring(0, pos)}_{md5}{filename.Substring(pos)}"; } else return $"{filename}_{md5}"; } //1、Editor读本地资源;2、读更新目录资源;3、WebGL动态下载对应资源;4、app版(pc,安卓,ios)读取包内文件 static string StripXidPlatformPrefix(string path) { if (string.IsNullOrEmpty(path)) return path; string normalized = path.Replace("\\", "/"); string prefix = $"XWorld/{GlobalData.CurPlatform}/"; if (normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return normalized.Substring(prefix.Length); string curPrefix = $"{m_sCurXid}/{GlobalData.CurPlatform}/"; if (!string.IsNullOrEmpty(m_sCurXid) && normalized.StartsWith(curPrefix, StringComparison.OrdinalIgnoreCase)) return normalized.Substring(curPrefix.Length); return normalized; } static string GetUnversionedAssetBundleName(string fileName) { if (string.IsNullOrEmpty(fileName)) return fileName; string ext = Path.GetExtension(fileName); if (!ext.Equals(m_Postfix, StringComparison.OrdinalIgnoreCase)) return fileName; string withoutExt = fileName.Substring(0, fileName.Length - ext.Length); int md5Pos = withoutExt.LastIndexOf('_'); if (md5Pos <= 0) return fileName; string md5 = withoutExt.Substring(md5Pos + 1); if (md5.Length != 8) return fileName; for (int i = 0; i < md5.Length; i++) { if (!Uri.IsHexDigit(md5[i])) return fileName; } return withoutExt.Substring(0, md5Pos) + ext; } static string ResolveLocalAssetBundlePath(string path) { if (string.IsNullOrEmpty(path)) return path; if (Path.IsPathRooted(path) || path.StartsWith(m_sUpdatePath) || path.StartsWith(m_sLocalPath)) return path; string updatePath = m_sUpdatePath + path; if (File.Exists(updatePath)) return updatePath; string bundleName = StripXidPlatformPrefix(path); return m_sLocalPath + GetUnversionedAssetBundleName(bundleName); } static string GetAssetBundleCacheKey(string path) { if (string.IsNullOrEmpty(path)) return path; string key = path.Replace("\\", "/"); if (!string.IsNullOrEmpty(m_sUpdatePath) && key.StartsWith(m_sUpdatePath, StringComparison.OrdinalIgnoreCase)) key = key.Substring(m_sUpdatePath.Length); if (!string.IsNullOrEmpty(m_sLocalPath) && key.StartsWith(m_sLocalPath, StringComparison.OrdinalIgnoreCase)) key = key.Substring(m_sLocalPath.Length); key = StripXidPlatformPrefix(key); return GetUnversionedAssetBundleName(key); } static bool IsAssetBundleLoading(string path) { return m_LoadingAssetBundles.Contains(GetAssetBundleCacheKey(path)); } static void BeginAssetBundleLoad(string path) { m_LoadingAssetBundles.Add(GetAssetBundleCacheKey(path)); } static void EndAssetBundleLoad(string path) { m_LoadingAssetBundles.Remove(GetAssetBundleCacheKey(path)); } public static void AddScene(string ScenePath, LoadAssetCallback Callback) { m_Base.StartCoroutine(OnLoadScene(ScenePath, Callback)); } public static IEnumerator OnLoadScene(string ScenePath, LoadAssetCallback Callback) { //检测是否Editor if (m_ABMode == 1) { //加载相关ab包 string res; string abpath = GetABResPath(ScenePath, out res); //var res_type = type; LoadAssetRequest request = new LoadAssetRequest(); request.assetType = typeof(Scene); int pos = res.LastIndexOf("/"); if (pos != -1) {//保留最后的资源名 request.assetNames = new string[] { res.Substring(pos + 1) }; } else request.assetNames = new string[] { res }; request.orginPath = null; request.sharpFunc = null; List requests = null; if (!m_LoadRequests.TryGetValue(abpath, out requests)) { AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abpath); if (bundleInfo == null) { requests = new List(); requests.Add(request); m_LoadRequests.Add(abpath, requests); yield return OnLoadAssetBundle(abpath, typeof(Scene)); } else { } } else { requests.Add(request); } } //bool SceneLoaded() { return bFinish; }; //yield return new WaitUntil(SceneLoaded); #if !UNITY_EDITOR #else //editor下的场景要加入到buildsetting中 #endif string SceneName = Path.GetFileNameWithoutExtension(ScenePath); m_Async = SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive); //m_Async.allowSceneActivation = false; if (m_Async == null) { Debug.Log($"LoadScene {ScenePath} Failed!"); Callback(null); } else { while (m_Async.progress < 0.98f) { m_toProgress = (int)m_Async.progress * 100; if (m_displayProgress < m_toProgress) { ++m_displayProgress; //yield return null;//new WaitForEndOfFrame(); } yield return null; } m_toProgress = 100; while (m_displayProgress < m_toProgress) { ++m_displayProgress; yield return new WaitForEndOfFrame(); } int count = SceneManager.sceneCount; GameObject[] objs = null; Scene scene = SceneManager.GetSceneByName(SceneName);//有同名问题? if (!m_SceneLoaded.ContainsKey(ScenePath)) { m_SceneLoaded[ScenePath] = count - 1; objs = scene.GetRootGameObjects(); } if (Callback != null) Callback(objs); //切换场景 //m_Async.allowSceneActivation = true; } } public static void DelScene(string ScenePath) { if (m_SceneLoaded.ContainsKey(ScenePath)) { string SceneName = Path.GetFileNameWithoutExtension(ScenePath); string abName; if (ScenePath.EndsWith(m_Postfix)) abName = ScenePath; else abName = $"{ScenePath}{m_Postfix}"; abName = GetDepFilePath(m_sUpdatePath, abName); UnloadAssetBundle(abName); SceneManager.UnloadSceneAsync(SceneName); m_SceneLoaded.Remove(ScenePath); } //GC回收 } public static string GetMd5ByResPath(string path, string xid = null) { string ret; Dictionary dic; if(xid == null) { dic = m_dicXidFileList["XWorld"]; } else { dic = m_dicXidFileList[xid]; } if(dic.TryGetValue(path, out ret)) //if (dic.ContainsKey(path)) { return ret;//dic[path]; } else return ""; } //直接加载文件,相对于StreamingAssets目录的路径 public static void LoadPackageFile(string Path, LoadFileCallback Callback) { m_Base.StartCoroutine(OnLoadPackageFile(Path, Callback)); } public static IEnumerator OnLoadPackageFile(string Path, LoadFileCallback Callback) { #if UNITY_EDITOR Callback(null); yield break; #else byte[] data = null; //编辑器 string url = $"{Application.streamingAssetsPath}/{Path}"; //包内目录 using (UnityWebRequest uwr = UnityWebRequest.Get(url)) { yield return uwr.SendWebRequest(); data = uwr.downloadHandler.data; //AssetBundle ab = null; if (!string.IsNullOrEmpty(uwr.error) /*|| bytes.Length == 0*/) { //url = url.Replace(relpath, ""); Debug.LogError(uwr.error + " | "+url); } } Callback(data); #endif } public static void AddAssetBundle(string fullPathName, AssetBundle assetBundle) { string cacheKey = GetAssetBundleCacheKey(fullPathName); if (m_LoadedAssetBundles.ContainsKey(cacheKey)) return; AssetBundleInfo abInfo = new AssetBundleInfo(assetBundle); m_LoadedAssetBundles.Add(cacheKey, abInfo); } public static string[] GetAllDependencies(string assetBundleName) { string[] bundles = m_AssetBundleManifest.GetAllDependencies(assetBundleName); //if (bundles.Length > 0) return bundles; //其他 ; //if(m_ABManifest3D != null) //if (assetBundleName == null || assetBundleName == "") //{ // Debug.Log("assetBundleName is null"); //} //if (m_ABManifest3D == null) //{ // Debug.Log("m_ABManifest3D is null"); // return null; //} //string[] bundles = m_ABManifest3D.GetAllDependencies(assetBundleName); //return bundles; } /// /// 加载TextMeshPro Setting /// static void LoadTMPSettings() { } //获取ab路径 public static string GetABResPath(string path, out string resname, bool bExtReserve = false) { string abpath; string abname = string.Empty; string dir = string.Empty; string xid; string md5; int pos; //针对xid路径修改 path = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name("files.txt", md5)}"; if(path.StartsWith("Game/")) { xid = "XWorld"; } else if (path.StartsWith("XWorld/")) { xid = "XWorld"; pos = 7; path = path.Substring(pos);//去掉前面的路径 } else { xid = m_sCurXid; pos = path.IndexOf('/'); path = path.Substring(pos);//去掉前面的路径 } //m_AssetBundleManifest = m_dicXidManaifest[xid]; path = path.Replace("\\", "/"); int npos = path.LastIndexOf('/'); int lastpos = path.LastIndexOf('.'); if ((pos = path.IndexOf(m_Postfix)) != -1) {//其他3d资源读取,兼容原来的map,model,effect 读取 读取方式为直接ab名加资源名 //npos = path.IndexOf(m_Postfix);//+8; if (path.Length <= pos + 8) { resname = path; Debug.LogWarning("GetABResPath Error: " + path); return path; } else { pos = pos + 8; } abname = path.Substring(0, pos).ToLower(); resname = path.Substring(pos + 1); if (m_dicXidFileList.ContainsKey(xid)) md5 = m_dicXidFileList[xid][abname];//md5需要是文件名path而不是资源路径 else md5 = ""; //修改为只返回相对路径 abpath = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(abname, md5)}"; return abpath; /*/这里需要先读取Update目录资源 abpath = m_sUpdatePath + abname; if (!File.Exists(abpath))//update目录为可写物理目录,可用File判断 { #if UNITY_IOS || UNITY_ANDROID //Debug.Log("Local : Update Path :" + abpath); abpath = m_sLocalPath + abname;//底包内部ab读取 #else //标准资源位置加载 abpath = m_sLocalPath + abname; if (!File.Exists(abpath))//PC 版也可以这样判定 { abpath = m_Path + abname;//统一资源位置 } #endif } return abpath;//*/ } else {//原始相对路径加载,转为ab + 资源名的形式 if (lastpos < npos) //目录中包含.字 { lastpos = -1; } if (lastpos == -1) { lastpos = path.Length; } int beginpos = 0; beginpos = path.IndexOf('/'); //beginpos = path.IndexOf('/', beginpos+1);//查找第二个子目录名 string sType = path.Substring(0, beginpos); //if (sType == "res") //{//res 目录下的路径,有子目录如res/effect/ // beginpos = path.IndexOf('/', beginpos + 1); //} if (beginpos == -1 || npos == -1) { Debug.LogError(" / beginpos == -1 || npos == -1 : " + path); beginpos = 0; } if (npos - beginpos - 1 <= 0) { resname = path; Debug.LogWarning("GetABResPath Error: " + path); return path; } if (path.EndsWith(".spriteatlas")) //单文件打包的 { npos = path.LastIndexOf('.'); abname = path.Substring(0, npos) + m_Postfix;//beginpos + 1 abname = abname.ToLower().Replace("/", "_"); npos = path.LastIndexOf('/'); } else { if (!path.EndsWith(m_Postfix)) abname = (path.Substring(0, npos) + m_Postfix).Replace('/', '_');//beginpos + 1 else abname = path.Substring(0, npos).Replace('/', '_'); //转小写 abname = abname.ToLower(); } if (beginpos + 1 <= 0) { resname = path; Debug.LogWarning("GetABResPath Error: " + path); return path; } //dir = path.Substring(0, beginpos + 1);// "/" + //dir = dir.ToLower(); dir = ""; } //统一走 60S/XWorld//_.unity3d(md5 命名),md5 取自 XWorld 清单;缺失则空(不抛) md5 = ""; if (m_dicXidFileList.ContainsKey(xid) && m_dicXidFileList[xid] != null) m_dicXidFileList[xid].TryGetValue(abname, out md5); #if UNITY_WEBGL //WebGL直接从网站下载 if (!bExtReserve) { resname = path.Substring(npos + 1, lastpos - npos - 1); //去掉文件后缀名 } else { resname = path.Substring(npos + 1, path.Length - npos - 1); } //修改为只返回相对路径 abpath = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(abname, md5)}"; return abpath; #endif if (!bExtReserve) { resname = path.Substring(npos + 1, lastpos - npos - 1); //去掉文件后缀名 } else { resname = path.Substring(npos + 1, path.Length - npos - 1); } //修改为只返回相对路径 abpath = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(abname, md5)}"; return abpath; /*/这里需要先读取Update目录资源 ud abpath = m_sUpdatePath + dir + abname;//path.Substring(0, beginpos + 1) //.Replace('\\', '/') if (!File.Exists(abpath))//update目录为可写物理目录,可用File判断 { #if UNITY_IOS || UNITY_ANDROID //Debug.Log("Local : Update Path :" + abpath); abpath = m_sLocalPath + dir + abname;//底包内部ab读取 #else //原始底包资源(LocalTest个人UIEditor打包资源) abpath = Application.dataPath + dir + abname; if (!File.Exists(abpath)) { //标准资源位置加载 abpath = m_sLocalPath + dir + abname; if (!File.Exists(abpath))//PC 版也可以这样判定 { abpath = m_Path + abname;//统一资源位置 } } #endif } return abpath;//*/ } public static void LoadRes(string[] paths, Type type, Action action) { m_Base.StartCoroutine(coLoadAllRes(paths, type, action)); } public static UObject[] GetCurLoadedObjs() { return m_CurLoadedObjs; } public static UObject GetCurLoadedObj() { if (m_CurLoadedObjs != null) return m_CurLoadedObjs[0]; else return null; } public static IEnumerator coLoadRes(string path, Type type = null, Action action = null) { if (path.StartsWith("Assets/")) { path = path.Substring(7); } yield return coLoadAllRes(new string[] { path }, type, objs => { if (objs != null && objs.Length > 0) { m_CurLoadedObjs = objs; if (action != null) action(objs[0]); } else if (action != null) { m_CurLoadedObjs = null; action(null); } }); } public static IEnumerator coLoadRess(string[] paths, Type type = null, Action action = null) { for (int i = 0; i < paths.Length; ++i) { if (paths[i].StartsWith("Assets/")) { paths[i] = paths[i].Substring(7); } } yield return coLoadAllRes(paths, type, objs => { if (objs != null && objs[0] != null) { m_CurLoadedObjs = objs; if (action != null) action(objs[0]); } else if (action != null) { m_CurLoadedObjs = null; action(null); } }); } public static IEnumerator coLoadXIDAB(string xid, string abpath, Action action) { AssetBundle ab = null; AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abpath); { if (bundleInfo != null) { ab = bundleInfo.m_AssetBundle; action(ab); yield break; } } if (IsAssetBundleLoading(abpath)) { while (IsAssetBundleLoading(abpath)) { yield return null; } bundleInfo = GetLoadedAssetBundle(abpath); if (bundleInfo != null) { ab = bundleInfo.m_AssetBundle; action(ab); yield break; } } BeginAssetBundleLoad(abpath); string cdn; if (!m_dicXidCDN.TryGetValue(xid, out cdn)) { EndAssetBundleLoad(abpath); yield break; } if (!m_dicXidFileList.TryGetValue(xid, out var fileList) || fileList == null || !fileList.TryGetValue(abpath, out var md5)) { // 清单里没有该资源(例如未打包/未发布),优雅返回,让上层去尝试其它 xid 或按"未找到"处理 EndAssetBundleLoad(abpath); yield break; } string path = $"{xid}/{GlobalData.CurPlatform}/{GetFileMd5Name(abpath, md5)}"; yield return GetAssetsBundleData(cdn, path, (uwr, isRaw) => { if (uwr != null) { //下载期间可能已被并发请求加载;复查命中则复用,避免对同一内容二次 LoadFromMemory。 AssetBundleInfo loadedInfo = GetLoadedAssetBundle(abpath); if (loadedInfo != null) { ab = loadedInfo.m_AssetBundle; EndAssetBundleLoad(abpath); return; } if(isRaw) ab = DownloadHandlerAssetBundle.GetContent(uwr); else ab = AssetBundle.LoadFromMemory(uwr.downloadHandler.data); //注册进全局缓存,使后续(含作为依赖的)加载能去重,避免重复 LoadFromMemory。 if (ab != null) AddAssetBundle(abpath, ab); } EndAssetBundleLoad(abpath); }); action(ab); } public static IEnumerator coLoadAB(string abpath, Action action) { AssetBundle ret = null; yield return coLoadXIDAB("XWorld", abpath, (ab) => { ret = ab; }); if (ret != null) { action(ret); yield break; } yield return coLoadXIDAB(m_sCurXid, abpath, (ab) => { ret = ab; }); if (ret != null) { action(ret); yield break; } //yield return coLoadXIDAB("XWorld", abpath, (ab) => //{ // ret = ab; //}); } public static IEnumerator coLoadAllRes(string[] paths, Type type, Action action) { string res; List result = null; List listPath = new List(); if (type == null) type = typeof(GameObject); var res_type = type; result = new List(); foreach (string path in paths) { string abpath = GetABResPath(path, out res); if (type == null) type = typeof(GameObject); int pos = res.LastIndexOf("/"); LoadAssetRequest request = new LoadAssetRequest(); request.assetType = type; if (pos != -1) {//保留最后的资源名 request.assetNames = new string[] { res.Substring(pos + 1) }; } else request.assetNames = new string[] { res }; request.sharpFunc = action; request.orginPath = path; List requests = null; if (!m_LoadRequests.TryGetValue(abpath, out requests)) { requests = new List(); requests.Add(request); m_LoadRequests.Add(abpath, requests); //Launcher.Instance.StartCoroutine(OnLoadAsset(abpath, res, path, res_type, null)); } else { requests.Add(request); } UObject getAsset = null; #if UNITY_EDITOR if (m_ABMode == 0) { //编辑器读取本地资源 string resPath; if (!path.StartsWith("Assets/")) resPath = $"Assets/{path}"; else resPath = path; if (File.Exists(resPath)) {//有才加载 getAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(resPath); if (getAsset != null) { result.Add(getAsset); } else { Debug.LogError($"Load {path} Failed!"); } m_LoadRequests.Remove(abpath); continue; } } #endif //Load AB from base AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abpath); { if (bundleInfo == null) { yield return OnLoadAssetBundle(abpath, type); bundleInfo = GetLoadedAssetBundle(abpath); if (bundleInfo == null) { m_LoadRequests.Remove(abpath); Debug.LogError("OnLoadAsset Failed--->>>" + abpath); continue; } if (bundleInfo.m_AssetBundle == null) { m_LoadRequests.Remove(abpath); Debug.LogError("OnLoadAsset AssetBundle is null--->>>" + abpath); continue; } } } //Load Asset —— 只加载并返回“本次请求自身”的资源。 //原实现会遍历 m_LoadRequests[abpath] 整个共享队列、把所有人的资源都加进自己的 result: //并发加载同一 AB 内不同资源时(如 UI_GameLobby/UI_CharacterSwitch/UI_LobbyJoystick 同在 game_art_ui_prefab.unity3d) //请求会互相串包,coLoadRes 取 objs[0] 便拿到别人的资源(表现为换角色面板/摇杆加载到错误 prefab → 按钮不可见、摇杆缺 Knob)。 List resultLocal = null; if (!objDic.TryGetValue(request.orginPath, out resultLocal)) { resultLocal = new List(); AssetBundle ab = bundleInfo.m_AssetBundle; for (int j = 0; j < request.assetNames.Length; j++) { string assetPath = request.assetNames[j]; AssetBundleRequest req = ab.LoadAssetAsync(assetPath, request.assetType); yield return req; if (req.asset == null) { Debug.Log($"LoadAsset From AB Failed {assetPath} type :{request.assetType.Name} "); } else { Debug.Log($"LoadAsset {assetPath} OK {DateTime.Now}"); } resultLocal.Add(req.asset); } objDic[request.orginPath] = resultLocal;//缓存对象(键=资源路径,值=该资源本身) } result.AddRange(resultLocal); //从共享队列中移除“自己的”请求;队列空了再移除整个键(避免影响并发中的其它请求) List cur; if (m_LoadRequests.TryGetValue(abpath, out cur)) { cur.Remove(request); if (cur.Count == 0) { m_LoadRequests.Remove(abpath); } } if (bundleInfo != null) bundleInfo.m_ReferencedCount++; } //完成 if (action != null) { action(result.ToArray()); } yield break; } public static void LoadResAB(string path, Type type, Action action) { string res; if (path.StartsWith("Assets/")) { path = path.Substring(7); } string abpath = GetABResPath(path, out res); //Debug.LogWarning($"LoadResAB {abpath} {res}"); if (type == null) type = typeof(GameObject); var res_type = type; LoadAssetRequest request = new LoadAssetRequest(); request.assetType = type; int pos = res.LastIndexOf("/"); if (pos != -1) {//保留最后的资源名 request.assetNames = new string[] { res.Substring(pos + 1) }; } else request.assetNames = new string[] { res }; request.sharpFunc = action; request.orginPath = path; List requests = null; if (!m_LoadRequests.TryGetValue(abpath, out requests)) { requests = new List(); requests.Add(request); m_LoadRequests.Add(abpath, requests); m_Base.StartCoroutine(OnLoadAsset(abpath, res, path, res_type, null)); } else { requests.Add(request); } } /// /// 同步加载AB /// /// /// /// [Obsolete("资源加载统一用异步:coLoadRes 或回调版 LoadRes(path,type,Action)。同步加载不从CDN下载热更资源,会读到旧APK底包。详见 Doc/Rule/UnityProject.md")] public static UObject LoadResAB(string path, Type type) { //先取缓存 List result = null; if (objDic.TryGetValue(path, out result) && result.Count > 0) { return result[0]; } if (type == null) type = typeof(GameObject); string res; string abPath = GetABResPath(path, out res); var res_type = type; AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abPath); AssetBundle ab = null; if (bundleInfo == null) { if (type != typeof(AssetBundleManifest)) { //先加载依赖项 string[] dependencies; if(!m_Dependencies.TryGetValue(abPath, out dependencies)) //if (m_Dependencies.ContainsKey(abPath)) //{ // dependencies = m_Dependencies[abPath]; //} //else { //if (abPath.Contains("/ui/")) //{ if (m_AssetBundleManifest == null) { dependencies = null; Debug.LogError("m_AssetBundleManifest is null : " + abPath); return null; } else { string abDep = abPath.Replace(m_sUpdatePath, "");//未考虑原始包文件 abDep = GetUnversionedAssetBundleName(StripXidPlatformPrefix(abDep)); dependencies = m_AssetBundleManifest.GetAllDependencies(abDep); //GetAllDependencies(Path.GetFileName(abPath)); } //} //else //{ // int pos = abPath.IndexOf("base/"); // dependencies = GetAllDependencies(path.Substring(0, path.Length - res.Length - 1)); //} m_Dependencies.Add(abPath, dependencies); } //将依赖的包也加载出来 if (dependencies.Length > 0) { for (int i = 0; i < dependencies.Length; i++) { string depName = dependencies[i]; AssetBundleInfo d_bundleInfo = null; #if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR string abfile = GetDepFilePath(abPath, depName);// abPath.Replace(Path.GetFileName(abPath), depName); #else //ud 220527 string abfile = abPath.Replace(m_sUpdatePath, "");//未考虑原始包文件 abfile = GetUnversionedAssetBundleName(StripXidPlatformPrefix(abfile)); dependencies = m_AssetBundleManifest.GetAllDependencies(abfile); #endif d_bundleInfo = GetLoadedAssetBundle(abfile); if (d_bundleInfo != null) { d_bundleInfo.m_ReferencedCount++; } else { //加载对应的依赖包 int count = 0; //跟协程加载那里进行线程安全判定 if(m_LoadDependenciesReq.TryGetValue(abfile, out count)) //if (m_LoadDependenciesReq.ContainsKey(abfile)) { m_LoadDependenciesReq[abfile] = count + 1; } else { d_bundleInfo = LoadAssetBundleSycn(abfile); if (d_bundleInfo == null) { Debug.LogError("LoadResAB Load dep ab Failed--->>>" + abfile); continue; } d_bundleInfo.m_ReferencedCount++; } } } } } bundleInfo = LoadAssetBundleSycn(abPath); if (bundleInfo == null) { Debug.LogError("LoadResAB LoadABSycn Failed--->>>" + abPath); return null; } else { ab = bundleInfo.m_AssetBundle; if (ab == null) { Debug.LogWarning("LoadResAB Load AB Error: " + abPath); } } } else { ab = bundleInfo.m_AssetBundle; } if (ab == null) { return null; } UObject uo = ab.LoadAsset(res, type); objDic.Add(path, new List() { uo }); //缓存对象 bundleInfo.m_ReferencedCount++; return uo; } public static byte[] LoadBytesRes(string filename) { filename = filename.Replace("\\", "/").Replace(".", "/"); if (m_ABMode == 0) {//本地加载 string fullPath = $"{Application.dataPath}/lua/{filename}.lua"; if (File.Exists(fullPath)) { return File.ReadAllBytes(fullPath); } } //ab加载 string abResName; int pos = filename.IndexOf("/"); if (pos == -1)//根路径脚本路径特殊处理一下 abResName = $"luabytes/luabytes/{filename}.bytes"; else abResName = $"luabytes/{filename}.bytes"; //luabytes/ return GetABBytesRes(abResName); } public static byte[] GetABBytesRes(string filename) { byte[] data; //1.update ab读取 //目前方式和ui一样,以目录为单位做ab包 string name = filename; string path = name;//android : Application.persistentDataPath + "/";m_sUpdatePath + UnityEngine.Object obj = LoadLuaAB(path, typeof(TextAsset));//LoadRes(path, typeof(TextAsset)); if (obj != null) { data = (obj as TextAsset).bytes;// txt;// if (data != null) return data; } //2.包内资源读取ab 或 Resources.Load(filename).bytes; //path = m_sProductPath + "lua/" + name; //obj = ResManager.LoadResAB(path, typeof(TextAsset)); //if (obj != null) //{ // data = (obj as TextAsset).bytes; // if (data != null) // return data; //} return null; } /// /// 获取文本资源字符 /// Plot/1001.BT /// /// public static string GetTextAssetRes(string filename) { string content = string.Empty; if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer) { TextAsset txt; //1.update ab读取 //目前方式和ui一样,以目录为单位做ab包 string name = filename; string path = "scripts/config/" + name;//android : Application.persistentDataPath + "/";m_sUpdatePath + UnityEngine.Object obj = LoadLuaAB(path, typeof(TextAsset));//LoadRes(path, typeof(TextAsset)); if (obj != null) { txt = (obj as TextAsset);// txt;// if (txt != null) content = txt.text; } } else { string m_productPath = m_sProductPath; string fullPath = $"{m_productPath}Config/Config/{filename}"; if (!File.Exists(fullPath)) { fullPath = $"{m_productPath}Scripts_c/Base/Config/{filename}"; if (!File.Exists(fullPath)) { fullPath = $"{Application.dataPath}/Scripts_c/Base/Config/{filename}"; } } try { if (File.Exists(fullPath)) { content = File.ReadAllText(fullPath); } } catch (Exception ex) { Debug.LogError(fullPath + "文件不存在(" + ex.Message + ")\n" + ex.StackTrace); } } return content; } public static UObject LoadLuaAB(string path, Type type) { //先取缓存 List result = null; if (objDic.TryGetValue(path, out result) && result.Count > 0) { return result[0]; } string res; string abPath = GetABResPath(path, out res, true); var res_type = type; AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abPath); AssetBundle ab = null; if (bundleInfo == null) { bundleInfo = LoadAssetBundleSycn(abPath); if (bundleInfo == null) { Debug.LogError("LoadLuaAB LoadABSync Failed--->>>" + abPath); return null; } ab = bundleInfo.m_AssetBundle; } else { ab = bundleInfo.m_AssetBundle; } if (ab == null) { Debug.LogWarning("LoadLuaAB Load AB Error: " + abPath); return null; } UObject uo = ab.LoadAsset(res, type); objDic.Add(path, new List() { uo }); //缓存对象 bundleInfo.m_ReferencedCount++; return uo; } /// /// 同步加载AB包 /// /// /// static AssetBundleInfo LoadAssetBundleSycn(string path) { AssetBundleInfo abInfo = null; // GetABResPath 现在返回相对路径(如 XWorld/android/xxx_md5.unity3d); // 与异步加载一致解析为完整本地路径:优先 persistentDataPath(已下载更新),回退底包 streamingAssets。 // 幂等:已是完整路径(rooted 或已含 m_sUpdatePath/m_sLocalPath)则不再拼。 path = ResolveLocalAssetBundlePath(path); abInfo = GetLoadedAssetBundle(path); if (abInfo != null) { return abInfo; } AssetBundle ab = AssetBundle.LoadFromFile(path); if (ab == null) { Debug.LogError($"LoadAssetBundleSycn failed: {path}"); return null; } string cacheKey = GetAssetBundleCacheKey(path); abInfo = new AssetBundleInfo(ab); m_LoadedAssetBundles.Add(cacheKey, abInfo); return abInfo; } public static AssetBundle LoadAssetBundleAsync(string reltivePath, System.Action callback = null) { string fullPath;//这里需要先读取Update目录资源 fullPath = ResolveLocalAssetBundlePath(reltivePath); AssetBundleInfo abInfo; if (callback == null) { abInfo = LoadAssetBundleSycn(fullPath); if (abInfo != null) return abInfo.m_AssetBundle; else return null; } abInfo = GetLoadedAssetBundle(fullPath); if (abInfo != null) { callback(fullPath, abInfo.m_AssetBundle); return null; } m_Base.StartCoroutine(OnLoadAssetBundleAsync(fullPath, callback)); return null; } public static IEnumerator OnLoadAssetBundleAsync(string fullPath, System.Action callback) { yield return OnLoadAssetBundle(fullPath, typeof(AssetBundle)); AssetBundle ab = null; AssetBundleInfo info; info = GetLoadedAssetBundle(fullPath); if (info != null) ab = info.m_AssetBundle; //var loadRequest = AssetBundle.LoadFromFileAsync(fullPath); //yield return loadRequest; //AssetBundleInfo abInfo = new AssetBundleInfo(loadRequest.assetBundle); //m_LoadedAssetBundles.Add(fullPath, abInfo); callback(fullPath, ab); } /// /// 同步方式加载资源 /// /// /// /// [Obsolete("资源加载统一用异步:coLoadRes 或回调版 LoadRes(path,type,Action)。同步加载不从CDN下载热更资源,会读到旧APK底包。详见 Doc/Rule/UnityProject.md")] public static UObject LoadRes(string path, Type type) { #pragma warning disable 618 // 该同步实现内部仍会调用已标记 [Obsolete] 的同步方法 //先取缓存 List result = null; if (objDic.TryGetValue(path, out result) && result.Count > 0) { return result[0]; } //如果是AB包模式 if (m_ABMode == 1) { return LoadResAB(path, type); } #if UNITY_EDITOR //编辑器环境直接读取本地资源 string finalpath; if (path.StartsWith("Assets/")) finalpath = path; else finalpath = "Assets/" + path; int lastpos = path.LastIndexOf('.'); if (lastpos == -1) { finalpath += ".prefab";//暂时无后缀名的默认都是prefab } UObject asset = AssetDatabase.LoadAssetAtPath(finalpath, type); if (asset != null) { return asset; } #endif UObject o = LoadResAB(path, type); //if (o == null) //{ // Debug.LogError("Load res Failed: "+ path); //} return o; #pragma warning restore 618 } //1、Editor读本地资源;2、读更新目录资源;3、WebGL动态下载对应资源;4、app版(pc,安卓,ios)读取包内文件 public static void LoadRes(string path, Type type, Action action = null) { //先取缓存 List result = null; if (objDic.TryGetValue(path, out result)) { //220412 ud 增加对C#回调的支持 if (action != null) { action(result.ToArray()); action = null; } return; } //表示UI界面处于AB包调试模式 if (m_ABMode == 1) { LoadResAB(path, type, action); return; } #if UNITY_EDITOR //编辑器环境直接读取本地资源 result = new List(); string finalpath = path; if (path.StartsWith("Assets/")) finalpath = path; else finalpath = "Assets/" + path; int lastpos = path.LastIndexOf('.'); if (lastpos == -1) { finalpath += ".prefab";//暂时无后缀名的默认都是prefab } UObject asset = AssetDatabase.LoadAssetAtPath(finalpath, type); if (asset) { result.Add(asset); objDic.Add(path, result); if (action != null) { action(result.ToArray()); } return; } #endif LoadResAB(path, type, action); return; } /// /// 获取图标 /// /// /// public static Sprite GetSpriteByName(string spritePath) { if (!spritePath.EndsWith(".png")) { spritePath += ".png"; } // List objs = null; // if(objDic.TryGetValue(spritePath,out objs) && objs.Count>0) // { // return (Sprite)objs[0]; // } #pragma warning disable 618 // XResLoader 内部同步辅助,暂保留 UObject spObj = LoadRes(spritePath, typeof(Sprite)); #pragma warning restore 618 return spObj ? (Sprite)spObj : null; } /// /// 获取图集Sprite序列 /// /// /// /// public static Sprite[] GetSpritesByName(string atlasName, string spriteName) { string path = atlasName + "/" + spriteName; List sprites = new List(); for (int i = 1; i <= 30; i++) { try { Sprite sp = GetSpriteByName(path + i.ToString("D2") + ".png"); if (sp) { sprites.Add(sp); } else break; } catch { } } return sprites.ToArray(); } //读取资源//、读取本地;、读更新目录资源;、WebGL动态下载对应资源;、app版(pc,安卓,ios)读取包内文件 static IEnumerator OnLoadAsset(string abPath, string res, string orginPath, Type type, Action action = null) { GameObject getAsset = null; if (m_ABMode == 0) { #if UNITY_EDITOR //编辑器读取本地资源 string artPath = res; getAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(orginPath); if (getAsset != null) { m_LoadRequests.Remove(abPath); yield break; } #endif } AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abPath); if (getAsset == null) { if (bundleInfo == null) { yield return OnLoadAssetBundle(abPath, type); bundleInfo = GetLoadedAssetBundle(abPath); if (bundleInfo == null) { m_LoadRequests.Remove(abPath); Debug.LogError("OnLoadAsset Failed--->>>" + abPath); yield break; } } } //相关ab包已经加载完了,可以真正读取资源 List list = null; if (!m_LoadRequests.TryGetValue(abPath, out list)) { m_LoadRequests.Remove(abPath); yield break; } for (int i = 0; i < list.Count; i++) { string[] assetNames = list[i].assetNames; List result = null; if (!objDic.TryGetValue(list[i].orginPath, out result)) { result = new List(); if (getAsset == null) { AssetBundle ab = bundleInfo.m_AssetBundle; for (int j = 0; j < assetNames.Length; j++) { string assetPath = assetNames[j]; AssetBundleRequest request = ab.LoadAssetAsync(assetPath, list[i].assetType); yield return request; result.Add(request.asset); } } else { result.Add(getAsset); } if (objDic.ContainsKey(list[i].orginPath)) { objDic.Remove(list[i].orginPath); } objDic.Add(list[i].orginPath, result);//缓存对象 } if (list[i].sharpFunc != null) { list[i].sharpFunc(result.ToArray()); list[i].sharpFunc = null; } if (bundleInfo != null) bundleInfo.m_ReferencedCount++; } m_LoadRequests.Remove(abPath); } static string GetDepFilePath(string abFullPath, string depName) { //ud 220616 分别支持ui和其他资源的打包方式 string path; if (abFullPath.Contains(m_sUpdatePath)) {//原ab路径是更新目录 path = $"{m_sUpdatePath}{depName}"; if (!File.Exists(path)) {//替换会本地目录 path = path.Replace(m_sUpdatePath, m_sLocalPath); } } else //if(path.Contains(m_sLocalPath)) { //原ab路径是本地目录 path = $"{m_sUpdatePath}{depName}"; if (!File.Exists(path)) { return $"{m_sLocalPath}{depName}"; } } return path; } //读取ab//、读更新目录资源ab;、WebGL动态下载对应资源ab;、app版(pc,安卓,ios)读取包内ab static IEnumerator OnLoadAssetBundle(string abPath, Type type) { AssetBundleInfo existingInfo = GetLoadedAssetBundle(abPath); if (existingInfo != null) { existingInfo.m_ReferencedCount++; yield break; } if (IsAssetBundleLoading(abPath)) { while (IsAssetBundleLoading(abPath)) { yield return null; } existingInfo = GetLoadedAssetBundle(abPath); if (existingInfo != null) { existingInfo.m_ReferencedCount++; yield break; } } BeginAssetBundleLoad(abPath); string url = abPath; string prePath; int n = abPath.LastIndexOf("/"); prePath = abPath.Substring(0, n+1); if (type != typeof(AssetBundleManifest)) { //先加载依赖项 string[] dependencies = null; if(!m_Dependencies.TryGetValue(abPath, out dependencies)) //if (m_Dependencies.ContainsKey(abPath)) //{ // dependencies = m_Dependencies[abPath]; //} //else { //if (abPath.Contains("/ui/"))//ui是一种依赖打包方式 //获得ab名 string depPath = abPath.Replace(m_sUpdatePath, ""); //int pos = depPath.LastIndexOf('_'); int beginpos = depPath.LastIndexOf('/')+1; depPath = depPath.Substring(beginpos); depPath = GetUnversionedAssetBundleName(depPath); //depPath = abPath.Replace(m_sLocalPath, ""); //GetDepFilePath(abPath, depPath); if (m_AssetBundleManifest != null) { dependencies = m_AssetBundleManifest.GetAllDependencies(depPath); //GetAllDependencies(Path.GetFileName(abPath)); //else //{//暂时兼容之前的打包方式 // int pos = abPath.IndexOf("base/"); // dependencies = GetAllDependencies(abPath.Substring(pos)); //}; m_Dependencies.Add(abPath, dependencies); } } if (dependencies != null && dependencies.Length > 0) { for (int i = 0; i < dependencies.Length; i++) { string depName = dependencies[i]; AssetBundleInfo bundleInfo = null; //#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR // string abfile = GetDepFilePath(abPath, depName);// abPath.Replace(Path.GetFileName(abPath), depName); //#else //ud 220527 string abfile; abfile = $"{prePath}{depName}";//abPath.Replace(Path.GetFileName(abPath), depName);//{m_sUpdatePath} //#endif bundleInfo = GetLoadedAssetBundle(abfile); if (bundleInfo != null) { bundleInfo.m_ReferencedCount++; } else if (!m_LoadRequests.ContainsKey(abfile) && !m_LoadDependenciesReq.ContainsKey(abfile)) { //加载对应的依赖包 m_LoadDependenciesReq.Add(abfile, 1); //此处需要判定update目录是否有更新文件,没有才从本地目录获取 yield return OnLoadAssetBundle(abfile, type); bundleInfo = GetLoadedAssetBundle(abfile); if (bundleInfo == null) { Debug.LogError("OnLoadAssetBundle Failed--->>>" + abfile); continue;//yield break; } bundleInfo.m_ReferencedCount++; } else { int count = 0; if (m_LoadDependenciesReq.TryGetValue(abfile, out count)) m_LoadDependenciesReq[abfile] = count + 1;//m_LoadDependenciesReq[abfile] + 1; else m_LoadDependenciesReq.Add(abfile, 1); } } } } AssetBundleInfo Info = GetLoadedAssetBundle(abPath); if (Info != null) {//中途有人加载了这个ab包,就不需要加载了 Info.m_ReferencedCount++; EndAssetBundleLoad(abPath); yield break; } //真正完整路径 string cdn; if (!m_dicXidCDN.TryGetValue(m_sCurXid, out cdn)) { Debug.LogError($"No XID({m_sCurXid}) CDN : "); //cdn = ""; EndAssetBundleLoad(abPath); yield break; } url = GetPathMd5Name(abPath); yield return GetAssetsBundleData(cdn, url, (uwr, isRaw) => { if (uwr != null) { //下载是异步的(可能耗时数秒),期间可能有并发请求把同一 AB 加载并注册了。 //若不在 LoadFromMemory 前复查,会对同一内容二次 LoadFromMemory,触发 //"another AssetBundle with the same files is already loaded" 并返回 null。 AssetBundleInfo loadedInfo = GetLoadedAssetBundle(abPath); if (loadedInfo != null) { int waited; if (m_LoadDependenciesReq.TryGetValue(abPath, out waited)) { loadedInfo.m_ReferencedCount += waited; m_LoadDependenciesReq.Remove(abPath); } else { loadedInfo.m_ReferencedCount++; } EndAssetBundleLoad(abPath); return; } AssetBundle ab; if (isRaw) ab = DownloadHandlerAssetBundle.GetContent(uwr); else ab = AssetBundle.LoadFromMemory(uwr.downloadHandler.data); if (ab == null) { Debug.LogError($"Load AB failed: {url}"); EndAssetBundleLoad(abPath); return; } DateTime now = DateTime.Now; //Debug.LogWarning($"Load AB ok: {abPath} {now}"); string cacheKey = GetAssetBundleCacheKey(abPath); m_LoadedAssetBundles[cacheKey] = new AssetBundleInfo(ab); int count; if (m_LoadDependenciesReq.TryGetValue(abPath, out count)) //if (m_LoadDependenciesReq.ContainsKey(abPath)) { AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abPath); if (bundleInfo != null) { bundleInfo.m_ReferencedCount += count - 1;//可能有隐患,如果当前加载不是由依赖引起的,又同时有其他作为依赖加载可能就有问题,虽然可能性很少 } m_LoadDependenciesReq.Remove(abPath); } } else { Debug.LogWarning("Load AB Error: " + url); } EndAssetBundleLoad(abPath); }); yield break; } static AssetBundleInfo GetLoadedAssetBundle(string abName) { AssetBundleInfo bundle = null; if (!m_LoadedAssetBundles.TryGetValue(abName, out bundle)) m_LoadedAssetBundles.TryGetValue(GetAssetBundleCacheKey(abName), out bundle); if (bundle == null) return null; return bundle; } /// /// 释放所有ab包缓存 /// //* static public void UnloadAllAssetBundle() { List strList = new List(); foreach (KeyValuePair kvp in m_LoadedAssetBundles) { if (m_LoadRequests.ContainsKey(kvp.Key)) { //Debug.Log("unload AB Error: " + kvp.Key); --kvp.Value.m_ReferencedCount; continue;//如果当前AB处于Async Loading过程中,卸载会崩溃,只减去引用计数即可 } else { //kvp.Value.m_AssetBundle.Unload(true); strList.Add(kvp.Key); } } Debug.Log("strList count: " + strList.Count); foreach (var name in strList) { if (name == "StreamingAssets.unity3d")//(name.IndexOf("StreamingAssets", StringComparison.OrdinalIgnoreCase) >= 0) { continue; } //Debug.Log("unload: " + name); AssetBundleInfo bundle = GetLoadedAssetBundle(name); if (bundle == null) continue; if (m_LoadRequests.ContainsKey(name)) { --bundle.m_ReferencedCount; continue; //如果当前AB处于Async Loading过程中,卸载会崩溃,只减去引用计数即可 } if (bundle.m_AssetBundle != null) { bundle.m_AssetBundle.Unload(false); } m_LoadedAssetBundles.Remove(name); } objDic.Clear(); m_AssetBundleManifest = null; m_Dependencies.Clear(); m_LoadedAssetBundles.Clear(); m_LoadingAssetBundles.Clear(); }//*/ static public void UnloadRes(string path, bool isThorough = false) { if (path.Length < 2) return; if (objDic.ContainsKey(path)) { objDic.Remove(path); } #if UNITY_EDITOR return; #endif //释放相关资源 /*int npos = path.LastIndexOf('/'); int lastpos = path.LastIndexOf('.'); if (lastpos == -1) { lastpos = 0; } string abname = (path.Substring(0, npos) + m_Postfix).Replace('/', '_'); string abpath = m_sProductPath + "/UI/" + abname; if (!File.Exists(abpath)) { abpath = Application.dataPath + "/UI/" + abname; }//*/ string res; string abpath = GetABResPath(path, out res); UnloadAssetBundle(abpath, isThorough); } /// /// 此函数交给外部卸载专用,自己调整是否需要彻底清除AB /// /// /// static public void UnloadAssetBundle(string abPath, bool isThorough = false) { //Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory before unloading " + abPath); UnloadAssetBundleInternal(abPath, isThorough); //依赖减少需要此资源彻底删除才删 //UnloadDependencies(abPath, isThorough); //Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory after unloading " + abName); } static void UnloadDependencies(string abName, bool isThorough) { string[] dependencies = null; if (!m_Dependencies.TryGetValue(abName, out dependencies)) return; // Loop dependencies. m_Dependencies.Remove(abName);//ud 先去掉依赖以免无限循环 foreach (var dependency in dependencies) { string deppath = abName.Replace(Path.GetFileName(abName), dependency); UnloadAssetBundleInternal(deppath, isThorough); } } static void UnloadAssetBundleInternal(string abName, bool isThorough) { AssetBundleInfo bundle = GetLoadedAssetBundle(abName); if (bundle == null) { //Debug.Log("Free AB Failed: " + abName); return; } if ((--bundle.m_ReferencedCount) <= 0) { if (m_LoadRequests.ContainsKey(abName)) { return; //如果当前AB处于Async Loading过程中,卸载会崩溃,只减去引用计数即可 } UnloadDependencies(abName, isThorough); bundle.m_AssetBundle.Unload(isThorough); m_LoadedAssetBundles.Remove(abName); //Debug.Log(abName + " has been unloaded successfully"); } } public static void Clear() { UnloadAllAssetBundle(); } } }