namespace assetBundleUtility { using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; public class AssetBundleUtility : MonoBehaviour { #region Config public static AssetBundleUtility Instance { get { if (instance == null) { GameObject go = new GameObject("AssetBundleUtility"); DontDestroyOnLoad(go); instance = go.AddComponent(); } return instance; } } public static AssetBundleUtility instance; private static string FTPPrefix = "file://"; #if UNITY_EDITOR public List AssetBundleGroups; #endif #endregion public static string GetMD5OfAssetBundleSets(List assetBundleSets) { string md5Dictionary = CreateMD5Dictionary(assetBundleSets, false); byte[] bytes = MD5Utility.GetMD5(md5Dictionary); string md5 = MD5Utility.BytesToString(bytes); return md5; } public static string CreateMD5Dictionary(List assetBundleSets, bool includeURL) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < assetBundleSets.Count; i++) { stringBuilder.Append($"{assetBundleSets[i].Name}|{assetBundleSets[i].MD5}"); if (includeURL) { stringBuilder.Append($"|{assetBundleSets[i].URL}"); } if (i < assetBundleSets.Count - 1) { stringBuilder.Append("\r\n"); } } return stringBuilder.ToString(); } private static List ParseMD5Dictionary(string md5Dictionary) { List assetBundleSets = new List(); List items = md5Dictionary.Trim().Split(new[] { "\r\n" }, StringSplitOptions.None).ToList(); for (int i = 0; i < items.Count; i++) { AssetBundleSet assetBundleSet = new AssetBundleSet(); string[] strings = items[i].Split('|'); assetBundleSet.Name = strings[0]; assetBundleSet.MD5 = strings[1]; assetBundleSet.URL = strings[2]; assetBundleSets.Add(assetBundleSet); } return assetBundleSets; } /// ///加载AssetBundle /// /// 所有AssetBundle都加载成功后的回调 /// 任意一个AssetBundle加载失败后的回调 如果有多个AssetBundle加载失败 也只回调一次 /// 每个AssetBundle加载成功后的回调 /// 每个AssetBundle加载失败后的回调 public static void LoadAllAssetBundle(List assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action getSucceedCallback, Action getFailedCallback) { List staleAssetBundleSets = new List(); foreach (var assetBundleSet in assetBundleSets) { assetBundleSet.GetSucceedCallback = getSucceedCallback; assetBundleSet.GetFailedCallback = getFailedCallback; //Debug.Log($"UpToDateAssetBundleSet : {assetBundleSet.Name}"); } LoadAndDownloadAssetBundles(staleAssetBundleSets, assetBundleSets, allSucceedCallback, haveFailedCallback); } /// ///加载/下载AssetBundle /// /// 下载后AssetBundle存放的目录 /// 所有AssetBundle都加载/下载成功后的回调 /// 任意一个AssetBundle加载/下载失败后的回调 如果有多个AssetBundle加载/下载失败 也只回调一次 /// 每个AssetBundle加载/下载成功后的回调 /// 每个AssetBundle加载/下载失败后的回调 public static int UpdateAllAssetBundle(string md5Dictionary, string persistentFolder, List assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action getSucceedCallback, Action getFailedCallback) { List staleAssetBundleSets = new List(); List upToDateAssetBundleSets = new List(); GetStaleAndUpToDateAssetBundleSets(md5Dictionary, persistentFolder, assetBundleSets, staleAssetBundleSets, upToDateAssetBundleSets); foreach (var staleAssetBundleSet in staleAssetBundleSets) { staleAssetBundleSet.GetSucceedCallback = getSucceedCallback; staleAssetBundleSet.GetFailedCallback = getFailedCallback; //Debug.Log($"StaleAssetBundleSet : {staleAssetBundleSet.Name}"); } foreach (var upToDateAssetBundleSet in upToDateAssetBundleSets) { upToDateAssetBundleSet.GetSucceedCallback = getSucceedCallback; upToDateAssetBundleSet.GetFailedCallback = getFailedCallback; //Debug.Log($"UpToDateAssetBundleSet : {upToDateAssetBundleSet.Name}"); } LoadAndDownloadAssetBundles(staleAssetBundleSets, upToDateAssetBundleSets, allSucceedCallback, haveFailedCallback); return staleAssetBundleSets.Count; } private static void GetStaleAndUpToDateAssetBundleSets(string md5Dictionary, string persistentFolder, List assetBundleSets, List staleAssetBundleSets, List upToDateAssetBundleSets) { List serverAssetBundleSets = ParseMD5Dictionary(md5Dictionary); foreach (var serverAssetBundleSet in serverAssetBundleSets) { bool missingFlag = true; foreach (var assetBundleSet in assetBundleSets) { if (assetBundleSet.Name != serverAssetBundleSet.Name) continue; if (assetBundleSet.MD5 == serverAssetBundleSet.MD5) { missingFlag = false; upToDateAssetBundleSets.Add(assetBundleSet); break; } else { assetBundleSet.URL = serverAssetBundleSet.URL; staleAssetBundleSets.Add(assetBundleSet); missingFlag = false; break; } } if (missingFlag) { serverAssetBundleSet.PersistentPath = $"{persistentFolder}{Path.DirectorySeparatorChar}{serverAssetBundleSet.Name}"; staleAssetBundleSets.Add(serverAssetBundleSet); } } } private static void LoadAndDownloadAssetBundles(List staleAssetBundleSets, List upToDateAssetBundleSets, Action allSucceedCallback, Action haveFailedCallback) { int remainAmount = upToDateAssetBundleSets.Count + staleAssetBundleSets.Count; bool failedFlag = false; foreach (var assetBundleSets in upToDateAssetBundleSets) { LoadBundle ( assetBundleSets, () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback), () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback) ); } foreach (var assetBundleSets in staleAssetBundleSets) { DownloadBundle ( assetBundleSets, () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback), () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback) ); } } private static void LoadAndDownloadAssetBundleSucceedCallback(ref int remainAmount, Action allSucceedCallback) { if (--remainAmount == 0) allSucceedCallback.Invoke(); } private static void LoadAndDownloadAssetBundleFailedCallback(ref bool failedFlag, Action haveFailedCallback) { if (failedFlag) return; failedFlag = true; haveFailedCallback.Invoke(); } /// /// /// /// 更新后AssetBundle存放的目录 /// 源AssetBundle存放的目录 /// public static List GetAssetBundleSetsFromFolders(string persistentFolder/*, string streamingFolder*/) { List persistentAssetBundleSets = new List(); if (Directory.Exists(persistentFolder)) { persistentAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(persistentFolder, persistentFolder)); } //List streamingAssetBundleSets = new List(); //streamingAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(streamingFolder, persistentFolder)); // for (int i = 0; i < streamingAssetBundleSets.Count; i++) //{ // foreach (var persistentAssetBundleSet in persistentAssetBundleSets) // { // if (streamingAssetBundleSets[i].Name != persistentAssetBundleSet.Name) continue; // streamingAssetBundleSets.RemoveAt(i--); // break; // } //} List assetBundleSets = new List(); //assetBundleSets.AddRange(streamingAssetBundleSets); assetBundleSets.AddRange(persistentAssetBundleSets); for (int i = 0; i < assetBundleSets.Count; i++) { assetBundleSets[i].StreamingPath = assetBundleSets[i].StreamingPath.Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString()); } assetBundleSets.MySort((bundleSet0, bundleSet1) => SortExtension.CompareASCII(bundleSet1.Name, bundleSet0.Name)); return assetBundleSets; } /// /// /// /// 源AssetBundle存放的目录 /// 更新后AssetBundle存放的目录 /// private static List GetAllAssetBundleSetFromFolder(string folder, string persistentFolder) { List pathes = new List(); List names = new List(); List md5s = new List(); List savePathes = new List(); pathes.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories)); RemoveMetaFilePath(pathes); RemoveManifestFilePath(pathes); names = pathes.GetAllFileNameFromPath(); md5s = MD5Utility.GetAllMD5StringFromPath(pathes); savePathes = pathes.ReplaceAll(folder, persistentFolder); for (int i = 0; i < pathes.Count; i++) { pathes[i] = pathes[i].Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString()); } List results = new List(); for (int i = 0; i < names.Count; i++) { AssetBundleSet assetBundleSet = new AssetBundleSet(); assetBundleSet.StreamingPath = pathes[i]; assetBundleSet.Name = names[i]; assetBundleSet.MD5 = md5s[i]; assetBundleSet.PersistentPath = savePathes[i]; results.Add(assetBundleSet); } return results; } private static void RemoveMetaFilePath(List pathes) { for (int i = 0; i < pathes.Count; i++) { if (pathes[i].EndsWith(".meta")) { pathes.RemoveAt(i--); } } } private static void RemoveManifestFilePath(List pathes) { for (int i = 0; i < pathes.Count; i++) { if (pathes[i].EndsWith(".manifest")) { pathes.RemoveAt(i--); } } } public static void LoadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback) { Instance.StartCoroutine(loadBundle(assetBundleSet, succeedCallback, failedCallback)); } public static void DownloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback) { Instance.StartCoroutine(downloadBundle(assetBundleSet, succeedCallback, failedCallback)); } private static IEnumerator loadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback) { WWW www = new WWW(FTPPrefix + assetBundleSet.StreamingPath); yield return www; if (!string.IsNullOrEmpty(www.error)) { assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error); failedCallback.Invoke(); yield break; } assetBundleSet.AssetBundle = www.assetBundle; assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet, false); succeedCallback.Invoke(); } private static IEnumerator downloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback) { WWW www = new WWW(assetBundleSet.URL); yield return www; if (!string.IsNullOrEmpty(www.error)) { assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error); failedCallback.Invoke(); yield break; } if (!Directory.Exists(assetBundleSet.PersistentPath)) { Directory.CreateDirectory(Path.GetDirectoryName(assetBundleSet.PersistentPath)); } File.WriteAllBytes(assetBundleSet.PersistentPath, www.bytes); assetBundleSet.AssetBundle = www.assetBundle; assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet, true); succeedCallback.Invoke(); } } }