Files
2026-07-10 10:24:29 +08:00

2462 lines
94 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//---加载模块---
//流程:本地加载(编辑器)=》基础系统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)读取包内文件
//场景加载切换:StartCoroutineLoadingScenePath));在中间场景(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<UObject[]> 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<string, string> m_dicFilelist = new Dictionary<string, string>();
static Dictionary<string, string> m_dicXWorldFilelist = new Dictionary<string, string>();
static Dictionary<string, string> m_dicTempFilelist = new Dictionary<string, string>();
static Dictionary<string, string> m_dicXidCDN = new Dictionary<string, string>();
static Dictionary<string, string> m_dicXidServer = new Dictionary<string, string>();
static Dictionary<string, Dictionary<string, string>> m_dicXidFileList = new Dictionary<string, Dictionary<string, string>>();
static Dictionary<string, AssetBundleManifest> m_dicXidManaifest = new Dictionary<string, AssetBundleManifest>();
static string m_sCurXid = "XWorld";
//static string m_sCurXidServerPath = "http://127.0.0.1:15081";
//Scene
static Dictionary<string, int> m_SceneLoaded = new Dictionary<string, int>();
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<string, Hash128> m_ResHash = new Dictionary<string, Hash128>();
/// <summary>
/// 包依赖关系
/// </summary>
static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();
/// <summary>
/// 已加载的AB包信息
/// </summary>
static Dictionary<string, AssetBundleInfo> m_LoadedAssetBundles = new Dictionary<string, AssetBundleInfo>();
static HashSet<string> m_LoadingAssetBundles = new HashSet<string>();
/// <summary>
/// 包加载队列列表
/// </summary>
static Dictionary<string, List<LoadAssetRequest>> m_LoadRequests = new Dictionary<string, List<LoadAssetRequest>>();
static Dictionary<string, int> m_LoadDependenciesReq = new Dictionary<string, int>();
/// <summary>
/// 对象缓存
/// </summary>
static Dictionary<string, List<UObject>> objDic = new Dictionary<string, List<UObject>>();
static string m_sUpdatePath;
static string m_sLocalPath;
static string m_sProductPath;
static string m_sServerPath;
public delegate void LoadAssetCallback(UnityEngine.GameObject[] resultObjects);//Action<UObject[]> 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<XWCoroutine>();
//if (m_Base == null)
// m_Base = game.AddComponent<XWCoroutine>();
#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/<platform>/ 由 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<byte[]> 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<byte[]> 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<UnityWebRequest> 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<UnityWebRequest> 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<UnityWebRequest, bool> 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;
}
//统一 <CurCDN>/<xid>/<platform>/...,与 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<string, string>();
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>("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<UnityWebRequest> 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<LoadAssetRequest> requests = null;
if (!m_LoadRequests.TryGetValue(abpath, out requests))
{
AssetBundleInfo bundleInfo = GetLoadedAssetBundle(abpath);
if (bundleInfo == null)
{
requests = new List<LoadAssetRequest>();
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<string, string> 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;
}
/// <summary>
/// 加载TextMeshPro Setting
/// </summary>
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资源读取,兼容原来的mapmodel,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/<platform>/<file>_<md5>.unity3dmd5 命名),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<UObject[]> 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<UObject> 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<UObject> 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<AssetBundle> 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<AssetBundle> 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<UObject[]> action)
{
string res;
List<UObject> result = null;
List<string> listPath = new List<string>();
if (type == null)
type = typeof(GameObject);
var res_type = type;
result = new List<UObject>();
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<LoadAssetRequest> requests = null;
if (!m_LoadRequests.TryGetValue(abpath, out requests))
{
requests = new List<LoadAssetRequest>();
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<UObject>(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<UObject> resultLocal = null;
if (!objDic.TryGetValue(request.orginPath, out resultLocal))
{
resultLocal = new List<UObject>();
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<LoadAssetRequest> 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<UObject[]> 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<LoadAssetRequest> requests = null;
if (!m_LoadRequests.TryGetValue(abpath, out requests))
{
requests = new List<LoadAssetRequest>();
requests.Add(request);
m_LoadRequests.Add(abpath, requests);
m_Base.StartCoroutine(OnLoadAsset(abpath, res, path, res_type, null));
}
else
{
requests.Add(request);
}
}
/// <summary>
/// 同步加载AB
/// </summary>
/// <param name="path"></param>
/// <param name="type"></param>
/// <returns></returns>
[Obsolete("资源加载统一用异步:coLoadRes 或回调版 LoadRes(path,type,Action)。同步加载不从CDN下载热更资源,会读到旧APK底包。详见 Doc/Rule/UnityProject.md")]
public static UObject LoadResAB(string path, Type type)
{
//先取缓存
List<UObject> 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<UObject>() { 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<TextAsset>(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;
}
/// <summary>
/// 获取文本资源字符
/// </summary> Plot/1001.BT
/// <param name="filename"></param>
/// <returns></returns>
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<UObject> 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<UObject>() { uo }); //缓存对象
bundleInfo.m_ReferencedCount++;
return uo;
}
/// <summary>
/// 同步加载AB包
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
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<string, AssetBundle> 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<string, AssetBundle> 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);
}
/// <summary>
/// 同步方式加载资源
/// </summary>
/// <param name="path"></param>
/// <param name="type"></param>
/// <returns></returns>
[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<UObject> 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<UObject[]> action = null)
{
//先取缓存
List<UObject> 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<UObject>();
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;
}
/// <summary>
/// 获取图标
/// </summary>
/// <param name="spritePath"></param>
/// <returns></returns>
public static Sprite GetSpriteByName(string spritePath)
{
if (!spritePath.EndsWith(".png"))
{
spritePath += ".png";
}
// List<UObject> 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;
}
/// <summary>
/// 获取图集Sprite序列
/// </summary>
/// <param name="atlasName"></param>
/// <param name="spriteName"></param>
/// <returns></returns>
public static Sprite[] GetSpritesByName(string atlasName, string spriteName)
{
string path = atlasName + "/" + spriteName;
List<Sprite> sprites = new List<Sprite>();
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<UObject> action = null)
{
GameObject getAsset = null;
if (m_ABMode == 0)
{
#if UNITY_EDITOR
//编辑器读取本地资源
string artPath = res;
getAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(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<LoadAssetRequest> 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<UObject> result = null;
if (!objDic.TryGetValue(list[i].orginPath, out result))
{
result = new List<UObject>();
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;
}
/// <summary>
/// 释放所有ab包缓存
/// </summary>
//*
static public void UnloadAllAssetBundle()
{
List<string> strList = new List<string>();
foreach (KeyValuePair<string, AssetBundleInfo> 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);
}
/// <summary>
/// 此函数交给外部卸载专用,自己调整是否需要彻底清除AB
/// </summary>
/// <param name="abName"></param>
/// <param name="isThorough"></param>
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();
}
}
}