├── Editor.meta ├── Editor ├── FindUnUsedAssetWindow.cs ├── FindUnUsedAssetWindow.cs.meta ├── YLYEditorGUI.meta └── YLYEditorGUI │ ├── TreeNode.cs │ └── TreeNode.cs.meta ├── Example.meta ├── Example ├── Lua.meta ├── Lua │ ├── Config.meta │ ├── Config │ │ ├── equip_config.lua │ │ └── equip_config.lua.meta │ ├── Main.lua │ └── Main.lua.meta ├── Model.meta ├── Model │ ├── Test.mat │ ├── Test.mat.meta │ ├── Test.png │ └── Test.png.meta ├── Prefab.meta ├── Prefab │ ├── Cube.prefab │ ├── Cube.prefab.meta │ ├── Cube2.prefab │ └── Cube2.prefab.meta ├── Scene.meta ├── Scene │ ├── 1.unity │ └── 1.unity.meta ├── Script.meta ├── Script │ ├── Test.cs │ └── Test.cs.meta ├── Shader.meta ├── Shader │ ├── Unlit-Texture.shader │ └── Unlit-Texture.shader.meta ├── UI.meta └── UI │ ├── Atlas.meta │ └── Atlas │ ├── Equip.meta │ └── Equip │ ├── 1.png │ ├── 1.png.meta │ ├── 2.png │ └── 2.png.meta ├── README.md └── README_Resource └── example.png /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ffb5b1ca17ed5746bcf45fe4f589339 3 | folderAsset: yes 4 | timeCreated: 1506246488 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Editor/FindUnUsedAssetWindow.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 冗余资源排查工具 3 | author:雨Lu尧 4 | email:cantry100@163.com 5 | blog:http://www.hiwrz.com 6 | */ 7 | using UnityEngine; 8 | using UnityEditor; 9 | using System; 10 | using System.IO; 11 | using System.Text; 12 | using System.Linq; 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | using UnityEditor.Animations; 16 | using System.Text.RegularExpressions; 17 | using YLYEditorGUI; 18 | 19 | public class FindUnUsedAssetWindow : EditorWindow { 20 | //阶段 21 | private enum Stage { 22 | None = 0, 23 | BeginGuidParse = 1, 24 | GuidParsing = 2, 25 | EndGuidParse = 3, 26 | GuidMatching = 4, 27 | EndGuidMatch = 5, 28 | BeginCodeParse = 6, 29 | CodeParsing = 7, 30 | EndCodeParse = 8, 31 | CodeMatching = 9, 32 | EndCodeMatch = 10, 33 | }; 34 | 35 | //错误码 36 | private enum ErrorCode { 37 | None = 0, 38 | RefFindAssetNone = 1, 39 | WaitForRefFindGuidParse = 2, 40 | }; 41 | 42 | //复选框过滤器 43 | private class ToggleFilter { 44 | public Rect rect; 45 | public bool oldIsSelect; 46 | public bool isSelect; 47 | public bool isSelChange; 48 | public string title; 49 | public ToggleFilter(Rect rect, bool isSelect, string title){ 50 | this.rect = rect; 51 | this.isSelect = isSelect; 52 | this.oldIsSelect = isSelect; 53 | this.isSelChange = false; 54 | this.title = title; 55 | } 56 | 57 | public void OnGUI(){ 58 | isSelect = GUI.Toggle(rect, isSelect, title); 59 | if (isSelect != oldIsSelect) { 60 | oldIsSelect = isSelect; 61 | isSelChange = true; 62 | } else { 63 | isSelChange = false; 64 | } 65 | } 66 | } 67 | 68 | //阶段提示 69 | private Dictionary StageTip = new Dictionary(){ 70 | {Stage.None, ""}, 71 | {Stage.BeginGuidParse, "开始解析资源guid"}, 72 | {Stage.GuidParsing, "资源guid解析中..."}, 73 | {Stage.EndGuidParse, "资源guid解析完毕"}, 74 | {Stage.GuidMatching, "资源正反向依赖查找中..."}, 75 | {Stage.EndGuidMatch, "资源正反向依赖查找完毕"}, 76 | {Stage.BeginCodeParse, "开始解析代码"}, 77 | {Stage.CodeParsing, "代码解析中..."}, 78 | {Stage.EndCodeParse, "代码解析完毕"}, 79 | {Stage.CodeMatching, "代码匹配中..."}, 80 | {Stage.EndCodeMatch, "代码匹配完毕"}, 81 | }; 82 | 83 | //错误提示 84 | private Dictionary ErrorTip = new Dictionary(){ 85 | {ErrorCode.None, ""}, 86 | {ErrorCode.RefFindAssetNone, "(待查找资源为空!)"}, 87 | {ErrorCode.WaitForRefFindGuidParse, "(正在等待右边的家伙完成资源guid的解析-->)"}, 88 | }; 89 | 90 | //代码扫描结果复选框过滤器,可拓展 91 | private ToggleFilter[] codeFindToggleFilters = { 92 | new ToggleFilter(new Rect (10, 250, 50, 20), true, "all"), 93 | new ToggleFilter(new Rect (56, 250, 60, 20), true, ".prefab"), 94 | new ToggleFilter(new Rect (130, 250, 50, 20), true, ".png"), 95 | new ToggleFilter(new Rect (190, 250, 50, 20), true, ".unity"), 96 | new ToggleFilter(new Rect (250, 250, 50, 20), true, ".mat"), 97 | new ToggleFilter(new Rect (310, 250, 50, 20), true, ".fbx"), 98 | new ToggleFilter(new Rect (370, 250, 80, 20), true, ".controller"), 99 | new ToggleFilter(new Rect (460, 250, 70, 20), true, ".shader"), 100 | new ToggleFilter(new Rect (10, 270, 50, 20), true, ".tga"), 101 | new ToggleFilter(new Rect (56, 270, 60, 20), true, ".mp3"), 102 | new ToggleFilter(new Rect (130, 270, 50, 20), true, ".ogg"), 103 | }; 104 | 105 | private static FindUnUsedAssetWindow _instance = null; 106 | 107 | //公共相关变量 BEGIN 108 | private SerializationMode oldSerializationMode = SerializationMode.ForceText; 109 | private HashSet collectFiles = null; 110 | private float winWidthHalf = 0f; 111 | private float leftGuiWidth = 0f; 112 | private float rightGuiWidth = 0f; 113 | private bool isCollectAssetDly = false; 114 | private bool isParseCodeDly = true; 115 | private bool isParseGuidDly = false; 116 | //公共相关变量 END 117 | 118 | //代码扫描相关变量 BEGIN 119 | private Stage codeFindStage = Stage.None; 120 | private string[] codeFindFiles = null; 121 | private string[] codeScriptFiles = null; 122 | private string[] codeShaderFiles = null; 123 | private string saveCodeFindRltPath = null; 124 | private EditorApplication.CallbackFunction codeFindUpdate = null; 125 | private int codeFindProgressCur = 0; 126 | private int codeFindProgressTotal = 0; 127 | private Vector2 codeFindRltScrollPos = Vector2.zero; 128 | private bool ignoreCheckScene = true; 129 | private HashSet codeFindUnUsedFiles = null; 130 | private string[] codeFindUnUsedFilterFiles = null; 131 | private Dictionary codeFindUnUsedFileDic = null; 132 | private Dictionary codeFindNameToPathDic = null; 133 | private bool isCodeFindToggleChg = false; 134 | private bool isCodeFindSearchChg = false; 135 | private string codeFindSearchStr = ""; 136 | private ErrorCode codeFindErrorCode = ErrorCode.None; 137 | private string assetPathRegStr = @"['""]{1}((assets[/\\]{1}.+?)?[^'""/\*:\?\\]+?\.(prefab|png|tga|unity|mat|mp3|ogg))['""]{1}"; //资源文件类型可拓展 138 | private string shaderPathRegStr = @"Shader(?:[ \t]*|.Find[ \t]*\([ \t]*)['""]{1}([^'""]+?)['""]{1}"; 139 | //代码扫描相关变量 END 140 | 141 | //正反向依赖查找相关变量 BEGIN 142 | private Stage refFindStage = Stage.None; 143 | private string[] guidAssetfiles = null; 144 | private EditorApplication.CallbackFunction refFindUpdate = null; 145 | private int refFindProgressCur = 0; 146 | private int refFindProgressTotal = 0; 147 | private Vector2 refFindRltScrollPos = Vector2.zero; 148 | private TreeNode refFindTreeRootNode = null; 149 | private UnityEngine.Object refFindAsset = null; 150 | private ErrorCode refFindErrorCode = ErrorCode.None; 151 | private int curRefFindAssetId = 0; 152 | private List refFindExtensions = new List(){".prefab", ".unity", ".mat", ".controller", ".asset"}; 153 | private Dictionary> refFindGuidDic = null; 154 | private string guidRegStr = @"guid: ([0-9a-z]+?),"; 155 | //正反向依赖查找相关变量 END 156 | 157 | [MenuItem("YLY/冗余资源排查工具")] 158 | [MenuItem("Assets/YLY冗余资源排查工具")] 159 | static void Init() 160 | { 161 | if (_instance == null) { 162 | _instance = EditorWindow.GetWindow(typeof(FindUnUsedAssetWindow), false, "冗余资源排查") as FindUnUsedAssetWindow; 163 | _instance.position = new UnityEngine.Rect (320f, 80f, 1112f, 786f); 164 | _instance.OnInit(); 165 | } 166 | _instance.Show(); 167 | } 168 | 169 | void OnInit() 170 | { 171 | //需要把资源文件的序列化改成明文text,这样才可以解析资源的guid 172 | oldSerializationMode = EditorSettings.serializationMode; 173 | if(EditorSettings.serializationMode != SerializationMode.ForceText){ 174 | EditorSettings.serializationMode = SerializationMode.ForceText; 175 | } 176 | } 177 | 178 | void OnDestroy() 179 | { 180 | //重置资源文件序列化方式 181 | if(EditorSettings.serializationMode != oldSerializationMode){ 182 | EditorSettings.serializationMode = oldSerializationMode; 183 | } 184 | 185 | codeFindStage = Stage.None; 186 | refFindStage = Stage.None; 187 | codeFindErrorCode = ErrorCode.None; 188 | refFindErrorCode = ErrorCode.None; 189 | 190 | if(refFindTreeRootNode != null){ 191 | refFindTreeRootNode.Destroy(); 192 | } 193 | if(codeFindUpdate != null){ 194 | EditorApplication.update -= codeFindUpdate; 195 | codeFindUpdate = null; 196 | } 197 | if(refFindUpdate != null){ 198 | EditorApplication.update -= refFindUpdate; 199 | refFindUpdate = null; 200 | } 201 | 202 | collectFiles = null; 203 | codeFindFiles = null; 204 | codeScriptFiles = null; 205 | codeShaderFiles = null; 206 | codeFindUnUsedFiles = null; 207 | codeFindUnUsedFilterFiles = null; 208 | codeFindUnUsedFileDic = null; 209 | codeFindNameToPathDic = null; 210 | codeFindToggleFilters = null; 211 | guidAssetfiles = null; 212 | refFindTreeRootNode = null; 213 | refFindAsset = null; 214 | if(refFindGuidDic != null){ 215 | refFindGuidDic.Clear(); 216 | refFindGuidDic = null; 217 | } 218 | 219 | _instance = null; 220 | } 221 | 222 | void OnGUI() 223 | { 224 | BeginWindows(); 225 | 226 | winWidthHalf = position.width / 2; 227 | 228 | isCollectAssetDly = GUI.Toggle(new Rect (10, 10, 600, 20), isCollectAssetDly, "是否动态收集资源(是:每次都重新收集资源,速度慢;否:只有第一次会收集资源,速度快)"); 229 | isParseCodeDly = GUI.Toggle(new Rect (10, 30, 600, 20), isParseCodeDly, "是否动态解析代码(是:每次都重新解析代码,速度慢;否:只有第一次会解析代码,速度快)"); 230 | isParseGuidDly = GUI.Toggle(new Rect (10, 50, 600, 20), isParseGuidDly, "是否动态解析资源guid(是:每次都重新解析资源guid,速度慢;否:只有第一次会解析资源guid,速度快)"); 231 | 232 | EditorGUI.DrawRect (new Rect (0, 70, position.width, 2), Color.green); 233 | EditorGUI.DrawRect (new Rect (winWidthHalf - 1, 70, 2, position.height - 70), Color.green); 234 | 235 | OnCodeFindGUI(); 236 | OnRefFindGUI(); 237 | 238 | EndWindows(); 239 | } 240 | 241 | //代码扫描ui 242 | void OnCodeFindGUI() 243 | { 244 | leftGuiWidth = winWidthHalf - 20; 245 | 246 | bool oldCheckScene = ignoreCheckScene; 247 | ignoreCheckScene = GUI.Toggle(new Rect (10, 80, 150, 20), ignoreCheckScene, "是否忽略场景的冗余检查"); 248 | if(oldCheckScene != ignoreCheckScene){ 249 | collectFiles = null; 250 | codeFindUnUsedFiles = null; 251 | refFindGuidDic = null; 252 | SetCodeFindStage(Stage.BeginCodeParse); 253 | } 254 | 255 | if (GUI.Button (new Rect (10, 110, leftGuiWidth, 50), "开始扫描代码")) { 256 | SetCodeFindStage(Stage.BeginCodeParse); 257 | } 258 | 259 | float progressPercent = 1f; 260 | if (codeFindStage == Stage.BeginCodeParse) { 261 | SetCodeFindStage(Stage.CodeParsing); 262 | codeFindErrorCode = ErrorCode.None; 263 | CollectAssets (); 264 | ParseCode (); 265 | } else if (codeFindStage == Stage.CodeParsing) { 266 | if (codeFindProgressTotal > 0) { 267 | progressPercent = (float)codeFindProgressCur / (float)codeFindProgressTotal; 268 | } 269 | if (progressPercent >= 1f) { 270 | SetCodeFindStage(Stage.EndCodeParse); 271 | } 272 | } else if (codeFindStage == Stage.EndCodeParse) { 273 | SetCodeFindStage(Stage.CodeMatching); 274 | SetRefFindStage(Stage.BeginGuidParse); 275 | } else if (codeFindStage == Stage.CodeMatching) { 276 | if (refFindStage == Stage.EndGuidMatch) { 277 | codeFindErrorCode = ErrorCode.None; 278 | MatchCode (); 279 | isCodeFindToggleChg = true; 280 | SetCodeFindStage(Stage.EndCodeMatch); 281 | SetRefFindStage(Stage.GuidMatching); 282 | } else { 283 | codeFindErrorCode = ErrorCode.WaitForRefFindGuidParse; 284 | } 285 | } 286 | 287 | GUI.SetNextControlName("searchInputField"); 288 | codeFindSearchStr = EditorGUI.TextField (new Rect (10, 290, 450, 18), codeFindSearchStr); 289 | if ((Event.current != null && Event.current.isKey && Event.current.type == EventType.keyUp && 290 | GUI.GetNameOfFocusedControl() == "searchInputField")) { 291 | isCodeFindSearchChg = true; 292 | Repaint(); 293 | } else if (GUI.Button (new Rect (465, 290, 45, 18), "搜索")) { 294 | isCodeFindSearchChg = true; 295 | } 296 | 297 | bool isAllSelectChg = false; 298 | for(int i=0; i s.ToLower().Contains(codeFindSearchStr.ToLower())).ToArray (); 320 | } else { 321 | codeFindUnUsedFilterFiles = codeFindUnUsedFiles.ToArray(); 322 | } 323 | Array.Sort(codeFindUnUsedFilterFiles, StringComparer.OrdinalIgnoreCase); 324 | } else { 325 | List codeFindRltExts = new List(); 326 | for(int i=1; i codeFindRltExts.Contains (Path.GetExtension (s).ToLower ()) && s.ToLower().Contains(codeFindSearchStr.ToLower())).ToArray (); 335 | } else { 336 | codeFindUnUsedFilterFiles = codeFindUnUsedFiles.Where (s => codeFindRltExts.Contains (Path.GetExtension (s).ToLower ())).ToArray (); 337 | } 338 | Array.Sort(codeFindUnUsedFilterFiles, StringComparer.OrdinalIgnoreCase); 339 | } 340 | } 341 | 342 | if (codeFindStage == Stage.EndCodeMatch) { 343 | if (GUI.Button (new Rect (winWidthHalf - 96, 77, 86, 28), "导出扫描结果")) { 344 | saveCodeFindRltPath = EditorUtility.SaveFilePanel("选择导出目录", saveCodeFindRltPath, "没被代码和资源使用的冗余资源列表(仅供参考).txt", "txt"); 345 | if (codeFindUnUsedFilterFiles != null && !string.IsNullOrEmpty (saveCodeFindRltPath)) { 346 | string dirPath = System.IO.Path.GetDirectoryName (saveCodeFindRltPath); 347 | if (!System.IO.Directory.Exists(dirPath)) { 348 | try { 349 | System.IO.Directory.CreateDirectory(dirPath); 350 | } catch (Exception exp) { 351 | Debug.LogError(string.Format("create directory fail {0}", exp.ToString())); 352 | } 353 | } 354 | 355 | StringBuilder sb = new StringBuilder(); 356 | foreach (string assetName in codeFindUnUsedFilterFiles) { 357 | sb.Append(assetName); 358 | sb.Append("\n"); 359 | } 360 | System.IO.File.WriteAllText(saveCodeFindRltPath, sb.ToString()); 361 | sb.Remove(0, sb.Length); 362 | sb = null; 363 | } 364 | } 365 | 366 | GUI.Label (new Rect (10, 230, 360, 20), string.Format ("没被代码和其它资源使用的资源查找结果(仅供参考)({0}):", codeFindUnUsedFilterFiles != null ? codeFindUnUsedFilterFiles.Length : 0)); 367 | 368 | codeFindRltScrollPos = GUI.BeginScrollView (new Rect (10, 310, leftGuiWidth, position.height - 310), codeFindRltScrollPos, new Rect (0, 0, 800, codeFindUnUsedFilterFiles != null ? codeFindUnUsedFilterFiles.Length * 20 : 1), true, true); 369 | if (codeFindUnUsedFilterFiles != null) { 370 | GUIStyle btnTextGuiStyle = new GUIStyle(); 371 | btnTextGuiStyle.fontSize = 11; 372 | btnTextGuiStyle.normal.textColor = new Color (0.8f, 0.36f, 0.36f); 373 | btnTextGuiStyle.alignment = TextAnchor.MiddleLeft; 374 | 375 | int i = 0; 376 | foreach (string assetName in codeFindUnUsedFilterFiles) { 377 | if (GUI.Button (new Rect (0, i * 20, 800, 20), assetName, btnTextGuiStyle)) { 378 | Selection.activeObject = AssetDatabase.LoadAssetAtPath (assetName, typeof(UnityEngine.Object)); 379 | } 380 | i++; 381 | } 382 | } 383 | GUI.EndScrollView (); 384 | } else { 385 | GUI.Label(new Rect(10, 230, 360, 20), "没被代码和其它资源使用的资源查找结果(仅供参考):"); 386 | codeFindRltScrollPos = GUI.BeginScrollView (new Rect (10, 310, leftGuiWidth, position.height - 310), codeFindRltScrollPos, new Rect (0, 0, 800, 1), true, true); 387 | GUI.EndScrollView (); 388 | } 389 | 390 | EditorGUI.ProgressBar (new Rect (10, 170, leftGuiWidth, 50), progressPercent, string.Format("{0}({1}/{2}) {3}", StageTip [codeFindStage], 391 | codeFindProgressCur, codeFindProgressTotal, ErrorTip [codeFindErrorCode])); 392 | } 393 | 394 | //正反向依赖查找ui 395 | void OnRefFindGUI() 396 | { 397 | rightGuiWidth = winWidthHalf - 20; 398 | 399 | if(refFindTreeRootNode == null){ 400 | refFindTreeRootNode = new TreeNode("资源正反向依赖查找结果:"); 401 | refFindTreeRootNode.isRoot = true; 402 | } 403 | 404 | GUI.Label(new Rect(position.width - rightGuiWidth - 10, 80, 150, 20), "需要查正反向依赖的资源"); 405 | refFindAsset = EditorGUI.ObjectField (new Rect (position.width - rightGuiWidth + 150, 80, 250, 18), refFindAsset, typeof(UnityEngine.Object)); 406 | 407 | if (refFindAsset != null) { 408 | int assetId = refFindAsset.GetInstanceID (); 409 | if (curRefFindAssetId != assetId) { 410 | curRefFindAssetId = assetId; 411 | if (refFindStage == Stage.EndGuidMatch) { 412 | SetRefFindStage(Stage.GuidMatching); 413 | } 414 | } 415 | } else { 416 | //todo: 资源asset的GetInstanceID返回值有木有可能为0??? 417 | if (curRefFindAssetId != 0 && refFindStage == Stage.EndGuidMatch) { 418 | SetRefFindStage(Stage.GuidMatching); 419 | } 420 | curRefFindAssetId = 0; 421 | } 422 | 423 | if (GUI.Button (new Rect (position.width - rightGuiWidth - 10, 110, rightGuiWidth, 50), "正反向依赖查找")) { 424 | SetRefFindStage(Stage.BeginGuidParse); 425 | } 426 | 427 | float progressPercent = 1f; 428 | if (refFindStage == Stage.BeginGuidParse) { 429 | SetRefFindStage(Stage.GuidParsing); 430 | refFindErrorCode = ErrorCode.None; 431 | CollectAssets(); 432 | ParseAssetGuid(); 433 | } else if (refFindStage == Stage.GuidParsing) { 434 | if (refFindProgressTotal > 0) { 435 | progressPercent = (float)refFindProgressCur / (float)refFindProgressTotal; 436 | } 437 | if(progressPercent >= 1f){ 438 | SetRefFindStage(Stage.EndGuidParse); 439 | } 440 | } else if (refFindStage == Stage.EndGuidParse) { 441 | SetRefFindStage(Stage.GuidMatching); 442 | } else if (refFindStage == Stage.GuidMatching) { 443 | refFindErrorCode = ErrorCode.None; 444 | MatchAssetGuid(); 445 | SetRefFindStage(Stage.EndGuidMatch); 446 | } 447 | 448 | if (refFindStage == Stage.EndGuidMatch) { 449 | refFindTreeRootNode.title = string.Format ("资源正反向依赖查找结果({0}):", refFindTreeRootNode.children.Count); 450 | } else { 451 | refFindTreeRootNode.title = "资源正反向依赖查找结果:"; 452 | } 453 | 454 | EditorGUI.ProgressBar (new Rect (position.width - rightGuiWidth - 10, 170, rightGuiWidth, 50), progressPercent, string.Format("{0}({1}/{2}) {3}", StageTip [refFindStage], 455 | refFindProgressCur, refFindProgressTotal, ErrorTip [refFindErrorCode])); 456 | 457 | GUI.Label(new Rect(position.width - rightGuiWidth - 10, 230, 100, 20), "<--:当前资源"); 458 | if (codeFindStage == Stage.EndCodeMatch) { 459 | GUI.Label (new Rect (position.width - rightGuiWidth + 90, 230, 100, 20), "!:冗余资源嫌疑"); 460 | } 461 | 462 | refFindRltScrollPos = GUI.BeginScrollView (new Rect (position.width - rightGuiWidth - 10, 255, rightGuiWidth, position.height - 255), refFindRltScrollPos, new Rect (0, 0, refFindTreeRootNode.recursiveSize.x, refFindTreeRootNode.recursiveSize.y), true, true); 463 | refFindTreeRootNode.OnGUI(); 464 | GUI.EndScrollView (); 465 | } 466 | 467 | //设置正反向查找阶段 468 | void SetRefFindStage(Stage stage){ 469 | refFindStage = stage; 470 | 471 | Repaint(); //刷新gui 472 | } 473 | 474 | //设置代码扫描阶段 475 | void SetCodeFindStage(Stage stage){ 476 | codeFindStage = stage; 477 | 478 | Repaint(); //刷新gui 479 | } 480 | 481 | //收集资源 482 | void CollectAssets(){ 483 | if(collectFiles != null && !isCollectAssetDly){ 484 | return; 485 | } 486 | 487 | string[] files = AssetDatabase.GetAllAssetPaths(); 488 | files = files.Where (s => s.StartsWith("Assets/") && !s.StartsWith("Assets/StreamingAssets/") && !s.EndsWith (".DS_Store") && File.Exists(s)).ToArray (); 489 | 490 | collectFiles = null; 491 | collectFiles = new HashSet(files); 492 | 493 | //todo: 目前使用资源的脚本只考虑cs和lua,如果有其它脚本可能会使用资源的话,需要扩展!!! 494 | codeFindFiles = null; 495 | if (ignoreCheckScene) { 496 | codeFindFiles = collectFiles.Where (s => !s.EndsWith (".cs") && !s.EndsWith (".lua") && !s.EndsWith (".unity")).ToArray (); 497 | } else { 498 | codeFindFiles = collectFiles.Where (s => !s.EndsWith (".cs") && !s.EndsWith (".lua")).ToArray (); 499 | } 500 | 501 | //todo: 目前使用资源的脚本只考虑cs和lua,如果有其它脚本可能会使用资源的话,需要扩展!!! 502 | codeScriptFiles = null; 503 | codeScriptFiles = collectFiles.Where (s => s.EndsWith (".cs") || s.EndsWith (".lua")).ToArray (); 504 | 505 | codeShaderFiles = null; 506 | codeShaderFiles = collectFiles.Where (s => s.EndsWith (".shader")).ToArray (); 507 | 508 | codeFindNameToPathDic = null; 509 | codeFindNameToPathDic = codeFindFiles.ToLookup(k1 => Path.GetFileName(k1), v1 => v1).ToDictionary (k2 => k2.Key, v2 => v2.First()); 510 | 511 | guidAssetfiles = null; 512 | guidAssetfiles = collectFiles.Where(s => refFindExtensions.Contains(Path.GetExtension(s).ToLower())).ToArray(); 513 | } 514 | 515 | //解析可能会使用资源的代码 516 | void ParseCode(){ 517 | if(codeFindUnUsedFiles != null && !isParseCodeDly){ 518 | return; 519 | } 520 | 521 | if(codeFindUpdate != null){ 522 | EditorApplication.update -= codeFindUpdate; 523 | codeFindUpdate = null; 524 | } 525 | 526 | codeFindProgressCur = 0; 527 | codeFindProgressTotal = codeShaderFiles.Length + codeScriptFiles.Length; 528 | codeFindUnUsedFiles = null; 529 | codeFindUnUsedFiles = new HashSet(codeFindFiles, StringComparer.OrdinalIgnoreCase); //StringComparer.OrdinalIgnoreCase 忽略路径大小写 530 | 531 | Dictionary shaderNameToPath = new Dictionary(); 532 | HashSet findAssetPaths = new HashSet(); 533 | string absolutePath; 534 | string fileContent; 535 | codeFindUpdate = () => { 536 | Repaint(); 537 | 538 | if(codeFindProgressCur >= codeFindProgressTotal){ 539 | EditorApplication.update -= codeFindUpdate; 540 | codeFindUpdate = null; 541 | codeFindUnUsedFiles.ExceptWith(findAssetPaths); 542 | codeFindUnUsedFileDic = null; 543 | codeFindUnUsedFileDic = codeFindUnUsedFiles.ToDictionary (k => k, v => true); 544 | return; 545 | } 546 | 547 | if(codeFindProgressCur < codeShaderFiles.Length){ 548 | //解析shader 549 | absolutePath = System.IO.Path.Combine(Application.dataPath, Regex.Replace(codeShaderFiles[codeFindProgressCur], "^Assets/", "")); 550 | fileContent = File.ReadAllText(absolutePath); 551 | foreach (System.Text.RegularExpressions.Match mtch in Regex.Matches(fileContent, shaderPathRegStr)) { 552 | string shaderName = mtch.Groups [1].Value; 553 | if(!shaderNameToPath.ContainsKey(shaderName)){ 554 | shaderNameToPath.Add(shaderName, codeShaderFiles[codeFindProgressCur].Replace(@"\\", @"/")); 555 | } 556 | } 557 | } else { 558 | //解析cs和lua 559 | absolutePath = System.IO.Path.Combine(Application.dataPath, Regex.Replace(codeScriptFiles[codeFindProgressCur - codeShaderFiles.Length], "^Assets/", "")); 560 | fileContent = File.ReadAllText(absolutePath); 561 | foreach (System.Text.RegularExpressions.Match mtch in Regex.Matches(fileContent, assetPathRegStr, RegexOptions.IgnoreCase)) { 562 | string assetName = mtch.Groups [1].Value; 563 | if(mtch.Groups [2].Value == ""){ 564 | if(codeFindNameToPathDic.ContainsKey(assetName)){ 565 | findAssetPaths.Add (codeFindNameToPathDic[assetName]); 566 | } 567 | } else { 568 | findAssetPaths.Add (assetName.Replace(@"\\", @"/")); 569 | } 570 | } 571 | 572 | foreach (System.Text.RegularExpressions.Match mtch in Regex.Matches(fileContent, shaderPathRegStr)) { 573 | string shaderName = mtch.Groups [1].Value; 574 | if(shaderNameToPath.ContainsKey(shaderName)){ 575 | findAssetPaths.Add (shaderNameToPath[shaderName]); 576 | } 577 | } 578 | } 579 | 580 | codeFindProgressCur++; 581 | }; 582 | 583 | EditorApplication.update += codeFindUpdate; 584 | } 585 | 586 | //解析资源guid 587 | void ParseAssetGuid(){ 588 | if(refFindGuidDic != null && !isParseGuidDly){ 589 | return; 590 | } 591 | 592 | if(refFindUpdate != null){ 593 | EditorApplication.update -= refFindUpdate; 594 | refFindUpdate = null; 595 | } 596 | 597 | refFindGuidDic = null; 598 | refFindGuidDic = new Dictionary>(); 599 | refFindProgressCur = 0; 600 | refFindProgressTotal = guidAssetfiles.Length; 601 | 602 | string absolutePath; 603 | refFindUpdate = () => { 604 | Repaint(); 605 | 606 | if(refFindProgressCur >= refFindProgressTotal){ 607 | EditorApplication.update -= refFindUpdate; 608 | refFindUpdate = null; 609 | return; 610 | } 611 | 612 | absolutePath = System.IO.Path.Combine(Application.dataPath, Regex.Replace(guidAssetfiles[refFindProgressCur], "^Assets/", "")); 613 | 614 | foreach (System.Text.RegularExpressions.Match mtch in Regex.Matches(File.ReadAllText(absolutePath), guidRegStr)) { 615 | string guid = mtch.Groups [mtch.Groups.Count - 1].Value; 616 | HashSet list = null; 617 | if (!refFindGuidDic.TryGetValue (guid, out list)) { 618 | list = new HashSet (); 619 | refFindGuidDic.Add (guid, list); 620 | } 621 | list.Add (guidAssetfiles [refFindProgressCur]); 622 | } 623 | refFindProgressCur++; 624 | }; 625 | 626 | EditorApplication.update += refFindUpdate; 627 | } 628 | 629 | //匹配代码中资源 630 | void MatchCode(){ 631 | if (codeFindUnUsedFiles == null || refFindGuidDic == null) { 632 | return; 633 | } 634 | 635 | HashSet beRefAssetFiles = new HashSet(); 636 | HashSet list = null; 637 | string guid; 638 | HashSet allRefAssetPaths = new HashSet(); 639 | bool isAllRefUnUsed = false; 640 | 641 | foreach (string assetName in codeFindUnUsedFiles) { 642 | guid = AssetDatabase.AssetPathToGUID(assetName); 643 | if (guid != null && refFindGuidDic.TryGetValue (guid, out list)) { 644 | isAllRefUnUsed = true; 645 | allRefAssetPaths.Clear(); 646 | GetAllRefAssets(assetName, allRefAssetPaths); 647 | foreach (string assetName1 in allRefAssetPaths) { 648 | if(!codeFindUnUsedFileDic.ContainsKey(assetName1)){ 649 | isAllRefUnUsed = false; 650 | break; 651 | } 652 | } 653 | 654 | if(!isAllRefUnUsed){ 655 | beRefAssetFiles.Add(assetName); 656 | } 657 | } 658 | } 659 | 660 | codeFindUnUsedFiles.ExceptWith(beRefAssetFiles); 661 | foreach (string assetName in beRefAssetFiles) { 662 | if(codeFindUnUsedFileDic.ContainsKey(assetName)){ 663 | codeFindUnUsedFileDic.Remove(assetName); 664 | } 665 | } 666 | codeFindUnUsedFilterFiles = null; 667 | codeFindUnUsedFilterFiles = codeFindUnUsedFiles.ToArray(); 668 | Array.Sort(codeFindUnUsedFilterFiles, StringComparer.OrdinalIgnoreCase); 669 | } 670 | 671 | void GetAllRefAssets(string assetPath, HashSet refRootAssetPaths){ 672 | string guid = AssetDatabase.AssetPathToGUID(assetPath); 673 | 674 | HashSet list = null; 675 | if (refFindGuidDic.TryGetValue (guid, out list)) { 676 | foreach (string assetName in list) { 677 | refRootAssetPaths.Add(assetName); 678 | GetAllRefAssets(assetName, refRootAssetPaths); 679 | } 680 | } 681 | } 682 | 683 | //匹配资源guid 684 | void MatchAssetGuid(){ 685 | refFindTreeRootNode.DestroyAllChild(); 686 | 687 | if (refFindGuidDic == null) { 688 | return; 689 | } 690 | 691 | if (refFindAsset == null) { 692 | return; 693 | } 694 | 695 | string assetPath = AssetDatabase.GetAssetPath(refFindAsset); 696 | GenerRefTreeNodeRecly(assetPath, null); 697 | } 698 | 699 | //递归生成正反向依赖assetPath对应资源的资源树节点 700 | void GenerRefTreeNodeRecly(string assetPath, TreeNode childNode){ 701 | if (string.IsNullOrEmpty (assetPath)) { 702 | return; 703 | } 704 | 705 | if(childNode == null){ 706 | childNode = new TreeNode (null, AssetDatabase.LoadAssetAtPath (assetPath, typeof(UnityEngine.Object))); 707 | if (codeFindUnUsedFileDic != null && codeFindUnUsedFileDic.ContainsKey (assetPath)) { 708 | childNode.SetSubTitle ("<--!", Color.red); 709 | } else { 710 | childNode.SetSubTitle ("<--", Color.green); 711 | } 712 | GenerDependTreeNodes(assetPath, childNode); 713 | } 714 | 715 | HashSet list = null; 716 | string guid = AssetDatabase.AssetPathToGUID(assetPath); 717 | if (refFindGuidDic.TryGetValue (guid, out list)) { 718 | foreach (string assetName in list) { 719 | //Debugger.Log ("=============references path=" + assetName); 720 | TreeNode parentNode = new TreeNode (null, AssetDatabase.LoadAssetAtPath (assetName, typeof(UnityEngine.Object))); 721 | if (codeFindUnUsedFileDic != null && codeFindUnUsedFileDic.ContainsKey (assetName)) { 722 | parentNode.SetSubTitle ("!", Color.red); 723 | } 724 | if (childNode.parent == null) { 725 | parentNode.AddChild (childNode); 726 | } else { 727 | TreeNode childNodeClone = childNode.Clone(); 728 | parentNode.AddChild(childNodeClone); 729 | } 730 | 731 | GenerRefTreeNodeRecly(assetName, parentNode); 732 | } 733 | } else { 734 | if (childNode != null) { 735 | refFindTreeRootNode.AddChild (childNode); 736 | } 737 | } 738 | } 739 | 740 | //生成assetPath对应资源正向依赖的资源树节点 741 | void GenerDependTreeNodes(string assetPath, TreeNode parentNode){ 742 | if (string.IsNullOrEmpty (assetPath) || parentNode == null) { 743 | return; 744 | } 745 | 746 | string[] dependencies = AssetDatabase.GetDependencies(new string[] { assetPath }, false); 747 | if(dependencies.Length == 0){ 748 | return; 749 | } 750 | Array.Sort(dependencies, StringComparer.OrdinalIgnoreCase); 751 | 752 | foreach (string assetName in dependencies) { 753 | TreeNode childNode = new TreeNode (null, AssetDatabase.LoadAssetAtPath (assetName, typeof(UnityEngine.Object))); 754 | if (codeFindUnUsedFileDic != null && codeFindUnUsedFileDic.ContainsKey (assetName)) { 755 | childNode.SetSubTitle ("!", Color.red); 756 | } 757 | parentNode.AddChild(childNode); 758 | } 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /Editor/FindUnUsedAssetWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4408e4e6d1648fc4580752738f1b799f 3 | timeCreated: 1506246488 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/YLYEditorGUI.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0767cc8e2b18d8d4ea1f9f69f695d453 3 | folderAsset: yes 4 | timeCreated: 1506247632 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Editor/YLYEditorGUI/TreeNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 折叠树节点控件(编辑器模式下使用) 3 | author:雨Lu尧 4 | email:cantry100@163.com 5 | blog:http://www.hiwrz.com 6 | */ 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using UnityEngine; 10 | using UnityEditor; 11 | using System; 12 | 13 | namespace YLYEditorGUI { 14 | public class TreeNode { 15 | public static float nodeDefaultMarginX = 20f; 16 | public static float nodeDefaultWidth = 100f; 17 | public static float nodeDefaultWidthObject = 180f; 18 | public static float nodeDefaultHeight = 20f; 19 | 20 | public bool isShow = true; 21 | public bool isRoot = false; 22 | public bool isDestroy = false; 23 | public string title = null; 24 | public string subTitle = null; 25 | public UnityEngine.Object assetObject = null; 26 | public List children = new List(); 27 | public TreeNode parent = null; 28 | public Rect position = new Rect(0, 0, nodeDefaultWidth, nodeDefaultHeight); 29 | public Vector2 recursiveSize = new Vector2(nodeDefaultWidth, nodeDefaultHeight); 30 | public float marginX = nodeDefaultMarginX; 31 | public GUIStyle subTitleGuiStyle = null; 32 | 33 | public TreeNode(string title, UnityEngine.Object assetObject = null){ 34 | this.title = title; 35 | this.assetObject = assetObject; 36 | 37 | if (title == null) { 38 | position.width = nodeDefaultWidthObject; 39 | } 40 | } 41 | 42 | //外部调用这个函数刷新gui 43 | public void OnGUI(){ 44 | if (title == null) { 45 | isShow = EditorGUI.Foldout (new Rect (position.x, position.y, 20, position.height), isShow, ""); 46 | EditorGUI.ObjectField (new Rect (position.x + 20, position.y, 160, position.height), assetObject, typeof(UnityEngine.Object)); 47 | if (subTitle != null) { 48 | EditorGUI.LabelField (new Rect (position.x + 186, position.y, position.width, position.height), subTitle, subTitleGuiStyle); 49 | } 50 | } else { 51 | isShow = EditorGUI.Foldout (position, isShow, title); 52 | if (subTitle != null) { 53 | EditorGUI.LabelField (new Rect (position.x + position.width + 5, position.y, position.width, position.height), subTitle, subTitleGuiStyle); 54 | } 55 | } 56 | 57 | if (isShow) { 58 | float childY = position.y + position.height; 59 | for (int i = 0; i < children.Count; i++) { 60 | if (children [i] != null) { 61 | children [i].position.y = childY; 62 | children [i].OnGUI (); 63 | childY = childY + children [i].recursiveSize.y; 64 | } 65 | } 66 | } 67 | 68 | CalcRecursiveSize(); 69 | } 70 | 71 | public int GetShowChildNum(){ 72 | int num = 0; 73 | if(isShow){ 74 | num++; 75 | } 76 | 77 | for(int i=0; i 0f){ 130 | recursiveSize.x = childMaxWidth + Math.Abs(marginX); 131 | } 132 | } 133 | } 134 | 135 | public void SetPositionX(float x){ 136 | position.x = x; 137 | 138 | for (int i = 0; i < children.Count; i++) { 139 | if (children [i] != null) { 140 | children [i].SetPositionX(position.x + marginX); 141 | } 142 | } 143 | } 144 | 145 | public bool IsShowRecursively(){ 146 | if(!isShow){ 147 | return false; 148 | } 149 | 150 | if(parent == null){ 151 | return isShow; 152 | } 153 | 154 | return parent.IsShowRecursively(); 155 | } 156 | 157 | public void AddChild(TreeNode node){ 158 | if(node == null){ 159 | return; 160 | } 161 | 162 | if (node == this) { 163 | return; 164 | } 165 | 166 | if (node.isDestroy) { 167 | return; 168 | } 169 | 170 | if(node.parent == this){ 171 | return; 172 | } 173 | 174 | node.SetParent(null); 175 | children.Add(node); 176 | node.parent = this; 177 | node.SetPositionX(position.x + marginX); 178 | } 179 | 180 | public void RemoveChild(TreeNode node){ 181 | if(node == null){ 182 | return; 183 | } 184 | 185 | int index = -1; 186 | for(int i=0; i