using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using TModule.Runtime; using UnityEditor; using UnityEngine; using LitJson; namespace TModule.Editor { public class AssetBudleTool : EditorWindow { private string AssetBudleConfigName= "AssetBundleConfig.asset"; private string AssetBudleConfigPath; private string AssetbudleToolConfigName = "BudleToolConfig.asset"; private string AssetbudleToolConfigPath; private AssetBudleConfig _list; private AssetBudleConfig BudleConfig { get { _list = _list ?? AssetDatabase.LoadAssetAtPath(AssetBudleConfigPath); if(_list==null) { _list = CreateInstance(); AssetDatabase.CreateAsset(_list, AssetBudleConfigPath); } return _list; } set => _list = value; } private AssetBudleToolConfig _toolConfig; private AssetBudleToolConfig BudleToolConfig { get { _toolConfig = _toolConfig ?? AssetDatabase.LoadAssetAtPath(AssetbudleToolConfigPath); if(_toolConfig==null) { _toolConfig = CreateInstance(); AssetDatabase.CreateAsset(_toolConfig, AssetbudleToolConfigPath); } return _toolConfig; } set => _toolConfig = value; } private static List versionDatas = new List(); private Vector2 _scorllView = Vector2.zero; #if UNITY_STANDALONE_WIN private BuildTarget _buildTarget = BuildTarget.StandaloneWindows64; #elif UNITY_ANDROID private BuildTarget _buildTarget = BuildTarget.Android; #elif UNITY_IPHON private BuildTarget _buildTarget = BuildTarget.iOS; #endif [MenuItem("TModule/热更/AssetBudleTool")] public static void OpenWin() { AssetBudleTool win = GetWindow(); win.maxSize = new Vector2(800, 400); win.titleContent.text = "ABTool"; win.titleContent.image = EditorGUIUtility.IconContent("PrefabModel Icon").image; win.Show(); } private void OnEnable() { var ms = MonoScript.FromScriptableObject(this); var path = AssetDatabase.GetAssetPath(ms); path = Path.GetDirectoryName(path); AssetBudleConfigPath = Path.Combine(path, AssetBudleConfigName); AssetbudleToolConfigPath = Path.Combine(path, AssetbudleToolConfigName); } private void OnGUI() { using (new EditorGUILayout.HorizontalScope("Box")) { EditorGUILayout.LabelField("打包平台:", GUILayout.Width(60)); _buildTarget = (BuildTarget)EditorGUILayout.EnumPopup(_buildTarget); EditorGUILayout.LabelField("输出路径:", GUILayout.Width(60)); BudleToolConfig.m_savePath = EditorGUILayout.TextField(BudleToolConfig.m_savePath); if (GUILayout.Button(EditorGUIUtility.IconContent("Project@2x").image, GUILayout.Height(20), GUILayout.Width(40))) { try { BudleToolConfig.m_savePath = EditorUtility.OpenFolderPanel("AB包输出路径", "Assets", "Assets"); } catch (Exception e) { Debug.Log(e.Message); } } } float _width = position.width / 6; using (new EditorGUILayout.HorizontalScope("Box")) { EditorGUILayout.LabelField("包名", GUILayout.Width(_width * 4)); EditorGUILayout.LabelField("文件夹", GUILayout.Width(_width)); EditorGUILayout.LabelField("操作"); } _scorllView = EditorGUILayout.BeginScrollView(_scorllView); for (int i = 0; i < BudleConfig.m_AB.Count; i++) { using (new EditorGUILayout.HorizontalScope()) { BudleConfig.m_AB[i].m_name = EditorGUILayout.TextField(BudleConfig.m_AB[i].m_name, GUILayout.Width(_width * 4)); BudleConfig.m_AB[i].m_isFolder = EditorGUILayout.Toggle(BudleConfig.m_AB[i].m_isFolder, GUILayout.Width(_width)); if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash").image, GUILayout.Height(20))) { BudleConfig.m_AB.Remove(BudleConfig.m_AB[i]); } } } if (GUILayout.Button(EditorGUIUtility.IconContent("CreateAddNew@2x").image, GUILayout.Height(30), GUILayout.Width(30))) { BudleConfig.m_AB.Add(new AssetBundleEntity()); } EditorGUILayout.EndScrollView(); if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) { if (Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = DragAndDropVisualMode.Generic; else if (Event.current.type == EventType.DragExited) { if (DragAndDrop.paths != null && DragAndDrop.paths.Length > 0) { for (int i = 0; i < DragAndDrop.paths.Length; i++) { if (BudleConfig.m_AB.Find(p => p.m_name == DragAndDrop.paths[i]) == null) BudleConfig.m_AB.Add(new AssetBundleEntity() { m_name = DragAndDrop.paths[i] }); } } } } using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("将打包资源拷贝到StreamingAssets文件夹", GUILayout.Width(230)); BudleToolConfig.m_copyToStreamingAssets = EditorGUILayout.Toggle(BudleToolConfig.m_copyToStreamingAssets); } if (GUILayout.Button("构建AssetBundle")) { OnAssetBundleCallBack(); } } /// /// 打包回调 /// private void OnAssetBundleCallBack() { string tartgetPath = BudleToolConfig.m_savePath + "/" + _buildTarget.ToString(); if (!Directory.Exists(tartgetPath)) Directory.CreateDirectory(tartgetPath); SetABName(); BuildPipeline.BuildAssetBundles(tartgetPath, BuildAssetBundleOptions.None, _buildTarget); if (BudleToolConfig.m_copyToStreamingAssets) CopyToStreamingAssets(); OnCreateVersionText(); AssetDatabase.Refresh(); } private void SetABName() { for (int i = 0; i < BudleConfig.m_AB.Count; i++) { AssetBundleEntity assetBudle = BudleConfig.m_AB[i]; if (assetBudle.m_isFolder) { string[] floderArr = new string[] {GetAbsolutePath(assetBudle.m_name) }; SaveFolderSettings(floderArr); } else { SaveFileSettings(Application.dataPath + "/" + assetBudle.m_name); } } } private void SaveFolderSettings(string[] folderArr) { foreach (string floderPath in folderArr) { string[] arrFile = Directory.GetFiles(floderPath); foreach (string filePah in arrFile) { SaveFileSettings(filePah); } string[] arrFolder = Directory.GetDirectories(floderPath); SaveFolderSettings(arrFolder); } } private void SaveFileSettings(string filePath) { FileInfo file = new FileInfo(filePath); if (!file.Extension.Equals(".meta", StringComparison.CurrentCultureIgnoreCase)) { int index = filePath.IndexOf("Assets/", StringComparison.CurrentCultureIgnoreCase); string newPath = filePath.Substring(index); string fileName = newPath.Substring(newPath.IndexOf("Assets/")).Replace(file.Extension, ""); string variant = file.Extension.Equals(".unity", StringComparison.CurrentCultureIgnoreCase) ? "unity3d" : "assetbundle"; AssetImporter importer = AssetImporter.GetAtPath(newPath); importer.SetAssetBundleNameAndVariant(fileName, variant); importer.SaveAndReimport(); } } private void CopyToStreamingAssets() { string toPath = Application.streamingAssetsPath; string tartgetPath = BudleToolConfig.m_savePath + "/" + _buildTarget.ToString(); CopyDirectory(tartgetPath, toPath,true); AssetDatabase.Refresh(); } /// /// 创建版本文件 /// private void OnCreateVersionText() { string strVersionJsonPath = $"{BudleToolConfig.m_savePath}/{_buildTarget.ToString()}/VersionFile.json"; versionDatas.Clear(); if (File.Exists(strVersionJsonPath)) File.Delete(strVersionJsonPath); StringBuilder sbContent = new StringBuilder(); DirectoryInfo directory = new DirectoryInfo(BudleToolConfig.m_savePath); FileInfo[] arrfiles = directory.GetFiles("*", SearchOption.AllDirectories); for (int i = 0; i < arrfiles.Length; i++) { FileInfo file = arrfiles[i]; string fullName = file.FullName; string md5 = GetFileMD5(fullName); if (md5 == null) continue; string subPath = $"{BudleToolConfig.m_savePath}/{_buildTarget.ToString()}"; versionDatas.Add(new VersionDataEntity() { m_fullName = fullName.Substring(fullName.IndexOf(subPath) + subPath.Length + 2), m_md5 = md5, m_size = Mathf.Ceil(file.Length / 1024.0f) }); } WriteJsonData(versionDatas, strVersionJsonPath); } private void OnDisable() { EditorUtility.SetDirty(_list); EditorUtility.SetDirty(BudleToolConfig); AssetDatabase.SaveAssets(); } public string GetAbsolutePath(string path) { string _path = Application.dataPath.Remove(Application.dataPath.LastIndexOf('/') + 1); return _path + path; } /// /// 拷贝文件夹 /// /// /// /// 是否覆盖已有同名文件 public void CopyDirectory(string sourceDirName, string destDirName, bool isOverlay = false) { try { if (!Directory.Exists(destDirName)) { Directory.CreateDirectory(destDirName); File.SetAttributes(destDirName, File.GetAttributes(sourceDirName)); } if (destDirName[destDirName.Length - 1] != Path.DirectorySeparatorChar) destDirName = destDirName + Path.DirectorySeparatorChar; string[] files = Directory.GetFiles(sourceDirName); foreach (string file in files) { if (File.Exists(destDirName + Path.GetFileName(file)) && !isOverlay) continue; FileInfo fileInfo = new FileInfo(file); if (fileInfo.Extension.Equals(".meta", StringComparison.CurrentCultureIgnoreCase)) continue; File.Copy(file, destDirName + Path.GetFileName(file), true); File.SetAttributes(destDirName + Path.GetFileName(file), FileAttributes.Normal); } string[] dirs = Directory.GetDirectories(sourceDirName); foreach (string dir in dirs) { CopyDirectory(dir, destDirName + Path.GetFileName(dir), isOverlay); } } catch (Exception ex) { throw ex; } } /// /// 写入数据 /// /// 数据 /// 文件完整路径 public static void WriteJsonData(object data, string path) { string json = JsonMapper.ToJson(data); using (StreamWriter sw = new StreamWriter(path)) { //保存数据 sw.WriteLine(json); //关闭文档 sw.Close(); sw.Dispose(); } } /// /// 获取文件MD5 /// /// /// public static string GetFileMD5(string filePath) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) return null; try { FileStream file = new FileStream(filePath, FileMode.Open); MD5 mD5 = new MD5CryptoServiceProvider(); byte[] byteResult = mD5.ComputeHash(file); string strResult = BitConverter.ToString(byteResult); strResult = strResult.Replace("-", ""); return strResult; } catch { return null; } } } }