├── .gitattributes ├── .gitignore ├── Editor.meta ├── Editor ├── ABBuild.cs ├── ABDoctor.cs ├── ABFindUnpack.cs ├── ABPackRule.cs ├── ABPackRuleConfig.cs └── ABViewer.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Visual Studio 2015 cache directory 9 | /.vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | 26 | # Unity3D generated meta files 27 | *.pidb.meta 28 | 29 | # Unity3D Generated File On Crash Reports 30 | sysinfo.txt 31 | 32 | # Builds 33 | *.apk 34 | *.unitypackage 35 | *.meta 36 | *.meta 37 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46daed7318f0c574389a57d1224887e6 3 | folderAsset: yes 4 | timeCreated: 1515584360 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Editor/ABBuild.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System.IO; 6 | 7 | namespace ABBuildHelper 8 | { 9 | public class ABBuild : EditorWindow 10 | { 11 | [MenuItem("Window/AB BuildHelper/AB Build", false, 4)] 12 | static void Init() 13 | { 14 | ABBuild w = EditorWindow.GetWindow(false, "AB Build", true); 15 | w.Show(); 16 | } 17 | 18 | enum CompressOption 19 | { 20 | Uncompressed, 21 | StandardCompression, 22 | ChunkBasedCompression 23 | } 24 | 25 | BuildTarget buildTarget 26 | { 27 | get { return EditorPrefs.HasKey("ABBuild.buildTarget") ? (BuildTarget)EditorPrefs.GetInt("ABBuild.buildTarget") : BuildTarget.StandaloneWindows; } 28 | set { EditorPrefs.SetInt("ABBuild.buildTarget", (int)value); } 29 | } 30 | string outputPath 31 | { 32 | get { return EditorPrefs.HasKey("ABBuild.outputPath") ? EditorPrefs.GetString("ABBuild.outputPath") : "AssetBundles"; } 33 | set { EditorPrefs.SetString("ABBuild.outputPath", value); } 34 | } 35 | 36 | CompressOption compressOption 37 | { 38 | get { return EditorPrefs.HasKey("ABBuild.compressOption") ? (CompressOption)EditorPrefs.GetInt("ABBuild.compressOption") : CompressOption.ChunkBasedCompression; } 39 | set { EditorPrefs.SetInt("ABBuild.compressOption", (int)value); } 40 | } 41 | 42 | bool forceBuild 43 | { 44 | get { return EditorPrefs.GetBool("ABBuild.forceBuild"); } 45 | set { EditorPrefs.SetBool("ABBuild.forceBuild", value); } 46 | } 47 | 48 | private void OnGUI() 49 | { 50 | buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target", buildTarget); 51 | 52 | EditorGUILayout.BeginHorizontal(); 53 | outputPath = EditorGUILayout.TextField("Output Path", outputPath); 54 | if (Event.current.type == EventType.DragUpdated && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 55 | { 56 | if (DragAndDrop.objectReferences[0] is DefaultAsset) 57 | { 58 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 59 | DragAndDrop.AcceptDrag(); 60 | Event.current.Use(); 61 | } 62 | } 63 | else if (Event.current.type == EventType.DragPerform && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 64 | { 65 | outputPath = GetAssetPath(DragAndDrop.paths[0]); 66 | GUI.FocusControl(null); 67 | Event.current.Use(); 68 | } 69 | if (GUILayout.Button("Brower")) 70 | { 71 | string result = EditorUtility.OpenFolderPanel("", "选择目录", ""); 72 | if (result != null) 73 | { 74 | outputPath = GetAssetPath(result); 75 | GUI.FocusControl(null); 76 | } 77 | } 78 | EditorGUILayout.EndHorizontal(); 79 | 80 | compressOption = (CompressOption)EditorGUILayout.EnumPopup("Compress Option", compressOption); 81 | 82 | forceBuild = EditorGUILayout.Toggle("Rebuild", forceBuild); 83 | 84 | if (GUILayout.Button("Build")) 85 | { 86 | BuildAssetBundleOptions options = 0; 87 | switch (compressOption) 88 | { 89 | case CompressOption.Uncompressed: options = options | BuildAssetBundleOptions.UncompressedAssetBundle; break; 90 | case CompressOption.ChunkBasedCompression: options = options | BuildAssetBundleOptions.ChunkBasedCompression; break; 91 | } 92 | if (forceBuild) 93 | { 94 | options = options | BuildAssetBundleOptions.ForceRebuildAssetBundle; 95 | } 96 | 97 | string path = Application.dataPath + "/" + outputPath + "/" + buildTarget.ToString(); 98 | if (!Directory.Exists(path)) 99 | Directory.CreateDirectory(path); 100 | BuildPipeline.BuildAssetBundles(path, options, buildTarget); 101 | AssetDatabase.Refresh(); 102 | } 103 | } 104 | 105 | 106 | 107 | private string GetAssetPath(string result) 108 | { 109 | if (result.StartsWith(Application.dataPath)) 110 | return result == Application.dataPath ? "" : result.Substring(Application.dataPath.Length + 1); 111 | else if (result.StartsWith("Assets")) 112 | return result == "Assets" ? "" : result.Substring("Assets/".Length); 113 | return null; 114 | } 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /Editor/ABDoctor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System.IO; 6 | using System.Linq; 7 | namespace ABBuildHelper 8 | { 9 | public class ABDoctor : EditorWindow 10 | { 11 | [MenuItem("Window/AB BuildHelper/AB Doctor", false, 0)] 12 | static void Init() 13 | { 14 | ABDoctor w = EditorWindow.GetWindow(false, "AB Doctor", true); 15 | w.Show(); 16 | } 17 | 18 | private string[] GetPaths(UnityEngine.Object[] objects) 19 | { 20 | int count = objects.Length; 21 | string[] paths = new string[count]; 22 | for (int i = 0; i < count; i++) 23 | { 24 | paths[i] = AssetDatabase.GetAssetPath(objects[i]); 25 | } 26 | return paths; 27 | } 28 | 29 | class RepeatData 30 | { 31 | public string abName; 32 | public List objects; 33 | public bool opened; 34 | } 35 | 36 | static bool showRepeat 37 | { 38 | get { return EditorPrefs.HasKey("ABDoctor.showRepeat") ? EditorPrefs.GetBool("ABDoctor.showRepeat") : true; } 39 | set { EditorPrefs.SetBool("ABDoctor.showRepeat", value); } 40 | } 41 | 42 | static bool showBuildIn 43 | { 44 | get { return EditorPrefs.HasKey("ABDoctor.showBuildIn") ? EditorPrefs.GetBool("ABDoctor.showBuildIn") : true; } 45 | set { EditorPrefs.SetBool("ABDoctor.showBuildIn", value); } 46 | } 47 | 48 | static bool showABList 49 | { 50 | get { return EditorPrefs.HasKey("ABDoctor.showABList") ? EditorPrefs.GetBool("ABDoctor.showABList") : true; } 51 | set { EditorPrefs.SetBool("ABDoctor.showABList", value); } 52 | } 53 | 54 | Dictionary> abAssets; 55 | Dictionary> assetDenpendGroups; 56 | Dictionary> repeatAssets; 57 | Dictionary> buildInAssets; 58 | 59 | bool showSubAsset = true; 60 | private void CollectAssets() 61 | { 62 | Dictionary> abAssetDict = new Dictionary>(); 63 | 64 | //获得ab依赖的所有资源 65 | string[] abNames = AssetDatabase.GetAllAssetBundleNames(); 66 | foreach (string abName in abNames) 67 | { 68 | string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundle(abName); 69 | List objects = new List(); 70 | foreach (string assetPath in assetPaths) 71 | { 72 | if (assetPath.EndsWith(".unity")) 73 | { 74 | objects.Add(AssetDatabase.LoadMainAssetAtPath(assetPath)); 75 | } 76 | else 77 | { 78 | objects.AddRange(AssetDatabase.LoadAllAssetsAtPath(assetPath)); 79 | } 80 | } 81 | HashSet abAssets = new HashSet(EditorUtility.CollectDependencies(objects.ToArray()).Where(x => !(x is MonoScript))); 82 | abAssetDict.Add(abName, abAssets); 83 | } 84 | //移除ab间依赖 85 | foreach (var pair in abAssetDict) 86 | { 87 | string[] dependAbs = AssetDatabase.GetAssetBundleDependencies(pair.Key, true); 88 | foreach (string depend in dependAbs) 89 | { 90 | foreach (Object obj in abAssetDict[depend]) 91 | { 92 | pair.Value.Remove(obj); 93 | } 94 | } 95 | } 96 | //排序 97 | abAssets = new Dictionary>(); 98 | foreach (var pair in abAssetDict) 99 | { 100 | List list = pair.Value 101 | .OrderBy(x => AssetDatabase.GetAssetPath(x)) 102 | .ThenByDescending(x => AssetDatabase.IsMainAsset(x)) 103 | .ToList(); 104 | abAssets.Add(pair.Key, list); 105 | } 106 | //统计 107 | assetDenpendGroups = new Dictionary>(); 108 | foreach (var pair in abAssetDict) 109 | { 110 | foreach (Object obj in pair.Value) 111 | { 112 | RepeatData repeatData = new RepeatData(); 113 | repeatData.abName = pair.Key; 114 | if (!assetDenpendGroups.ContainsKey(obj)) 115 | { 116 | assetDenpendGroups.Add(obj, new List() { repeatData }); 117 | } 118 | else 119 | { 120 | assetDenpendGroups[obj].Add(repeatData); 121 | } 122 | } 123 | } 124 | //分类 125 | repeatAssets = new Dictionary>(); 126 | buildInAssets = new Dictionary>(); 127 | foreach (var pair in assetDenpendGroups) 128 | { 129 | if (pair.Value.Count > 1) 130 | { 131 | repeatAssets.Add(pair.Key, pair.Value); 132 | } 133 | if (IsBuildIn(AssetDatabase.GetAssetPath(pair.Key))) 134 | { 135 | //foreach (var repeatData in pair.Value) 136 | //{ 137 | // CollectRepeatDependencies(repeatData, pair.Key); 138 | //} 139 | buildInAssets.Add(pair.Key, pair.Value); 140 | } 141 | } 142 | } 143 | 144 | private void CollectRepeatDependencies(RepeatData repeatData, Object target) 145 | { 146 | Dictionary repeatDataDepends = new Dictionary(); 147 | HashSet result = new HashSet(); 148 | foreach (Object obj in abAssets[repeatData.abName]) 149 | { 150 | if (obj == target) 151 | continue; 152 | 153 | Object[] depends = EditorUtility.CollectDependencies(new Object[] { obj }); 154 | if (System.Array.IndexOf(depends, target) >= 0) 155 | { 156 | result.Add(obj); 157 | repeatDataDepends.Add(obj, depends); 158 | } 159 | } 160 | 161 | foreach (var pair in repeatDataDepends) 162 | { 163 | if (result.Contains(pair.Key)) 164 | { 165 | foreach (var depend in pair.Value) 166 | { 167 | if (depend != pair.Key && result.Contains(depend)) 168 | { 169 | result.Remove(pair.Key); 170 | break; 171 | } 172 | } 173 | } 174 | } 175 | repeatData.objects = result.ToList(); 176 | } 177 | 178 | private void OnEnable() 179 | { 180 | CollectAssets(); 181 | } 182 | 183 | Vector2 scrollPosition; 184 | Dictionary toggleGroupData = new Dictionary(); 185 | private void OnGUI() 186 | { 187 | if (GUILayout.Button("Refresh")) 188 | { 189 | CollectAssets(); 190 | } 191 | 192 | scrollPosition = GUILayout.BeginScrollView(scrollPosition); 193 | if (repeatAssets.Count > 0) 194 | { 195 | EditorGUILayout.BeginHorizontal(GUI.skin.button); 196 | showRepeat = EditorGUILayout.ToggleLeft("重复打包的资源:", showRepeat); 197 | EditorGUILayout.EndHorizontal(); 198 | if (showRepeat) 199 | { 200 | EditorGUI.indentLevel++; 201 | foreach (var pair in repeatAssets) 202 | { 203 | ShowAsset(pair.Key); 204 | ShowDependencies(pair.Value, pair.Key); 205 | } 206 | EditorGUI.indentLevel--; 207 | } 208 | } 209 | 210 | if (buildInAssets.Count > 0) 211 | { 212 | EditorGUILayout.BeginHorizontal(GUI.skin.button); 213 | showBuildIn = EditorGUILayout.ToggleLeft("不能依赖打包的内置资源:", showBuildIn); 214 | if (GUILayout.Button("替换为用户资源")) 215 | { 216 | FixBuildInAssets(); 217 | } 218 | EditorGUILayout.EndHorizontal(); 219 | if (showBuildIn) 220 | { 221 | EditorGUI.indentLevel++; 222 | foreach (var pair in buildInAssets) 223 | { 224 | ShowAsset(pair.Key); 225 | ShowDependencies(pair.Value, pair.Key); 226 | } 227 | EditorGUI.indentLevel--; 228 | } 229 | } 230 | 231 | EditorGUILayout.BeginHorizontal(GUI.skin.button); 232 | showABList = EditorGUILayout.ToggleLeft("AssetBundles内容:", showABList); 233 | showSubAsset = GUILayout.Toggle(showSubAsset, "Show Sub Asset"); 234 | EditorGUILayout.EndHorizontal(); 235 | if (showABList) 236 | { 237 | EditorGUI.indentLevel++; 238 | foreach (var pair in abAssets) 239 | { 240 | if (!toggleGroupData.ContainsKey(pair.Key)) 241 | toggleGroupData.Add(pair.Key, false); 242 | 243 | toggleGroupData[pair.Key] = EditorGUILayout.Foldout(toggleGroupData[pair.Key], pair.Key); 244 | CheckDragToAssetBoundles(pair.Key); 245 | if (toggleGroupData[pair.Key]) 246 | { 247 | ShowAssets(pair.Key); 248 | } 249 | } 250 | EditorGUI.indentLevel--; 251 | } 252 | GUILayout.EndScrollView(); 253 | } 254 | 255 | private void ShowDependencies(List boundles, Object target) 256 | { 257 | EditorGUI.indentLevel++; 258 | foreach (RepeatData repeatData in boundles) 259 | { 260 | repeatData.opened = EditorGUILayout.Foldout(repeatData.opened, repeatData.abName); 261 | if (repeatData.opened) 262 | { 263 | if (repeatData.objects == null) 264 | { 265 | CollectRepeatDependencies(repeatData, target); 266 | } 267 | 268 | EditorGUI.indentLevel++; 269 | foreach (Object obj in repeatData.objects) 270 | { 271 | ShowAsset(obj); 272 | } 273 | EditorGUI.indentLevel--; 274 | } 275 | } 276 | EditorGUI.indentLevel--; 277 | } 278 | 279 | private void ShowAssets(string abName) 280 | { 281 | var abValues = abAssets[abName]; 282 | EditorGUI.indentLevel++; 283 | foreach (var asset in abValues) 284 | { 285 | ShowAsset(asset, true); 286 | } 287 | string[] dependAbs = AssetDatabase.GetAssetBundleDependencies(abName, false); 288 | foreach (string depend in dependAbs) 289 | { 290 | EditorGUILayout.LabelField(depend); 291 | ShowAssets(depend); 292 | } 293 | EditorGUI.indentLevel--; 294 | } 295 | 296 | private void ShowAsset(Object asset, bool showInList = false) 297 | { 298 | Color oldColor = GUI.color; 299 | string path = AssetDatabase.GetAssetPath(asset); 300 | bool isSubAsset = !AssetDatabase.IsMainAsset(asset); 301 | if (string.IsNullOrEmpty(path)) 302 | { 303 | isSubAsset = false; 304 | GUI.color = Color.blue; 305 | } 306 | else if (IsBuildIn(path)) 307 | { 308 | isSubAsset = false; 309 | GUI.color = Color.yellow; 310 | } 311 | else if (isSubAsset) 312 | { 313 | GUI.color = new Color(0.7f, 0.7f, 0.7f, 1f); 314 | } 315 | 316 | if (showInList) 317 | { 318 | if (showSubAsset || !isSubAsset) 319 | { 320 | if (isSubAsset) EditorGUI.indentLevel++; 321 | EditorGUILayout.ObjectField(asset, typeof(Object), true); 322 | if (isSubAsset) EditorGUI.indentLevel--; 323 | } 324 | } 325 | else 326 | { 327 | EditorGUILayout.ObjectField(asset, typeof(Object), true); 328 | } 329 | 330 | GUI.color = oldColor; 331 | } 332 | 333 | private static bool IsBuildIn(string path) 334 | { 335 | return path.StartsWith("Resources/unity_builtin_extra") || path == "Library/unity default resources"; 336 | } 337 | 338 | private void CheckDragToAssetBoundles(string adName) 339 | { 340 | if (Event.current.type == EventType.DragPerform && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 341 | { 342 | foreach (Object obj in DragAndDrop.objectReferences) 343 | { 344 | AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(obj)).assetBundleName = adName; 345 | } 346 | Event.current.Use(); 347 | CollectAssets(); 348 | } 349 | else if (Event.current.type == EventType.DragUpdated && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 350 | { 351 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 352 | DragAndDrop.AcceptDrag(); 353 | } 354 | } 355 | 356 | private void FixBuildInAssets() 357 | { 358 | bool fixAll = true; 359 | foreach (var pair in assetDenpendGroups) 360 | { 361 | Object asset = pair.Key; 362 | if (IsBuildIn(AssetDatabase.GetAssetPath(asset))) 363 | { 364 | Object repeatObject = null; 365 | if (asset is Shader) 366 | { 367 | repeatObject = Shader.Find(asset.name); 368 | } 369 | else 370 | { 371 | string[] assetPaths = AssetDatabase.FindAssets(asset.name + " t:" + asset.GetType().Name.ToLower()); 372 | if (assetPaths.Length > 0) 373 | { 374 | repeatObject = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(assetPaths[0]), asset.GetType()); 375 | } 376 | } 377 | if (repeatObject != null) 378 | { 379 | foreach (RepeatData repeatData in pair.Value) 380 | { 381 | CollectRepeatDependencies(repeatData, asset); 382 | foreach (Object obj in repeatData.objects) 383 | { 384 | if (IsBuildIn(AssetDatabase.GetAssetPath(obj))) 385 | continue; 386 | 387 | if (repeatObject is Shader) 388 | { 389 | if (obj is Material) 390 | (obj as Material).shader = repeatObject as Shader; 391 | } 392 | else if (repeatObject is Mesh) 393 | { 394 | if (obj is MeshFilter) 395 | (obj as MeshFilter).sharedMesh = repeatObject as Mesh; 396 | } 397 | else if (repeatObject is Material) 398 | { 399 | if (obj is Renderer) 400 | (obj as Renderer).sharedMaterial = repeatObject as Material; 401 | } 402 | } 403 | } 404 | } 405 | else 406 | { 407 | fixAll = false; 408 | } 409 | } 410 | } 411 | 412 | CollectAssets(); 413 | if (!fixAll) 414 | { 415 | EditorApplication.delayCall = () => 416 | { 417 | if (EditorUtility.DisplayDialog("", "需要先到Unity官网下载内建文件(在点击下载后的下拉框中)并复制到工程目录,\n是否跳转到下载网站?", "确定", "取消")) 418 | { 419 | Application.OpenURL("https://unity3d.com/cn/get-unity/download/archive"); 420 | } 421 | }; 422 | } 423 | } 424 | } 425 | 426 | } 427 | -------------------------------------------------------------------------------- /Editor/ABFindUnpack.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEditor; 6 | namespace ABBuildHelper 7 | { 8 | public class ABFindUnpack : EditorWindow 9 | { 10 | [MenuItem("Window/AB BuildHelper/AB FindUnpack", false, 3)] 11 | static void Init() 12 | { 13 | ABFindUnpack w = EditorWindow.GetWindow(false, "AB FindUnpack", true); 14 | w.Show(); 15 | } 16 | 17 | List assets; 18 | Vector2 scrollPosition; 19 | string folder; 20 | 21 | public void CollectData() 22 | { 23 | List assetPaths = new List(); 24 | foreach (string abName in AssetDatabase.GetAllAssetBundleNames()) 25 | { 26 | assetPaths.AddRange(AssetDatabase.GetAssetPathsFromAssetBundle(abName)); 27 | } 28 | assets = new List(); 29 | string filter = "Assets/" + folder + (folder == "" ? "" : "/"); 30 | foreach (string path in AssetDatabase.GetAllAssetPaths().Except(AssetDatabase.GetDependencies(assetPaths.ToArray())).OrderBy(x => x)) 31 | { 32 | if (path.StartsWith(filter)) 33 | { 34 | Object obj = AssetDatabase.LoadMainAssetAtPath(path); 35 | if (!(obj is DefaultAsset || obj is MonoScript)) 36 | assets.Add(obj); 37 | } 38 | } 39 | } 40 | 41 | private void OnEnable() 42 | { 43 | folder = EditorPrefs.GetString("ABFindUnpack.folder"); 44 | if (string.IsNullOrEmpty(folder)) 45 | { 46 | folder = ""; 47 | } 48 | } 49 | 50 | private void OnGUI() 51 | { 52 | EditorGUILayout.BeginHorizontal(GUI.skin.box); 53 | folder = EditorGUILayout.TextField(folder); 54 | if (Event.current.type == EventType.DragUpdated && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 55 | { 56 | if (DragAndDrop.objectReferences[0] is DefaultAsset) 57 | { 58 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 59 | DragAndDrop.AcceptDrag(); 60 | Event.current.Use(); 61 | } 62 | } 63 | else if (Event.current.type == EventType.DragPerform && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 64 | { 65 | SetFolder(DragAndDrop.paths[0]); 66 | Event.current.Use(); 67 | } 68 | if (GUILayout.Button("Select Root Path")) 69 | { 70 | string result = EditorUtility.OpenFolderPanel("", "选择目录", ""); 71 | if (result != null) 72 | { 73 | SetFolder(result); 74 | GUI.FocusControl(null); 75 | } 76 | } 77 | if (GUILayout.Button("Find")) 78 | { 79 | CollectData(); 80 | } 81 | EditorGUILayout.EndHorizontal(); 82 | if (assets != null) 83 | { 84 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 85 | foreach (Object asset in assets) 86 | { 87 | EditorGUILayout.ObjectField(asset, typeof(Object), true); 88 | } 89 | EditorGUILayout.EndScrollView(); 90 | } 91 | } 92 | 93 | private void SetFolder(string result) 94 | { 95 | if (result.StartsWith(Application.dataPath)) 96 | folder = result == Application.dataPath ? "" : result.Substring(Application.dataPath.Length + 1); 97 | else if (result.StartsWith("Assets")) 98 | folder = result == "Assets" ? "" : result.Substring("Assets/".Length); 99 | 100 | EditorPrefs.SetString("ABFindUnpack.folder", folder); 101 | } 102 | } 103 | 104 | } 105 | 106 | -------------------------------------------------------------------------------- /Editor/ABPackRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System; 6 | using System.Linq; 7 | using System.IO; 8 | 9 | namespace ABBuildHelper 10 | { 11 | public class ABPackRule : EditorWindow 12 | { 13 | [MenuItem("Window/AB BuildHelper/AB PackRule", false, 2)] 14 | static void Init() 15 | { 16 | ABPackRule w = EditorWindow.GetWindow(false, "AB PackRule", true); 17 | w.Show(); 18 | } 19 | 20 | private Vector2 scrollPosition; 21 | private int selectIndex = -1; 22 | 23 | private void OnGUI() 24 | { 25 | ABPackRuleConfig config = AutoABNamePostprocessor.config; 26 | EditorGUILayout.BeginHorizontal(GUI.skin.box); 27 | AutoABNamePostprocessor.autoPack = EditorGUILayout.ToggleLeft("autoPack", AutoABNamePostprocessor.autoPack); 28 | if (GUILayout.Button("Apply")) 29 | { 30 | AutoABNamePostprocessor.PackAll(); 31 | } 32 | EditorGUILayout.EndHorizontal(); 33 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 34 | int count = config.rules.Count; 35 | EditorGUI.BeginChangeCheck(); 36 | for (int i = 0; i < count; i++) 37 | { 38 | OnGUIRule(config.rules[i], selectIndex == i); 39 | if (Event.current.type == EventType.MouseUp && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 40 | { 41 | selectIndex = i; 42 | Event.current.Use(); 43 | } 44 | } 45 | if (EditorGUI.EndChangeCheck()) 46 | { 47 | EditorUtility.SetDirty(config); 48 | } 49 | EditorGUILayout.BeginHorizontal(); 50 | if (GUILayout.Button("Add Rule")) 51 | { 52 | config.rules.Add(new ABPackRuleConfig.Rule()); 53 | EditorUtility.SetDirty(config); 54 | } 55 | if (count > 0 && selectIndex >= 0 && GUILayout.Button("Remove Rule")) 56 | { 57 | config.rules.RemoveAt(selectIndex); 58 | selectIndex = -1; 59 | EditorUtility.SetDirty(config); 60 | } 61 | EditorGUILayout.EndHorizontal(); 62 | EditorGUILayout.EndScrollView(); 63 | 64 | } 65 | 66 | void OnGUIRule(ABPackRuleConfig.Rule rule, bool selected) 67 | { 68 | ABPackRuleConfig config = AutoABNamePostprocessor.config; 69 | 70 | EditorGUILayout.BeginVertical(selected ? GUI.skin.button : GUI.skin.box); 71 | EditorGUILayout.BeginHorizontal(); 72 | rule.path = EditorGUILayout.TextField("Path: ", rule.path); 73 | if (Event.current.type == EventType.DragUpdated && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 74 | { 75 | if (DragAndDrop.objectReferences[0] is DefaultAsset) 76 | { 77 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 78 | DragAndDrop.AcceptDrag(); 79 | Event.current.Use(); 80 | } 81 | } 82 | else if (Event.current.type == EventType.DragPerform && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 83 | { 84 | rule.path = GetAssetPath(DragAndDrop.paths[0]); 85 | Event.current.Use(); 86 | EditorUtility.SetDirty(config); 87 | } 88 | if (GUILayout.Button("Select", GUILayout.Width(100))) 89 | { 90 | string result = EditorUtility.OpenFolderPanel("", "选择目录", ""); 91 | if (result != null) 92 | { 93 | rule.path = GetAssetPath(result); 94 | GUI.FocusControl(null); 95 | EditorUtility.SetDirty(config); 96 | } 97 | } 98 | EditorGUILayout.EndHorizontal(); 99 | if (!string.IsNullOrEmpty(rule.path)) 100 | { 101 | rule.typeFilter = EditorGUILayout.TextField("TypeFilter: ", rule.typeFilter); 102 | if (Event.current.type == EventType.DragUpdated && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 103 | { 104 | DragAndDrop.visualMode = DragAndDropVisualMode.Move; 105 | DragAndDrop.AcceptDrag(); 106 | Event.current.Use(); 107 | } 108 | else if (Event.current.type == EventType.DragPerform && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) 109 | { 110 | rule.typeFilter = string.Join(",", DragAndDrop.objectReferences.Select(x => x.GetType().Name).Distinct().ToArray()); 111 | Event.current.Use(); 112 | EditorUtility.SetDirty(config); 113 | } 114 | rule.ruleType = EditorGUILayout.Popup("Rule: ", rule.ruleType, AutoABNamePostprocessor.packRuleNames.ToArray()); 115 | } 116 | EditorGUILayout.EndVertical(); 117 | } 118 | 119 | private string GetAssetPath(string result) 120 | { 121 | if (result.StartsWith(Application.dataPath)) 122 | return result == Application.dataPath ? "" : result.Substring(Application.dataPath.Length + 1); 123 | else if (result.StartsWith("Assets")) 124 | return result == "Assets" ? "" : result.Substring("Assets/".Length); 125 | return null; 126 | } 127 | } 128 | 129 | public class AutoABNamePostprocessor : AssetPostprocessor 130 | { 131 | public delegate void PackRule(AssetImporter ai, string rulePath); 132 | public static string[] packRuleNames = 133 | { 134 | "PackInOne", 135 | "PackByFirstDir", 136 | "PackByDir" 137 | }; 138 | public static PackRule[] packRuleValues = 139 | { 140 | PackInOne, 141 | PackByFirstDir, 142 | PackByDir 143 | }; 144 | public static bool autoPack 145 | { 146 | get { return EditorPrefs.GetBool("ABPackRuleConfig.autoPack"); } 147 | set { EditorPrefs.SetBool("ABPackRuleConfig.autoPack", value); } 148 | } 149 | static ABPackRuleConfig m_Config; 150 | static public ABPackRuleConfig config 151 | { 152 | get 153 | { 154 | if (m_Config == null) 155 | { 156 | m_Config = AssetDatabase.LoadAssetAtPath("Assets/ABPackRuleConfig.asset"); 157 | if (m_Config == null) 158 | { 159 | m_Config = ScriptableObject.CreateInstance(); 160 | AssetDatabase.CreateAsset(m_Config, "Assets/ABPackRuleConfig.asset"); 161 | AssetDatabase.Refresh(); 162 | } 163 | } 164 | return m_Config; 165 | } 166 | } 167 | 168 | static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) 169 | { 170 | if (!autoPack) 171 | return; 172 | 173 | foreach (string str in importedAssets) 174 | { 175 | PackOne(str); 176 | } 177 | 178 | foreach (string str in movedAssets) 179 | { 180 | PackOne(str); 181 | } 182 | } 183 | 184 | public static void PackAll() 185 | { 186 | foreach (string path in AssetDatabase.GetAllAssetPaths()) 187 | { 188 | PackOne(path); 189 | } 190 | } 191 | 192 | public static void PackOne(string path) 193 | { 194 | foreach (var rule in config.rules) 195 | { 196 | if (path.StartsWith("Assets/" + rule.path + "/") && rule.MatchType(AssetDatabase.GetMainAssetTypeAtPath(path).Name)) 197 | { 198 | packRuleValues[rule.ruleType](AssetImporter.GetAtPath(path), rule.path); 199 | } 200 | } 201 | } 202 | 203 | private static void PackInOne(AssetImporter ai, string rulePath) 204 | { 205 | ai.assetBundleName = rulePath; 206 | } 207 | 208 | private static void PackByFirstDir(AssetImporter ai, string rulePath) 209 | { 210 | string path = ai.assetPath.Substring(8 + rulePath.Length);//"Assets/" + rulePath + "/" 211 | int index = path.IndexOf('/'); 212 | if (index >= 0) 213 | { 214 | ai.assetBundleName = rulePath + "/" + path.Substring(0, index); 215 | } 216 | else 217 | { 218 | ai.assetBundleName = rulePath; 219 | } 220 | } 221 | 222 | private static void PackByDir(AssetImporter ai, string rulePath) 223 | { 224 | string path = ai.assetPath.Substring(8 + rulePath.Length);//"Assets/" + rulePath + "/" 225 | int index = path.LastIndexOf('/'); 226 | if (index >= 0) 227 | { 228 | ai.assetBundleName = rulePath + "/" + path.Substring(0, index); 229 | } 230 | else 231 | { 232 | ai.assetBundleName = rulePath; 233 | } 234 | } 235 | } 236 | } 237 | 238 | -------------------------------------------------------------------------------- /Editor/ABPackRuleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System; 4 | using UnityEngine; 5 | 6 | [Serializable] 7 | public class ABPackRuleConfig : ScriptableObject 8 | { 9 | [Serializable] 10 | public class Rule 11 | { 12 | public string path; 13 | public string typeFilter; 14 | public int ruleType; 15 | 16 | public bool MatchType(string type) 17 | { 18 | if (type == "MonoScript" || type == "DefaultAsset") 19 | return false; 20 | 21 | return string.IsNullOrEmpty(typeFilter) ? true : Array.IndexOf(typeFilter.Split(','), type) >= 0; 22 | } 23 | } 24 | public List rules = new List(); 25 | } -------------------------------------------------------------------------------- /Editor/ABViewer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.IO; 5 | using UnityEngine; 6 | using UnityEditor; 7 | namespace ABBuildHelper 8 | { 9 | public class ABViewer : EditorWindow 10 | { 11 | [MenuItem("Assets/AB Viewer", false)] 12 | [MenuItem("Window/AB BuildHelper/AB Viewer", false, 1)] 13 | static void Init() 14 | { 15 | ABViewer w = EditorWindow.GetWindow(false, "AB Viewer", true); 16 | w.Show(); 17 | } 18 | 19 | [MenuItem("Assets/AB Viewer", true)] 20 | static bool IsAssetVaild() 21 | { 22 | foreach (Object target in Selection.objects) 23 | { 24 | if (target is UnityEditor.DefaultAsset) 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | public class ABEntiy 31 | { 32 | public AssetBundle ab; 33 | public string[] abDepends; 34 | public AssetEntiy[] assets; 35 | public Object[] depends; 36 | } 37 | 38 | public class AssetEntiy 39 | { 40 | public Object asset; 41 | public Object[] depends; 42 | } 43 | 44 | public List enties; 45 | 46 | Vector2 scrollPosition; 47 | static bool showDependencies = false; 48 | static bool exportMesh = false; 49 | HashSet openedAsset; 50 | 51 | private void OnEnable() 52 | { 53 | LoadAssetBoundles(); 54 | } 55 | 56 | private void OnDisable() 57 | { 58 | UnloadAssetBoundles(); 59 | } 60 | 61 | private void OnSelectionChange() 62 | { 63 | if (IsAssetVaild()) 64 | LoadAssetBoundles(); 65 | } 66 | 67 | public void LoadAssetBoundles() 68 | { 69 | UnloadAssetBoundles(); 70 | openedAsset = new HashSet(); 71 | 72 | enties = new List(); 73 | foreach (Object target in Selection.objects) 74 | { 75 | if (target is DefaultAsset) 76 | { 77 | string path = AssetDatabase.GetAssetPath(target); 78 | AssetBundle ab = AssetBundle.LoadFromFile(path); 79 | if (ab == null) 80 | break; 81 | 82 | Object[] assets = ab.LoadAllAssets().Where(x => !(x is MonoScript) && x != null).OrderBy(x => x.GetType().Name).ThenBy(x => x.name).ToArray(); 83 | int count = assets.Length; 84 | AssetEntiy[] assetEntiys = new AssetEntiy[count]; 85 | for (int i = 0;i < count;i++) 86 | { 87 | Object asset = assets[i]; 88 | assetEntiys[i] = new AssetEntiy() { asset = asset, depends = EditorUtility.CollectDependencies(new Object[] { asset }).Where(x => !(x is MonoScript) && x != null).Except(new Object[] { asset }).OrderBy(x => x.GetType().Name).ThenBy(x => x.name).ToArray() }; 89 | } 90 | 91 | enties.Add(new ABEntiy() 92 | { 93 | ab = ab, 94 | abDepends = AssetDatabase.GetAssetBundleDependencies(ab.name, false), 95 | assets = assetEntiys, 96 | depends = assetEntiys.SelectMany(x => x.depends).Distinct().OrderBy(x => x.GetType().Name).ThenBy(x => x.name).ToArray() 97 | }); 98 | } 99 | } 100 | 101 | this.Repaint(); 102 | } 103 | 104 | public void UnloadAssetBoundles() 105 | { 106 | if (enties == null) 107 | return; 108 | 109 | openedAsset = null; 110 | 111 | foreach (ABEntiy entiy in enties) 112 | { 113 | if (entiy.ab != null) 114 | entiy.ab.Unload(false); 115 | } 116 | enties = null; 117 | } 118 | 119 | private void OnGUI() 120 | { 121 | EditorGUILayout.BeginHorizontal(); 122 | showDependencies = EditorGUILayout.ToggleLeft("Show Dependencies", showDependencies); 123 | exportMesh = EditorGUILayout.ToggleLeft("Export Mesh", exportMesh); 124 | if (GUILayout.Button("Unload All AB")) 125 | { 126 | AssetBundle.UnloadAllAssetBundles(false); 127 | LoadAssetBoundles(); 128 | } 129 | EditorGUILayout.EndHorizontal(); 130 | if (enties == null || enties.Count == 0) 131 | { 132 | EditorGUILayout.LabelField("Select a AssetBoundle File", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); 133 | return; 134 | } 135 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 136 | foreach (ABEntiy entiy in enties) 137 | { 138 | EditorGUILayout.BeginHorizontal(GUI.skin.button); 139 | EditorGUILayout.LabelField(string.IsNullOrEmpty(entiy.ab.name) ? "(No Name)" : entiy.ab.name); 140 | if (entiy.abDepends != null && entiy.abDepends.Length > 0) 141 | { 142 | EditorGUILayout.LabelField("Dependency: " + string.Join(",", entiy.abDepends)); 143 | } 144 | EditorGUILayout.EndHorizontal(); 145 | 146 | GUI.color = Color.white; 147 | if (entiy.assets != null) 148 | { 149 | foreach (AssetEntiy item in entiy.assets) 150 | { 151 | DrawAsset(item.asset, item.depends.Length > 0); 152 | if (openedAsset.Contains(item.asset)) 153 | { 154 | EditorGUI.indentLevel++; 155 | foreach (Object obj in item.depends) 156 | { 157 | if (obj != item.asset) 158 | { 159 | DrawAsset(obj,false); 160 | } 161 | } 162 | EditorGUI.indentLevel--; 163 | } 164 | } 165 | } 166 | if (showDependencies) 167 | { 168 | GUI.color = new Color(0.7f, 0.7f, 0.7f, 1f); 169 | foreach (Object asset in entiy.depends) 170 | { 171 | DrawAsset(asset,false); 172 | } 173 | GUI.color = Color.white; 174 | } 175 | 176 | } 177 | 178 | EditorGUILayout.EndScrollView(); 179 | } 180 | 181 | private void DrawAsset(Object asset, bool isFolder) 182 | { 183 | Rect r = EditorGUILayout.BeginHorizontal(); 184 | EditorGUIUtility.SetIconSize(new Vector2(12f, 12f)); 185 | if (isFolder) 186 | { 187 | EditorGUI.BeginChangeCheck(); 188 | bool flag = EditorGUILayout.Foldout(openedAsset.Contains(asset), EditorGUIUtility.ObjectContent(asset, asset.GetType())); 189 | if (EditorGUI.EndChangeCheck()) 190 | { 191 | if (flag) 192 | openedAsset.Add(asset); 193 | else 194 | openedAsset.Remove(asset); 195 | } 196 | } 197 | else 198 | { 199 | EditorGUILayout.LabelField(EditorGUIUtility.ObjectContent(asset, asset.GetType())); 200 | } 201 | 202 | if (GUILayout.Button("Export", GUILayout.Width(60))) 203 | { 204 | if (asset is GameObject) 205 | { 206 | string url = EditorUtility.SaveFilePanelInProject("Export To", asset.name, "prefab", null); 207 | if (!string.IsNullOrEmpty(url)) 208 | { 209 | PrefabUtility.CreatePrefab(url, asset as GameObject); 210 | if (exportMesh) 211 | { 212 | GameObject go = AssetDatabase.LoadAssetAtPath(url); 213 | foreach (SkinnedMeshRenderer skinMesh in go.GetComponentsInChildren()) 214 | { 215 | if (skinMesh.sharedMesh != null) 216 | { 217 | string name = skinMesh.sharedMesh.name; 218 | skinMesh.sharedMesh = Object.Instantiate(skinMesh.sharedMesh); 219 | skinMesh.sharedMesh.name = name; 220 | AssetDatabase.AddObjectToAsset(skinMesh.sharedMesh, url); 221 | } 222 | } 223 | AssetDatabase.SaveAssets(); 224 | AssetDatabase.Refresh(); 225 | } 226 | } 227 | } 228 | else 229 | { 230 | string url = EditorUtility.SaveFilePanelInProject("Export To", asset.name, "asset", null); 231 | if (!string.IsNullOrEmpty(url)) 232 | { 233 | AssetDatabase.CreateAsset(Object.Instantiate(asset), url); 234 | } 235 | } 236 | 237 | } 238 | EditorGUILayout.EndHorizontal(); 239 | 240 | if (Event.current.clickCount >= 1 && r.Contains(Event.current.mousePosition)) 241 | { 242 | AssetDatabase.OpenAsset(asset); 243 | } 244 | else if (Event.current.type == EventType.MouseDrag && r.Contains(Event.current.mousePosition)) 245 | { 246 | DragAndDrop.PrepareStartDrag(); 247 | DragAndDrop.visualMode = DragAndDropVisualMode.Link; 248 | DragAndDrop.objectReferences = new Object[] { asset }; 249 | DragAndDrop.StartDrag("Move Asset"); 250 | Event.current.Use(); 251 | } 252 | } 253 | } 254 | 255 | } 256 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 flashyiyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AB BuildHelper 2 | 菜单Window/AB BuildHelper下为功能入口 3 | - AB Doctor负责检测资源臃余(同一资源打到多个包内)和内置资源的处理 4 | - AB Viewer负责查看打好的包内资源,双击和拖动可进行测试加载 5 | - AB PackRule自动设置AB Name 6 | - AB FindUnpack检查遗漏未打包资源 7 | - AB Build一个执行打包的界面 8 | 9 | 10 | 对官方工具的补充: 11 |
12 | 官方工具判重忽略了图集文件和BuildIn资源。 13 | --------------------------------------------------------------------------------