AssetBundleUtility.cs 16 KB

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