AssetBundleUtility.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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> 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 void UpdateAllAssetBundle(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, Action allSucceedCallback, Action haveFailedCallback, Action<AssetBundleSet> 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. }
  116. private static void GetStaleAndUpToDateAssetBundleSets(string md5Dictionary, string persistentFolder, List<AssetBundleSet> assetBundleSets, List<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> upToDateAssetBundleSets)
  117. {
  118. List<AssetBundleSet> serverAssetBundleSets = ParseMD5Dictionary(md5Dictionary);
  119. foreach (var serverAssetBundleSet in serverAssetBundleSets)
  120. {
  121. bool missingFlag = true;
  122. foreach (var assetBundleSet in assetBundleSets)
  123. {
  124. if (assetBundleSet.Name != serverAssetBundleSet.Name) continue;
  125. if (assetBundleSet.MD5 == serverAssetBundleSet.MD5)
  126. {
  127. missingFlag = false;
  128. upToDateAssetBundleSets.Add(assetBundleSet);
  129. break;
  130. }
  131. else
  132. {
  133. assetBundleSet.URL = serverAssetBundleSet.URL;
  134. staleAssetBundleSets.Add(assetBundleSet);
  135. missingFlag = false;
  136. break;
  137. }
  138. }
  139. if (missingFlag)
  140. {
  141. serverAssetBundleSet.PersistentPath = $"{persistentFolder}{Path.DirectorySeparatorChar}{serverAssetBundleSet.Name}";
  142. staleAssetBundleSets.Add(serverAssetBundleSet);
  143. }
  144. }
  145. }
  146. private static void LoadAndDownloadAssetBundles(List<AssetBundleSet> staleAssetBundleSets, List<AssetBundleSet> upToDateAssetBundleSets, Action allSucceedCallback, Action haveFailedCallback)
  147. {
  148. int remainAmount = upToDateAssetBundleSets.Count + staleAssetBundleSets.Count;
  149. bool failedFlag = false;
  150. foreach (var assetBundleSets in upToDateAssetBundleSets)
  151. {
  152. LoadBundle
  153. (
  154. assetBundleSets,
  155. () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback),
  156. () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback)
  157. );
  158. }
  159. foreach (var assetBundleSets in staleAssetBundleSets)
  160. {
  161. DownloadBundle
  162. (
  163. assetBundleSets,
  164. () => LoadAndDownloadAssetBundleSucceedCallback(ref remainAmount, allSucceedCallback),
  165. () => LoadAndDownloadAssetBundleFailedCallback(ref failedFlag, haveFailedCallback)
  166. );
  167. }
  168. }
  169. private static void LoadAndDownloadAssetBundleSucceedCallback(ref int remainAmount, Action allSucceedCallback)
  170. {
  171. if (--remainAmount == 0) allSucceedCallback.Invoke();
  172. }
  173. private static void LoadAndDownloadAssetBundleFailedCallback(ref bool failedFlag, Action haveFailedCallback)
  174. {
  175. if (failedFlag) return;
  176. failedFlag = true;
  177. haveFailedCallback.Invoke();
  178. }
  179. /// <summary>
  180. ///
  181. /// </summary>
  182. /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
  183. /// <param name="streamingFolder">源AssetBundle存放的目录</param>
  184. /// <returns></returns>
  185. public static List<AssetBundleSet> GetAssetBundleSetsFromFolders(string persistentFolder/*, string streamingFolder*/)
  186. {
  187. List<AssetBundleSet> persistentAssetBundleSets = new List<AssetBundleSet>();
  188. if (Directory.Exists(persistentFolder))
  189. {
  190. persistentAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(persistentFolder, persistentFolder));
  191. }
  192. //List<AssetBundleSet> streamingAssetBundleSets = new List<AssetBundleSet>();
  193. //streamingAssetBundleSets.AddRange(GetAllAssetBundleSetFromFolder(streamingFolder, persistentFolder));
  194. // for (int i = 0; i < streamingAssetBundleSets.Count; i++)
  195. //{
  196. // foreach (var persistentAssetBundleSet in persistentAssetBundleSets)
  197. // {
  198. // if (streamingAssetBundleSets[i].Name != persistentAssetBundleSet.Name) continue;
  199. // streamingAssetBundleSets.RemoveAt(i--);
  200. // break;
  201. // }
  202. //}
  203. List<AssetBundleSet> assetBundleSets = new List<AssetBundleSet>();
  204. //assetBundleSets.AddRange(streamingAssetBundleSets);
  205. assetBundleSets.AddRange(persistentAssetBundleSets);
  206. for (int i = 0; i < assetBundleSets.Count; i++)
  207. {
  208. assetBundleSets[i].StreamingPath = assetBundleSets[i].StreamingPath.Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString());
  209. }
  210. assetBundleSets.MySort((bundleSet0, bundleSet1) => SortExtension.CompareASCII(bundleSet1.Name, bundleSet0.Name));
  211. return assetBundleSets;
  212. }
  213. /// <summary>
  214. ///
  215. /// </summary>
  216. /// <param name="folder">源AssetBundle存放的目录</param>
  217. /// <param name="persistentFolder">更新后AssetBundle存放的目录</param>
  218. /// <returns></returns>
  219. private static List<AssetBundleSet> GetAllAssetBundleSetFromFolder(string folder, string persistentFolder)
  220. {
  221. List<string> pathes = new List<string>();
  222. List<string> names = new List<string>();
  223. List<string> md5s = new List<string>();
  224. List<string> savePathes = new List<string>();
  225. pathes.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories));
  226. RemoveMetaFilePath(pathes);
  227. RemoveManifestFilePath(pathes);
  228. names = pathes.GetAllFileNameFromPath();
  229. md5s = MD5Utility.GetAllMD5StringFromPath(pathes);
  230. savePathes = pathes.ReplaceAll(folder, persistentFolder);
  231. for (int i = 0; i < pathes.Count; i++)
  232. {
  233. pathes[i] = pathes[i].Replace("\\", Path.DirectorySeparatorChar.ToString()).Replace("/", Path.DirectorySeparatorChar.ToString());
  234. }
  235. List<AssetBundleSet> results = new List<AssetBundleSet>();
  236. for (int i = 0; i < names.Count; i++)
  237. {
  238. AssetBundleSet assetBundleSet = new AssetBundleSet();
  239. assetBundleSet.StreamingPath = pathes[i];
  240. assetBundleSet.Name = names[i];
  241. assetBundleSet.MD5 = md5s[i];
  242. assetBundleSet.PersistentPath = savePathes[i];
  243. results.Add(assetBundleSet);
  244. }
  245. return results;
  246. }
  247. private static void RemoveMetaFilePath(List<string> pathes)
  248. {
  249. for (int i = 0; i < pathes.Count; i++)
  250. {
  251. if (pathes[i].EndsWith(".meta"))
  252. {
  253. pathes.RemoveAt(i--);
  254. }
  255. }
  256. }
  257. private static void RemoveManifestFilePath(List<string> pathes)
  258. {
  259. for (int i = 0; i < pathes.Count; i++)
  260. {
  261. if (pathes[i].EndsWith(".manifest"))
  262. {
  263. pathes.RemoveAt(i--);
  264. }
  265. }
  266. }
  267. public static void LoadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  268. {
  269. Instance.StartCoroutine(loadBundle(assetBundleSet, succeedCallback, failedCallback));
  270. }
  271. public static void DownloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  272. {
  273. Instance.StartCoroutine(downloadBundle(assetBundleSet, succeedCallback, failedCallback));
  274. }
  275. private static IEnumerator loadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  276. {
  277. WWW www = new WWW(FTPPrefix + assetBundleSet.StreamingPath);
  278. yield return www;
  279. if (!string.IsNullOrEmpty(www.error))
  280. {
  281. assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error);
  282. failedCallback.Invoke();
  283. yield break;
  284. }
  285. assetBundleSet.AssetBundle = www.assetBundle;
  286. assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet);
  287. succeedCallback.Invoke();
  288. }
  289. private static IEnumerator downloadBundle(AssetBundleSet assetBundleSet, Action succeedCallback, Action failedCallback)
  290. {
  291. WWW www = new WWW(assetBundleSet.URL);
  292. yield return www;
  293. if (!string.IsNullOrEmpty(www.error))
  294. {
  295. assetBundleSet.GetFailedCallback.Invoke(assetBundleSet, www.error);
  296. failedCallback.Invoke();
  297. yield break;
  298. }
  299. if (!Directory.Exists(assetBundleSet.PersistentPath))
  300. {
  301. Directory.CreateDirectory(Path.GetDirectoryName(assetBundleSet.PersistentPath));
  302. }
  303. File.WriteAllBytes(assetBundleSet.PersistentPath, www.bytes);
  304. assetBundleSet.AssetBundle = www.assetBundle;
  305. assetBundleSet.GetSucceedCallback.Invoke(assetBundleSet);
  306. succeedCallback.Invoke();
  307. }
  308. }
  309. }