|
@@ -0,0 +1,343 @@
|
|
|
+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<AssetBundleUtility>();
|
|
|
+ }
|
|
|
+ return instance;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ public static AssetBundleUtility instance;
|
|
|
+
|
|
|
+ private static string FTPPrefix = "file://";
|
|
|
+
|
|
|
+#if UNITY_EDITOR
|
|
|
+ public List<AssetBundleGroup> AssetBundleGroups;
|
|
|
+#endif
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ public static string GetMD5OfAssetBundleSets(List<AssetBundleSet> assetBundleSets)
|
|
|
+ {
|
|
|
+ string md5Dictionary = CreateMD5Dictionary(assetBundleSets, false);
|
|
|
+ byte[] bytes = MD5Utility.GetMD5(md5Dictionary);
|
|
|
+ string md5 = MD5Utility.BytesToString(bytes);
|
|
|
+ return md5;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string CreateMD5Dictionary(List<AssetBundleSet> 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<AssetBundleSet> ParseMD5Dictionary(string md5Dictionary)
|
|
|
+ {
|
|
|
+ List<AssetBundleSet> assetBundleSets = new List<AssetBundleSet>();
|
|
|
+ List<string> 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;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ ///加载AssetBundle
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="allSucceedCallback">所有AssetBundle都加载成功后的回调</param>
|
|
|
+ /// <param name="haveFailedCallback">任意一个AssetBundle加载失败后的回调 如果有多个AssetBundle加载失败 也只回调一次</param>
|
|
|
+ /// <param name="getSucceedCallback">每个AssetBundle加载成功后的回调</param>
|
|
|
+ /// <param name="getFailedCallback">每个AssetBundle加载失败后的回调</param>
|
|
|
+ public static void LoadAllAssetBundle(List<AssetBundleSet> assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action<AssetBundleSet> getSucceedCallback, Action<AssetBundleSet, string> getFailedCallback)
|
|
|
+ {
|
|
|
+ List<AssetBundleSet> staleAssetBundleSets = new List<AssetBundleSet>();
|
|
|
+ foreach (var assetBundleSet in assetBundleSets)
|
|
|
+ {
|
|
|
+ assetBundleSet.GetSucceedCallback = getSucceedCallback;
|
|
|
+ assetBundleSet.GetFailedCallback = getFailedCallback;
|
|
|
+ //Debug.Log($"UpToDateAssetBundleSet : {assetBundleSet.Name}");
|
|
|
+ }
|
|
|
+ LoadAndDownloadAssetBundles(staleAssetBundleSets, assetBundleSets, allSucceedCallback, haveFailedCallback);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ ///加载/下载AssetBundle
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="persistentFolder">下载后AssetBundle存放的目录</param>
|
|
|
+ /// <param name="allSucceedCallback">所有AssetBundle都加载/下载成功后的回调</param>
|
|
|
+ /// <param name="haveFailedCallback">任意一个AssetBundle加载/下载失败后的回调 如果有多个AssetBundle加载/下载失败 也只回调一次</param>
|
|
|
+ /// <param name="getSucceedCallback">每个AssetBundle加载/下载成功后的回调</param>
|
|
|
+ /// <param name="getFailedCallback">每个AssetBundle加载/下载失败后的回调</param>
|
|
|
+ public static void UpdateAllAssetBundle(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action<AssetBundleSet> getSucceedCallback, Action<AssetBundleSet, string> getFailedCallback)
|
|
|
+ {
|
|
|
+ List<AssetBundleSet> staleAssetBundleSets = new List<AssetBundleSet>();
|
|
|
+ List<AssetBundleSet> upToDateAssetBundleSets = new List<AssetBundleSet>();
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void GetStaleAndUpToDateAssetBundleSets(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, List<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> upToDateAssetBundleSets)
|
|
|
+ {
|
|
|
+ List<AssetBundleSet> 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<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> 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();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ ///
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
|
|
|
+ /// <param name="streamingFolder">源AssetBundle存放的目录</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static List<AssetBundleSet> GetAssetBundleSetsFromFolders(string persistentFolder, string streamingFolder)
|
|
|
+ {
|
|
|
+ List<AssetBundleSet> persistentAssetBundleSets = new List<AssetBundleSet>();
|
|
|
+ if (Directory.Exists(persistentFolder))
|
|
|
+ {
|
|
|
+ persistentAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(persistentFolder, persistentFolder));
|
|
|
+ }
|
|
|
+ List<AssetBundleSet> streamingAssetBundleSets = new List<AssetBundleSet>();
|
|
|
+ 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<AssetBundleSet> assetBundleSets = new List<AssetBundleSet>();
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ ///
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="folder">源AssetBundle存放的目录</param>
|
|
|
+ /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private static List<AssetBundleSet> GetAllAssetBundleSetFromFolder(string folder, string persistentFolder)
|
|
|
+ {
|
|
|
+ List<string> pathes = new List<string>();
|
|
|
+ List<string> names = new List<string>();
|
|
|
+ List<string> md5s = new List<string>();
|
|
|
+ List<string> savePathes = new List<string>();
|
|
|
+ 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<AssetBundleSet> results = new List<AssetBundleSet>();
|
|
|
+ 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<string> pathes)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < pathes.Count; i++)
|
|
|
+ {
|
|
|
+ if (pathes[i].EndsWith(".meta"))
|
|
|
+ {
|
|
|
+ pathes.RemoveAt(i--);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void RemoveManifestFilePath(List<string> 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);
|
|
|
+ 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);
|
|
|
+ succeedCallback.Invoke();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|