AB包工具

AssetBudleTool.cs 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Security.Cryptography;
  6. using System.Text;
  7. using TModule.Runtime;
  8. using UnityEditor;
  9. using UnityEngine;
  10. using LitJson;
  11. namespace TModule.Editor
  12. {
  13. public class AssetBudleTool : EditorWindow
  14. {
  15. private string AssetBudleConfigName= "AssetBundleConfig.asset";
  16. private string AssetBudleConfigPath;
  17. private string AssetbudleToolConfigName = "BudleToolConfig.asset";
  18. private string AssetbudleToolConfigPath;
  19. private AssetBudleConfig _list;
  20. private AssetBudleConfig BudleConfig
  21. {
  22. get
  23. {
  24. _list = _list ?? AssetDatabase.LoadAssetAtPath<AssetBudleConfig>(AssetBudleConfigPath);
  25. if(_list==null)
  26. {
  27. _list = CreateInstance<AssetBudleConfig>();
  28. AssetDatabase.CreateAsset(_list, AssetBudleConfigPath);
  29. }
  30. return _list;
  31. }
  32. set => _list = value;
  33. }
  34. private AssetBudleToolConfig _toolConfig;
  35. private AssetBudleToolConfig BudleToolConfig
  36. {
  37. get
  38. {
  39. _toolConfig = _toolConfig ?? AssetDatabase.LoadAssetAtPath<AssetBudleToolConfig>(AssetbudleToolConfigPath);
  40. if(_toolConfig==null)
  41. {
  42. _toolConfig = CreateInstance<AssetBudleToolConfig>();
  43. AssetDatabase.CreateAsset(_toolConfig, AssetbudleToolConfigPath);
  44. }
  45. return _toolConfig;
  46. }
  47. set => _toolConfig = value;
  48. }
  49. private static List<VersionDataEntity> versionDatas = new List<VersionDataEntity>();
  50. private Vector2 _scorllView = Vector2.zero;
  51. #if UNITY_STANDALONE_WIN
  52. private BuildTarget _buildTarget = BuildTarget.StandaloneWindows64;
  53. #elif UNITY_ANDROID
  54. private BuildTarget _buildTarget = BuildTarget.Android;
  55. #elif UNITY_IPHON
  56. private BuildTarget _buildTarget = BuildTarget.iOS;
  57. #endif
  58. [MenuItem("TModule/热更/AssetBudleTool")]
  59. public static void OpenWin()
  60. {
  61. AssetBudleTool win = GetWindow<AssetBudleTool>();
  62. win.maxSize = new Vector2(800, 400);
  63. win.titleContent.text = "ABTool";
  64. win.titleContent.image = EditorGUIUtility.IconContent("PrefabModel Icon").image;
  65. win.Show();
  66. }
  67. private void OnEnable()
  68. {
  69. var ms = MonoScript.FromScriptableObject(this);
  70. var path = AssetDatabase.GetAssetPath(ms);
  71. path = Path.GetDirectoryName(path);
  72. AssetBudleConfigPath = Path.Combine(path, AssetBudleConfigName);
  73. AssetbudleToolConfigPath = Path.Combine(path, AssetbudleToolConfigName);
  74. }
  75. private void OnGUI()
  76. {
  77. using (new EditorGUILayout.HorizontalScope("Box"))
  78. {
  79. EditorGUILayout.LabelField("打包平台:", GUILayout.Width(60));
  80. _buildTarget = (BuildTarget)EditorGUILayout.EnumPopup(_buildTarget);
  81. EditorGUILayout.LabelField("输出路径:", GUILayout.Width(60));
  82. BudleToolConfig.m_savePath = EditorGUILayout.TextField(BudleToolConfig.m_savePath);
  83. if (GUILayout.Button(EditorGUIUtility.IconContent("Project@2x").image, GUILayout.Height(20), GUILayout.Width(40)))
  84. {
  85. try
  86. {
  87. BudleToolConfig.m_savePath = EditorUtility.OpenFolderPanel("AB包输出路径", "Assets", "Assets");
  88. }
  89. catch (Exception e)
  90. {
  91. Debug.Log(e.Message);
  92. }
  93. }
  94. }
  95. float _width = position.width / 6;
  96. using (new EditorGUILayout.HorizontalScope("Box"))
  97. {
  98. EditorGUILayout.LabelField("包名", GUILayout.Width(_width * 4));
  99. EditorGUILayout.LabelField("文件夹", GUILayout.Width(_width));
  100. EditorGUILayout.LabelField("操作");
  101. }
  102. _scorllView = EditorGUILayout.BeginScrollView(_scorllView);
  103. for (int i = 0; i < BudleConfig.m_AB.Count; i++)
  104. {
  105. using (new EditorGUILayout.HorizontalScope())
  106. {
  107. BudleConfig.m_AB[i].m_name = EditorGUILayout.TextField(BudleConfig.m_AB[i].m_name, GUILayout.Width(_width * 4));
  108. BudleConfig.m_AB[i].m_isFolder = EditorGUILayout.Toggle(BudleConfig.m_AB[i].m_isFolder, GUILayout.Width(_width));
  109. if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash").image, GUILayout.Height(20)))
  110. {
  111. BudleConfig.m_AB.Remove(BudleConfig.m_AB[i]);
  112. }
  113. }
  114. }
  115. if (GUILayout.Button(EditorGUIUtility.IconContent("CreateAddNew@2x").image, GUILayout.Height(30), GUILayout.Width(30)))
  116. {
  117. BudleConfig.m_AB.Add(new AssetBundleEntity());
  118. }
  119. EditorGUILayout.EndScrollView();
  120. if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
  121. {
  122. if (Event.current.type == EventType.DragUpdated)
  123. DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
  124. else if (Event.current.type == EventType.DragExited)
  125. {
  126. if (DragAndDrop.paths != null && DragAndDrop.paths.Length > 0)
  127. {
  128. for (int i = 0; i < DragAndDrop.paths.Length; i++)
  129. {
  130. if (BudleConfig.m_AB.Find(p => p.m_name == DragAndDrop.paths[i]) == null)
  131. BudleConfig.m_AB.Add(new AssetBundleEntity()
  132. { m_name = DragAndDrop.paths[i] });
  133. }
  134. }
  135. }
  136. }
  137. using (new EditorGUILayout.HorizontalScope())
  138. {
  139. EditorGUILayout.LabelField("将打包资源拷贝到StreamingAssets文件夹", GUILayout.Width(230));
  140. BudleToolConfig.m_copyToStreamingAssets = EditorGUILayout.Toggle(BudleToolConfig.m_copyToStreamingAssets);
  141. }
  142. if (GUILayout.Button("构建AssetBundle"))
  143. {
  144. OnAssetBundleCallBack();
  145. }
  146. }
  147. /// <summary>
  148. /// 打包回调
  149. /// </summary>
  150. private void OnAssetBundleCallBack()
  151. {
  152. string tartgetPath = BudleToolConfig.m_savePath + "/" + _buildTarget.ToString();
  153. if (!Directory.Exists(tartgetPath))
  154. Directory.CreateDirectory(tartgetPath);
  155. SetABName();
  156. BuildPipeline.BuildAssetBundles(tartgetPath, BuildAssetBundleOptions.None, _buildTarget);
  157. if (BudleToolConfig.m_copyToStreamingAssets)
  158. CopyToStreamingAssets();
  159. OnCreateVersionText();
  160. AssetDatabase.Refresh();
  161. }
  162. private void SetABName()
  163. {
  164. for (int i = 0; i < BudleConfig.m_AB.Count; i++)
  165. {
  166. AssetBundleEntity assetBudle = BudleConfig.m_AB[i];
  167. if (assetBudle.m_isFolder)
  168. {
  169. string[] floderArr = new string[] {GetAbsolutePath(assetBudle.m_name) };
  170. SaveFolderSettings(floderArr);
  171. }
  172. else
  173. {
  174. SaveFileSettings(Application.dataPath + "/" + assetBudle.m_name);
  175. }
  176. }
  177. }
  178. private void SaveFolderSettings(string[] folderArr)
  179. {
  180. foreach (string floderPath in folderArr)
  181. {
  182. string[] arrFile = Directory.GetFiles(floderPath);
  183. foreach (string filePah in arrFile)
  184. {
  185. SaveFileSettings(filePah);
  186. }
  187. string[] arrFolder = Directory.GetDirectories(floderPath);
  188. SaveFolderSettings(arrFolder);
  189. }
  190. }
  191. private void SaveFileSettings(string filePath)
  192. {
  193. FileInfo file = new FileInfo(filePath);
  194. if (!file.Extension.Equals(".meta", StringComparison.CurrentCultureIgnoreCase))
  195. {
  196. int index = filePath.IndexOf("Assets/", StringComparison.CurrentCultureIgnoreCase);
  197. string newPath = filePath.Substring(index);
  198. string fileName = newPath.Substring(newPath.IndexOf("Assets/")).Replace(file.Extension, "");
  199. string variant = file.Extension.Equals(".unity", StringComparison.CurrentCultureIgnoreCase) ? "unity3d" : "assetbundle";
  200. AssetImporter importer = AssetImporter.GetAtPath(newPath);
  201. importer.SetAssetBundleNameAndVariant(fileName, variant);
  202. importer.SaveAndReimport();
  203. }
  204. }
  205. private void CopyToStreamingAssets()
  206. {
  207. string toPath = Application.streamingAssetsPath;
  208. string tartgetPath = BudleToolConfig.m_savePath + "/" + _buildTarget.ToString();
  209. CopyDirectory(tartgetPath, toPath,true);
  210. AssetDatabase.Refresh();
  211. }
  212. /// <summary>
  213. /// 创建版本文件
  214. /// </summary>
  215. private void OnCreateVersionText()
  216. {
  217. string strVersionJsonPath = $"{BudleToolConfig.m_savePath}/{_buildTarget.ToString()}/VersionFile.json";
  218. versionDatas.Clear();
  219. if (File.Exists(strVersionJsonPath))
  220. File.Delete(strVersionJsonPath);
  221. StringBuilder sbContent = new StringBuilder();
  222. DirectoryInfo directory = new DirectoryInfo(BudleToolConfig.m_savePath);
  223. FileInfo[] arrfiles = directory.GetFiles("*", SearchOption.AllDirectories);
  224. for (int i = 0; i < arrfiles.Length; i++)
  225. {
  226. FileInfo file = arrfiles[i];
  227. string fullName = file.FullName;
  228. string md5 = GetFileMD5(fullName);
  229. if (md5 == null) continue;
  230. string subPath = $"{BudleToolConfig.m_savePath}/{_buildTarget.ToString()}";
  231. versionDatas.Add(new VersionDataEntity()
  232. {
  233. m_fullName = fullName.Substring(fullName.IndexOf(subPath) + subPath.Length + 2),
  234. m_md5 = md5,
  235. m_size = Mathf.Ceil(file.Length / 1024.0f)
  236. });
  237. }
  238. WriteJsonData(versionDatas, strVersionJsonPath);
  239. }
  240. private void OnDisable()
  241. {
  242. EditorUtility.SetDirty(_list);
  243. EditorUtility.SetDirty(BudleToolConfig);
  244. AssetDatabase.SaveAssets();
  245. }
  246. public string GetAbsolutePath(string path)
  247. {
  248. string _path = Application.dataPath.Remove(Application.dataPath.LastIndexOf('/') + 1);
  249. return _path + path;
  250. }
  251. /// <summary>
  252. /// 拷贝文件夹
  253. /// </summary>
  254. /// <param name="sourceDirName"></param>
  255. /// <param name="destDirName"></param>
  256. /// <param name="isOverlay">是否覆盖已有同名文件</param>
  257. public void CopyDirectory(string sourceDirName, string destDirName, bool isOverlay = false)
  258. {
  259. try
  260. {
  261. if (!Directory.Exists(destDirName))
  262. {
  263. Directory.CreateDirectory(destDirName);
  264. File.SetAttributes(destDirName, File.GetAttributes(sourceDirName));
  265. }
  266. if (destDirName[destDirName.Length - 1] != Path.DirectorySeparatorChar)
  267. destDirName = destDirName + Path.DirectorySeparatorChar;
  268. string[] files = Directory.GetFiles(sourceDirName);
  269. foreach (string file in files)
  270. {
  271. if (File.Exists(destDirName + Path.GetFileName(file)) && !isOverlay)
  272. continue;
  273. FileInfo fileInfo = new FileInfo(file);
  274. if (fileInfo.Extension.Equals(".meta", StringComparison.CurrentCultureIgnoreCase))
  275. continue;
  276. File.Copy(file, destDirName + Path.GetFileName(file), true);
  277. File.SetAttributes(destDirName + Path.GetFileName(file), FileAttributes.Normal);
  278. }
  279. string[] dirs = Directory.GetDirectories(sourceDirName);
  280. foreach (string dir in dirs)
  281. {
  282. CopyDirectory(dir, destDirName + Path.GetFileName(dir), isOverlay);
  283. }
  284. }
  285. catch (Exception ex)
  286. {
  287. throw ex;
  288. }
  289. }
  290. /// <summary>
  291. /// 写入数据
  292. /// </summary>
  293. /// <param name="data">数据</param>
  294. /// <param name="path">文件完整路径</param>
  295. public static void WriteJsonData(object data, string path)
  296. {
  297. string json = JsonMapper.ToJson(data);
  298. using (StreamWriter sw = new StreamWriter(path))
  299. {
  300. //保存数据
  301. sw.WriteLine(json);
  302. //关闭文档
  303. sw.Close();
  304. sw.Dispose();
  305. }
  306. }
  307. /// <summary>
  308. /// 获取文件MD5
  309. /// </summary>
  310. /// <param name="filePath"></param>
  311. /// <returns></returns>
  312. public static string GetFileMD5(string filePath)
  313. {
  314. if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
  315. return null;
  316. try
  317. {
  318. FileStream file = new FileStream(filePath, FileMode.Open);
  319. MD5 mD5 = new MD5CryptoServiceProvider();
  320. byte[] byteResult = mD5.ComputeHash(file);
  321. string strResult = BitConverter.ToString(byteResult);
  322. strResult = strResult.Replace("-", "");
  323. return strResult;
  324. }
  325. catch
  326. {
  327. return null;
  328. }
  329. }
  330. }
  331. }