AssetBundleUtility.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. namespace assetBundleUtility
  2. {
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using UnityEngine;
  10. public class AssetBundleUtility : MonoBehaviour
  11. {
  12. #region Config
  13. public static AssetBundleUtility Instance
  14. {
  15. get
  16. {
  17. if (instance == null)
  18. {
  19. GameObject go = new GameObject("AssetBundleUtility");
  20. DontDestroyOnLoad(go);
  21. instance = go.AddComponent<AssetBundleUtility>();
  22. }
  23. return instance;
  24. }
  25. }
  26. public static AssetBundleUtility instance;
  27. private static string FTPPrefix = "file://";
  28. #if UNITY_EDITOR
  29. public List<AssetBundleGroup> AssetBundleGroups;
  30. #endif
  31. #endregion
  32. public static string GetMD5OfAssetBundleSets(List<AssetBundleSet> assetBundleSets)
  33. {
  34. string md5Dictionary = CreateMD5Dictionary(assetBundleSets, false);
  35. byte[] bytes = MD5Utility.GetMD5(md5Dictionary);
  36. string md5 = MD5Utility.BytesToString(bytes);
  37. return md5;
  38. }
  39. public static string CreateMD5Dictionary(List<AssetBundleSet> assetBundleSets, bool includeURL)
  40. {
  41. StringBuilder stringBuilder = new StringBuilder();
  42. for (int i = 0; i < assetBundleSets.Count; i++)
  43. {
  44. stringBuilder.Append($"{assetBundleSets[i].Name}|{assetBundleSets[i].MD5}");
  45. if (includeURL)
  46. {
  47. stringBuilder.Append($"|{assetBundleSets[i].URL}");
  48. }
  49. if (i < assetBundleSets.Count - 1)
  50. {
  51. stringBuilder.Append("\r\n");
  52. }
  53. }
  54. return stringBuilder.ToString();
  55. }
  56. private static List<AssetBundleSet> ParseMD5Dictionary(string md5Dictionary)
  57. {
  58. List<AssetBundleSet> assetBundleSets = new List<AssetBundleSet>();
  59. List<string> items = md5Dictionary.Trim().Split(new[] { "\r\n" }, StringSplitOptions.None).ToList();
  60. for (int i = 0; i < items.Count; i++)
  61. {
  62. AssetBundleSet assetBundleSet = new AssetBundleSet();
  63. string[] strings = items[i].Split('|');
  64. assetBundleSet.Name = strings[0];
  65. assetBundleSet.MD5 = strings[1];
  66. assetBundleSet.URL = strings[2];
  67. assetBundleSets.Add(assetBundleSet);
  68. }
  69. return assetBundleSets;
  70. }
  71. /// <summary>
  72. ///加载AssetBundle
  73. /// </summary>
  74. /// <param name="allSucceedCallback">所有AssetBundle都加载成功后的回调</param>
  75. /// <param name="haveFailedCallback">任意一个AssetBundle加载失败后的回调 如果有多个AssetBundle加载失败 也只回调一次</param>
  76. /// <param name="getSucceedCallback">每个AssetBundle加载成功后的回调</param>
  77. /// <param name="getFailedCallback">每个AssetBundle加载失败后的回调</param>
  78. public static void LoadAllAssetBundle(List<AssetBundleSet> assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action<AssetBundleSet, bool> getSucceedCallback, Action<AssetBundleSet, string> getFailedCallback)
  79. {
  80. List<AssetBundleSet> staleAssetBundleSets = new List<AssetBundleSet>();
  81. foreach (var assetBundleSet in assetBundleSets)
  82. {
  83. assetBundleSet.GetSucceedCallback = getSucceedCallback;
  84. assetBundleSet.GetFailedCallback = getFailedCallback;
  85. //Debug.Log($"UpToDateAssetBundleSet : {assetBundleSet.Name}");
  86. }
  87. LoadAndDownloadAssetBundles(staleAssetBundleSets, assetBundleSets, allSucceedCallback, haveFailedCallback);
  88. }
  89. /// <summary>
  90. ///加载/下载AssetBundle
  91. /// </summary>
  92. /// <param name="persistentFolder">下载后AssetBundle存放的目录</param>
  93. /// <param name="allSucceedCallback">所有AssetBundle都加载/下载成功后的回调</param>
  94. /// <param name="haveFailedCallback">任意一个AssetBundle加载/下载失败后的回调 如果有多个AssetBundle加载/下载失败 也只回调一次</param>
  95. /// <param name="getSucceedCallback">每个AssetBundle加载/下载成功后的回调</param>
  96. /// <param name="getFailedCallback">每个AssetBundle加载/下载失败后的回调</param>
  97. public static int UpdateAllAssetBundle(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action<AssetBundleSet, bool> getSucceedCallback, Action<AssetBundleSet, string> getFailedCallback)
  98. {
  99. List<AssetBundleSet> staleAssetBundleSets = new List<AssetBundleSet>();
  100. List<AssetBundleSet> upToDateAssetBundleSets = new List<AssetBundleSet>();
  101. GetStaleAndUpToDateAssetBundleSets(md5Dictionary, persistentFolder, assetBundleSets, staleAssetBundleSets, upToDateAssetBundleSets);
  102. foreach (var staleAssetBundleSet in staleAssetBundleSets)
  103. {
  104. staleAssetBundleSet.GetSucceedCallback = getSucceedCallback;
  105. staleAssetBundleSet.GetFailedCallback = getFailedCallback;
  106. //Debug.Log($"StaleAssetBundleSet : {staleAssetBundleSet.Name}");
  107. }
  108. foreach (var upToDateAssetBundleSet in upToDateAssetBundleSets)
  109. {
  110. upToDateAssetBundleSet.GetSucceedCallback = getSucceedCallback;
  111. upToDateAssetBundleSet.GetFailedCallback = getFailedCallback;
  112. //Debug.Log($"UpToDateAssetBundleSet : {upToDateAssetBundleSet.Name}");
  113. }
  114. LoadAndDownloadAssetBundles(staleAssetBundleSets, upToDateAssetBundleSets, allSucceedCallback, haveFailedCallback);
  115. return staleAssetBundleSets.Count;
  116. }
  117. private static void GetStaleAndUpToDateAssetBundleSets(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, List<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> upToDateAssetBundleSets)
  118. {
  119. List<AssetBundleSet> serverAssetBundleSets = ParseMD5Dictionary(md5Dictionary);
  120. foreach (var serverAssetBundleSet in serverAssetBundleSets)
  121. {
  122. bool missingFlag = true;
  123. foreach (var assetBundleSet in assetBundleSets)
  124. {
  125. if (assetBundleSet.Name != serverAssetBundleSet.Name) continue;
  126. if (assetBundleSet.MD5 == serverAssetBundleSet.MD5)
  127. {
  128. missingFlag = false;
  129. upToDateAssetBundleSets.Add(assetBundleSet);
  130. break;
  131. }
  132. else
  133. {
  134. assetBundleSet.URL = serverAssetBundleSet.URL;
  135. staleAssetBundleSets.Add(assetBundleSet);
  136. missingFlag = false;
  137. break;
  138. }
  139. }
  140. if (missingFlag)
  141. {
  142. serverAssetBundleSet.PersistentPath = $"{persistentFolder}{Path.DirectorySeparatorChar}{serverAssetBundleSet.Name}";
  143. staleAssetBundleSets.Add(serverAssetBundleSet);
  144. }
  145. }
  146. }
  147. private static void LoadAndDownloadAssetBundles(List<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> upToDateAssetBundleSets, Action allSucceedCallback, Action haveFailedCallback)
  148. {
  149. int remainAmount = upToDateAssetBundleSets.Count + staleAssetBundleSets.Count;
  150. bool failedFlag = false;
  151. foreach (var assetBundleSets in upToDateAssetBundleSets)
  152. {
  153. LoadBundle
  154. (
  155. assetBundleSets,
  156. () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback),
  157. () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback)
  158. );
  159. }
  160. foreach (var assetBundleSets in staleAssetBundleSets)
  161. {
  162. DownloadBundle
  163. (
  164. assetBundleSets,
  165. () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback),
  166. () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback)
  167. );
  168. }
  169. }
  170. private static void LoadAndDownloadAssetBundleSucceedCallback(ref int remainAmount, Action allSucceedCallback)
  171. {
  172. if (--remainAmount == 0) allSucceedCallback.Invoke();
  173. }
  174. private static void LoadAndDownloadAssetBundleFailedCallback(ref bool failedFlag, Action haveFailedCallback)
  175. {
  176. if (failedFlag) return;
  177. failedFlag = true;
  178. haveFailedCallback.Invoke();
  179. }
  180. /// <summary>
  181. ///
  182. /// </summary>
  183. /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
  184. /// <param name="streamingFolder">源AssetBundle存放的目录</param>
  185. /// <returns></returns>
  186. public static List<AssetBundleSet> GetAssetBundleSetsFromFolders(string persistentFolder/*, string streamingFolder*/)
  187. {
  188. List<AssetBundleSet> persistentAssetBundleSets = new List<AssetBundleSet>();
  189. if (Directory.Exists(persistentFolder))
  190. {
  191. persistentAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(persistentFolder, persistentFolder));
  192. }
  193. //List<AssetBundleSet> streamingAssetBundleSets = new List<AssetBundleSet>();
  194. //streamingAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(streamingFolder, persistentFolder));
  195. // for (int i = 0; i < streamingAssetBundleSets.Count; i++)
  196. //{
  197. // foreach (var persistentAssetBundleSet in persistentAssetBundleSets)
  198. // {
  199. // if (streamingAssetBundleSets[i].Name != persistentAssetBundleSet.Name) continue;
  200. // streamingAssetBundleSets.RemoveAt(i--);
  201. // break;
  202. // }
  203. //}
  204. List<AssetBundleSet> assetBundleSets = new List<AssetBundleSet>();
  205. //assetBundleSets.AddRange(streamingAssetBundleSets);
  206. assetBundleSets.AddRange(persistentAssetBundleSets);
  207. for (int i = 0; i < assetBundleSets.Count; i++)
  208. {
  209. assetBundleSets[i].StreamingPath = assetBundleSets[i].StreamingPath.Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString());
  210. }
  211. assetBundleSets.MySort((bundleSet0, bundleSet1) => SortExtension.CompareASCII(bundleSet1.Name, bundleSet0.Name));
  212. return assetBundleSets;
  213. }
  214. /// <summary>
  215. ///
  216. /// </summary>
  217. /// <param name="folder">源AssetBundle存放的目录</param>
  218. /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
  219. /// <returns></returns>
  220. private static List<AssetBundleSet> GetAllAssetBundleSetFromFolder(string folder, string persistentFolder)
  221. {
  222. List<string> pathes = new List<string>();
  223. List<string> names = new List<string>();
  224. List<string> md5s = new List<string>();
  225. List<string> savePathes = new List<string>();
  226. pathes.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories));
  227. RemoveMetaFilePath(pathes);
  228. RemoveManifestFilePath(pathes);
  229. names = pathes.GetAllFileNameFromPath();
  230. md5s = MD5Utility.GetAllMD5StringFromPath(pathes);
  231. savePathes = pathes.ReplaceAll(folder, persistentFolder);
  232. for (int i = 0; i < pathes.Count; i++)
  233. {
  234. pathes[i] = pathes[i].Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString());
  235. }
  236. List<AssetBundleSet> results = new List<AssetBundleSet>();
  237. for (int i = 0; i < names.Count; i++)
  238. {
  239. AssetBundleSet assetBundleSet = new AssetBundleSet();
  240. assetBundleSet.StreamingPath = pathes[i];
  241. assetBundleSet.Name = names[i];
  242. assetBundleSet.MD5 = md5s[i];
  243. assetBundleSet.PersistentPath = savePathes[i];
  244. results.Add(assetBundleSet);
  245. }
  246. return results;
  247. }
  248. private static void RemoveMetaFilePath(List<string> pathes)
  249. {
  250. for (int i = 0; i < pathes.Count; i++)
  251. {
  252. if (pathes[i].EndsWith(".meta"))
  253. {
  254. pathes.RemoveAt(i--);
  255. }
  256. }
  257. }
  258. private static void RemoveManifestFilePath(List<string> pathes)
  259. {
  260. for (int i = 0; i < pathes.Count; i++)
  261. {
  262. if (pathes[i].EndsWith(".manifest"))
  263. {
  264. pathes.RemoveAt(i--);
  265. }
  266. }
  267. }
  268. public static void LoadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  269. {
  270. Instance.StartCoroutine(loadBundle(assetBundleSet, succeedCallback, failedCallback));
  271. }
  272. public static void DownloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  273. {
  274. Instance.StartCoroutine(downloadBundle(assetBundleSet, succeedCallback, failedCallback));
  275. }
  276. private static IEnumerator loadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  277. {
  278. WWW www = new WWW(FTPPrefix + assetBundleSet.StreamingPath);
  279. yield return www;
  280. if (!string.IsNullOrEmpty(www.error))
  281. {
  282. assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error);
  283. failedCallback.Invoke();
  284. yield break;
  285. }
  286. assetBundleSet.AssetBundle = www.assetBundle;
  287. assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet, false);
  288. succeedCallback.Invoke();
  289. }
  290. private static IEnumerator downloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  291. {
  292. WWW www = new WWW(assetBundleSet.URL);
  293. yield return www;
  294. if (!string.IsNullOrEmpty(www.error))
  295. {
  296. assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error);
  297. failedCallback.Invoke();
  298. yield break;
  299. }
  300. if (!Directory.Exists(assetBundleSet.PersistentPath))
  301. {
  302. Directory.CreateDirectory(Path.GetDirectoryName(assetBundleSet.PersistentPath));
  303. }
  304. File.WriteAllBytes(assetBundleSet.PersistentPath, www.bytes);
  305. assetBundleSet.AssetBundle = www.assetBundle;
  306. assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet, true);
  307. succeedCallback.Invoke();
  308. }
  309. }
  310. }