├── LICENSE ├── README.md ├── ReadmeDoc ├── BatchSearch.png ├── SingleSearch.png └── Start.png └── ReferenceFinder ├── AssetTreeView.cs ├── ReferenceFinderData.cs └── ReferenceFinderWindow.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity 引用查找 2 | 3 | ## 简介 4 | 5 |   这是一个用来查找资源引用和依赖的插件,通过缓存来保存资源间的引用信息,通过树状结构直观的展示。 6 |   由于是通过缓存进行实现的,所以在希望的到精确的引用信息时需要刷新缓存。不过由于缓存的存在,在资源改动较少的情况下,刷新速度较快,对使用影响较小。 7 |   直接将文件夹拖到项目中即可使用。 8 | 9 | ## 环境 10 | 11 | - Unity 2018.4.0f1 12 | - Unity 2017.4.35f1 13 | - Unity 5.6.6f2 14 | 15 | ## 使用示例 16 | 17 |   右键需要查找引用的文件或文件夹,点击 `Find References In Project` 进行查找。 18 | ![](ReadmeDoc/Start.png) 19 | **按钮含义:** 20 | Refresh Data:刷新缓存 21 | Model:切换引用和依赖 22 | NeedUpdateState:是否需要根据当前文件的状态更新State栏。 23 | Expand:展开列表 24 | Collapse:折叠列表 25 | ![](ReadmeDoc/SingleSearch.png) 26 | ![](ReadmeDoc/BatchSearch.png) 27 |   第三列的State为Changed代表这个资源被修改了,Missing代表资源被删除了,No Data代表缓存中没有该资源的信息。 28 | 29 | ## 实现方案 30 | 31 | ### 方案选择 32 | 33 |   1.在每次需要查找时进行一次全局查找,保证查找的正确性。但是进行全局查找会很慢,因为查找资源依赖信息的接口GetDependencies本质上其实是对文本的查找(比如prefab中会以文本的形式记录prefab所引用资源的guiid和fileid),在不进行多线程查询优化的情况下这是一个很慢的过程,存储在机械硬盘上时会更慢。 34 |   2.进行一次全局查找生成缓存,在进行查找时直接读取缓存(在资源改动时会出现引用信息不准确的问题),在资源变动时需要更新缓存,保持查找的正确性,不过由于缓存的存在,在资源没有太大的改动的情况下,刷新速度较快。 35 | 36 |   这里选择了方案二。 37 | 38 | ### 查找及缓存 39 | 40 | **引用信息的生成:** 41 |   通过AssetDatabase.GetAllAssetPaths()获取项目中所有的资源路径。 42 |   通过AssetDatabase.GetDependencies()获取每一个资源所依赖资源的信息。 43 |   经过这两步之后就有了所有资源的依赖信息了。 44 |   通过资源的依赖信息,我们就可以生成所有资源的引用信息了。 45 | **缓存:** 46 |   为了让缓存尽量的小,所以缓存只保存每个资源的guid、引用资源哈希值、依赖资源的信息。 47 |   其中引用资源哈希值时用于判断这个资源依赖的资源是否有修改,若有修改,则在刷新资源引用信息的时候需要重新读取这个资源的依赖信息,否则继续使用该信息。这个判断就是在资源改动较少时减少刷新时间的关键。 48 |   在记录依赖时,没有直接记录依赖资源的guid,而是记录了资源在缓存中下标的位置,从而进一步缩小缓存的大小。 49 | 50 | ### 界面实现 51 | 52 |   主要使用了Unity自带的TreeView实现树形界面的展示。 53 | -------------------------------------------------------------------------------- /ReadmeDoc/BatchSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blueberryzzz/ReferenceFinder/c69627031a7d8b2cfe94f7371c08f342079e545e/ReadmeDoc/BatchSearch.png -------------------------------------------------------------------------------- /ReadmeDoc/SingleSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blueberryzzz/ReferenceFinder/c69627031a7d8b2cfe94f7371c08f342079e545e/ReadmeDoc/SingleSearch.png -------------------------------------------------------------------------------- /ReadmeDoc/Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blueberryzzz/ReferenceFinder/c69627031a7d8b2cfe94f7371c08f342079e545e/ReadmeDoc/Start.png -------------------------------------------------------------------------------- /ReferenceFinder/AssetTreeView.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using UnityEditor.IMGUI.Controls; 4 | 5 | //带数据的TreeViewItem 6 | public class AssetViewItem : TreeViewItem 7 | { 8 | public ReferenceFinderData.AssetDescription data; 9 | } 10 | 11 | //资源引用树 12 | public class AssetTreeView : TreeView 13 | { 14 | //图标宽度 15 | const float kIconWidth = 18f; 16 | //列表高度 17 | const float kRowHeights = 20f; 18 | public AssetViewItem assetRoot; 19 | 20 | private GUIStyle stateGUIStyle = new GUIStyle { richText = true, alignment = TextAnchor.MiddleCenter }; 21 | 22 | //列信息 23 | enum MyColumns 24 | { 25 | Name, 26 | Path, 27 | State, 28 | } 29 | 30 | public AssetTreeView(TreeViewState state,MultiColumnHeader multicolumnHeader):base(state,multicolumnHeader) 31 | { 32 | rowHeight = kRowHeights; 33 | columnIndexForTreeFoldouts = 0; 34 | showAlternatingRowBackgrounds = true; 35 | showBorder = false; 36 | customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI 37 | extraSpaceBeforeIconAndLabel = kIconWidth; 38 | } 39 | 40 | //响应右击事件 41 | protected override void ContextClickedItem(int id) 42 | { 43 | SetExpanded(id, !IsExpanded(id)); 44 | } 45 | 46 | //响应双击事件 47 | protected override void DoubleClickedItem(int id) 48 | { 49 | var item = (AssetViewItem)FindItem(id, rootItem); 50 | //在ProjectWindow中高亮双击资源 51 | if (item != null) 52 | { 53 | var assetObject = AssetDatabase.LoadAssetAtPath(item.data.path, typeof(UnityEngine.Object)); 54 | EditorUtility.FocusProjectWindow(); 55 | Selection.activeObject = assetObject; 56 | EditorGUIUtility.PingObject(assetObject); 57 | } 58 | } 59 | 60 | //生成ColumnHeader 61 | public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth) 62 | { 63 | var columns = new[] 64 | { 65 | //图标+名称 66 | new MultiColumnHeaderState.Column 67 | { 68 | headerContent = new GUIContent("Name"), 69 | headerTextAlignment = TextAlignment.Center, 70 | sortedAscending = false, 71 | width = 200, 72 | minWidth = 60, 73 | autoResize = false, 74 | allowToggleVisibility = false, 75 | canSort = false 76 | }, 77 | //路径 78 | new MultiColumnHeaderState.Column 79 | { 80 | headerContent = new GUIContent("Path"), 81 | headerTextAlignment = TextAlignment.Center, 82 | sortedAscending = false, 83 | width = 360, 84 | minWidth = 60, 85 | autoResize = false, 86 | allowToggleVisibility = false, 87 | canSort = false 88 | }, 89 | //状态 90 | new MultiColumnHeaderState.Column 91 | { 92 | headerContent = new GUIContent("State"), 93 | headerTextAlignment = TextAlignment.Center, 94 | sortedAscending = false, 95 | width = 60, 96 | minWidth = 60, 97 | autoResize = false, 98 | allowToggleVisibility = true, 99 | canSort = false 100 | }, 101 | }; 102 | var state = new MultiColumnHeaderState(columns); 103 | return state; 104 | } 105 | 106 | protected override TreeViewItem BuildRoot() 107 | { 108 | return assetRoot; 109 | } 110 | 111 | protected override void RowGUI(RowGUIArgs args) 112 | { 113 | var item = (AssetViewItem)args.item; 114 | for(int i = 0; i < args.GetNumVisibleColumns(); ++i) 115 | { 116 | CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args); 117 | } 118 | } 119 | 120 | //绘制列表中的每项内容 121 | void CellGUI(Rect cellRect,AssetViewItem item,MyColumns column, ref RowGUIArgs args) 122 | { 123 | CenterRectUsingSingleLineHeight(ref cellRect); 124 | switch (column) 125 | { 126 | case MyColumns.Name: 127 | { 128 | var iconRect = cellRect; 129 | iconRect.x += GetContentIndent(item); 130 | iconRect.width = kIconWidth; 131 | if (iconRect.x < cellRect.xMax) 132 | { 133 | var icon = GetIcon(item.data.path); 134 | if(icon != null) 135 | GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit); 136 | } 137 | args.rowRect = cellRect; 138 | base.RowGUI(args); 139 | } 140 | break; 141 | case MyColumns.Path: 142 | { 143 | GUI.Label(cellRect, item.data.path); 144 | } 145 | break; 146 | case MyColumns.State: 147 | { 148 | GUI.Label(cellRect, ReferenceFinderData.GetInfoByState(item.data.state),stateGUIStyle); 149 | } 150 | break; 151 | } 152 | } 153 | 154 | //根据资源信息获取资源图标 155 | private Texture2D GetIcon(string path) 156 | { 157 | Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Object)); 158 | if (obj != null) 159 | { 160 | Texture2D icon = AssetPreview.GetMiniThumbnail(obj); 161 | if (icon == null) 162 | icon = AssetPreview.GetMiniTypeThumbnail(obj.GetType()); 163 | return icon; 164 | } 165 | return null; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /ReferenceFinder/ReferenceFinderData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Serialization.Formatters.Binary; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | public class ReferenceFinderData 10 | { 11 | //缓存路径 12 | private const string CACHE_PATH = "Library/ReferenceFinderCache"; 13 | private const string CACHE_VERSION = "V1"; 14 | //资源引用信息字典 15 | public Dictionary assetDict = new Dictionary(); 16 | 17 | //收集资源引用信息并更新缓存 18 | public void CollectDependenciesInfo() 19 | { 20 | try 21 | { 22 | ReadFromCache(); 23 | var allAssets = AssetDatabase.GetAllAssetPaths(); 24 | int totalCount = allAssets.Length; 25 | for (int i = 0; i < allAssets.Length; i++) 26 | { 27 | //每遍历100个Asset,更新一下进度条,同时对进度条的取消操作进行处理 28 | if ((i % 100 == 0) && EditorUtility.DisplayCancelableProgressBar("Refresh", string.Format("Collecting {0} assets", i), (float)i / totalCount)) 29 | { 30 | EditorUtility.ClearProgressBar(); 31 | return; 32 | } 33 | if (File.Exists(allAssets[i])) 34 | ImportAsset(allAssets[i]); 35 | if (i % 2000 == 0) 36 | GC.Collect(); 37 | } 38 | //将信息写入缓存 39 | EditorUtility.DisplayCancelableProgressBar("Refresh", "Write to cache", 1f); 40 | WriteToChache(); 41 | //生成引用数据 42 | EditorUtility.DisplayCancelableProgressBar("Refresh", "Generating asset reference info", 1f); 43 | UpdateReferenceInfo(); 44 | EditorUtility.ClearProgressBar(); 45 | } 46 | catch(Exception e) 47 | { 48 | Debug.LogError(e); 49 | EditorUtility.ClearProgressBar(); 50 | } 51 | } 52 | 53 | //通过依赖信息更新引用信息 54 | private void UpdateReferenceInfo() 55 | { 56 | foreach(var asset in assetDict) 57 | { 58 | foreach(var assetGuid in asset.Value.dependencies) 59 | { 60 | if(assetDict.ContainsKey(assetGuid) && !assetDict[assetGuid].references.Contains(asset.Key)) 61 | { 62 | assetDict[assetGuid].references.Add(asset.Key); 63 | } 64 | } 65 | } 66 | } 67 | 68 | //生成并加入引用信息 69 | private void ImportAsset(string path) 70 | { 71 | if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/")) 72 | return; 73 | 74 | //通过path获取guid进行储存 75 | string guid = AssetDatabase.AssetPathToGUID(path); 76 | //获取该资源的最后修改时间,用于之后的修改判断 77 | Hash128 assetDependencyHash = AssetDatabase.GetAssetDependencyHash(path); 78 | //如果assetDict没包含该guid或包含了修改时间不一样则需要更新 79 | if (!assetDict.ContainsKey(guid) || assetDict[guid].assetDependencyHash != assetDependencyHash.ToString()) 80 | { 81 | //将每个资源的直接依赖资源转化为guid进行储存 82 | var guids = AssetDatabase.GetDependencies(path, false). 83 | Select(p => AssetDatabase.AssetPathToGUID(p)). 84 | ToList(); 85 | 86 | //生成asset依赖信息,被引用需要在所有的asset依赖信息生成完后才能生成 87 | AssetDescription ad = new AssetDescription(); 88 | ad.name = Path.GetFileNameWithoutExtension(path); 89 | ad.path = path; 90 | ad.assetDependencyHash = assetDependencyHash.ToString(); 91 | ad.dependencies = guids; 92 | 93 | if (assetDict.ContainsKey(guid)) 94 | assetDict[guid] = ad; 95 | else 96 | assetDict.Add(guid, ad); 97 | } 98 | } 99 | 100 | //读取缓存信息 101 | public bool ReadFromCache() 102 | { 103 | assetDict.Clear(); 104 | if (!File.Exists(CACHE_PATH)) 105 | { 106 | return false; 107 | } 108 | 109 | var serializedGuid = new List(); 110 | var serializedDependencyHash = new List(); 111 | var serializedDenpendencies = new List(); 112 | //反序列化数据 113 | FileStream fs = File.OpenRead(CACHE_PATH); 114 | try 115 | { 116 | BinaryFormatter bf = new BinaryFormatter(); 117 | string cacheVersion = (string) bf.Deserialize(fs); 118 | if (cacheVersion != CACHE_VERSION) 119 | { 120 | return false; 121 | } 122 | 123 | EditorUtility.DisplayCancelableProgressBar("Import Cache", "Reading Cache", 0); 124 | serializedGuid = (List) bf.Deserialize(fs); 125 | serializedDependencyHash = (List) bf.Deserialize(fs); 126 | serializedDenpendencies = (List) bf.Deserialize(fs); 127 | EditorUtility.ClearProgressBar(); 128 | } 129 | catch 130 | { 131 | //兼容旧版本序列化格式 132 | return false; 133 | } 134 | finally 135 | { 136 | fs.Close(); 137 | } 138 | 139 | for (int i = 0; i < serializedGuid.Count; ++i) 140 | { 141 | string path = AssetDatabase.GUIDToAssetPath(serializedGuid[i]); 142 | if (!string.IsNullOrEmpty(path)) 143 | { 144 | var ad = new AssetDescription(); 145 | ad.name = Path.GetFileNameWithoutExtension(path); 146 | ad.path = path; 147 | ad.assetDependencyHash = serializedDependencyHash[i]; 148 | assetDict.Add(serializedGuid[i], ad); 149 | } 150 | } 151 | 152 | for(int i = 0; i < serializedGuid.Count; ++i) 153 | { 154 | string guid = serializedGuid[i]; 155 | if (assetDict.ContainsKey(guid)) 156 | { 157 | var guids = serializedDenpendencies[i]. 158 | Select(index => serializedGuid[index]). 159 | Where(g => assetDict.ContainsKey(g)). 160 | ToList(); 161 | assetDict[guid].dependencies = guids; 162 | } 163 | } 164 | UpdateReferenceInfo(); 165 | return true; 166 | } 167 | 168 | //写入缓存 169 | private void WriteToChache() 170 | { 171 | if (File.Exists(CACHE_PATH)) 172 | File.Delete(CACHE_PATH); 173 | 174 | var serializedGuid = new List(); 175 | var serializedDependencyHash = new List(); 176 | var serializedDenpendencies = new List(); 177 | //辅助映射字典 178 | var guidIndex = new Dictionary(); 179 | //序列化 180 | using (FileStream fs = File.OpenWrite(CACHE_PATH)) 181 | { 182 | foreach (var pair in assetDict) 183 | { 184 | guidIndex.Add(pair.Key, guidIndex.Count); 185 | serializedGuid.Add(pair.Key); 186 | serializedDependencyHash.Add(pair.Value.assetDependencyHash); 187 | } 188 | 189 | foreach(var guid in serializedGuid) 190 | { 191 | //使用 Where 子句过滤目录 192 | int[] indexes = assetDict[guid].dependencies. 193 | Where(s => guidIndex.ContainsKey(s)). 194 | Select(s => guidIndex[s]).ToArray(); 195 | serializedDenpendencies.Add(indexes); 196 | } 197 | 198 | BinaryFormatter bf = new BinaryFormatter(); 199 | bf.Serialize(fs, CACHE_VERSION); 200 | bf.Serialize(fs, serializedGuid); 201 | bf.Serialize(fs, serializedDependencyHash); 202 | bf.Serialize(fs, serializedDenpendencies); 203 | } 204 | } 205 | 206 | //更新引用信息状态 207 | public void UpdateAssetState(string guid) 208 | { 209 | AssetDescription ad; 210 | if (assetDict.TryGetValue(guid,out ad) && ad.state != AssetState.NODATA) 211 | { 212 | if (File.Exists(ad.path)) 213 | { 214 | //修改时间与记录的不同为修改过的资源 215 | if (ad.assetDependencyHash != AssetDatabase.GetAssetDependencyHash(ad.path).ToString()) 216 | { 217 | ad.state = AssetState.CHANGED; 218 | } 219 | else 220 | { 221 | //默认为普通资源 222 | ad.state = AssetState.NORMAL; 223 | } 224 | } 225 | //不存在为丢失 226 | else 227 | { 228 | ad.state = AssetState.MISSING; 229 | } 230 | } 231 | 232 | //字典中没有该数据 233 | else if(!assetDict.TryGetValue(guid, out ad)) 234 | { 235 | string path = AssetDatabase.GUIDToAssetPath(guid); 236 | ad = new AssetDescription(); 237 | ad.name = Path.GetFileNameWithoutExtension(path); 238 | ad.path = path; 239 | ad.state = AssetState.NODATA; 240 | assetDict.Add(guid, ad); 241 | } 242 | } 243 | 244 | //根据引用信息状态获取状态描述 245 | public static string GetInfoByState(AssetState state) 246 | { 247 | if(state == AssetState.CHANGED) 248 | { 249 | return "Changed"; 250 | } 251 | else if (state == AssetState.MISSING) 252 | { 253 | return "Missing"; 254 | } 255 | else if(state == AssetState.NODATA) 256 | { 257 | return "No Data"; 258 | } 259 | return "Normal"; 260 | } 261 | 262 | public class AssetDescription 263 | { 264 | public string name = ""; 265 | public string path = ""; 266 | public string assetDependencyHash; 267 | public List dependencies = new List(); 268 | public List references = new List(); 269 | public AssetState state = AssetState.NORMAL; 270 | } 271 | 272 | public enum AssetState 273 | { 274 | NORMAL, 275 | CHANGED, 276 | MISSING, 277 | NODATA, 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /ReferenceFinder/ReferenceFinderWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEditor.IMGUI.Controls; 6 | 7 | public class ReferenceFinderWindow : EditorWindow 8 | { 9 | //依赖模式的key 10 | const string isDependPrefKey = "ReferenceFinderData_IsDepend"; 11 | //是否需要更新信息状态的key 12 | const string needUpdateStatePrefKey = "ReferenceFinderData_needUpdateState"; 13 | 14 | private static ReferenceFinderData data = new ReferenceFinderData(); 15 | private static bool initializedData = false; 16 | 17 | private bool isDepend = false; 18 | private bool needUpdateState = true; 19 | 20 | private bool needUpdateAssetTree = false; 21 | private bool initializedGUIStyle = false; 22 | //工具栏按钮样式 23 | private GUIStyle toolbarButtonGUIStyle; 24 | //工具栏样式 25 | private GUIStyle toolbarGUIStyle; 26 | //选中资源列表 27 | private List selectedAssetGuid = new List(); 28 | 29 | private AssetTreeView m_AssetTreeView; 30 | 31 | [SerializeField] 32 | private TreeViewState m_TreeViewState; 33 | 34 | //查找资源引用信息 35 | [MenuItem("Assets/Find References In Project %#&f", false, 25)] 36 | static void FindRef() 37 | { 38 | InitDataIfNeeded(); 39 | OpenWindow(); 40 | ReferenceFinderWindow window = GetWindow(); 41 | window.UpdateSelectedAssets(); 42 | } 43 | 44 | //打开窗口 45 | [MenuItem("Window/Reference Finder", false, 1000)] 46 | static void OpenWindow() 47 | { 48 | ReferenceFinderWindow window = GetWindow(); 49 | window.wantsMouseMove = false; 50 | window.titleContent = new GUIContent("Ref Finder"); 51 | window.Show(); 52 | window.Focus(); 53 | } 54 | 55 | //初始化数据 56 | static void InitDataIfNeeded() 57 | { 58 | if (!initializedData) 59 | { 60 | //初始化数据 61 | if(!data.ReadFromCache()) 62 | { 63 | data.CollectDependenciesInfo(); 64 | } 65 | initializedData = true; 66 | } 67 | } 68 | 69 | //初始化GUIStyle 70 | void InitGUIStyleIfNeeded() 71 | { 72 | if (!initializedGUIStyle) 73 | { 74 | toolbarButtonGUIStyle = new GUIStyle("ToolbarButton"); 75 | toolbarGUIStyle = new GUIStyle("Toolbar"); 76 | initializedGUIStyle = true; 77 | } 78 | } 79 | 80 | //更新选中资源列表 81 | private void UpdateSelectedAssets() 82 | { 83 | selectedAssetGuid.Clear(); 84 | foreach(var obj in Selection.objects) 85 | { 86 | string path = AssetDatabase.GetAssetPath(obj); 87 | //如果是文件夹 88 | if (Directory.Exists(path)) 89 | { 90 | string[] folder = new string[] { path }; 91 | //将文件夹下所有资源作为选择资源 92 | string[] guids = AssetDatabase.FindAssets(null, folder); 93 | foreach(var guid in guids) 94 | { 95 | if (!selectedAssetGuid.Contains(guid) && 96 | !Directory.Exists(AssetDatabase.GUIDToAssetPath(guid))) 97 | { 98 | selectedAssetGuid.Add(guid); 99 | } 100 | } 101 | } 102 | //如果是文件资源 103 | else 104 | { 105 | string guid = AssetDatabase.AssetPathToGUID(path); 106 | selectedAssetGuid.Add(guid); 107 | } 108 | } 109 | needUpdateAssetTree = true; 110 | } 111 | 112 | //通过选中资源列表更新TreeView 113 | private void UpdateAssetTree() 114 | { 115 | if (needUpdateAssetTree && selectedAssetGuid.Count != 0) 116 | { 117 | var root = SelectedAssetGuidToRootItem(selectedAssetGuid); 118 | if(m_AssetTreeView == null) 119 | { 120 | //初始化TreeView 121 | if (m_TreeViewState == null) 122 | m_TreeViewState = new TreeViewState(); 123 | var headerState = AssetTreeView.CreateDefaultMultiColumnHeaderState(position.width); 124 | var multiColumnHeader = new MultiColumnHeader(headerState); 125 | m_AssetTreeView = new AssetTreeView(m_TreeViewState, multiColumnHeader); 126 | } 127 | m_AssetTreeView.assetRoot = root; 128 | m_AssetTreeView.CollapseAll(); 129 | m_AssetTreeView.Reload(); 130 | needUpdateAssetTree = false; 131 | } 132 | } 133 | 134 | private void OnEnable() 135 | { 136 | isDepend = PlayerPrefs.GetInt(isDependPrefKey, 0) == 1; 137 | needUpdateState = PlayerPrefs.GetInt(needUpdateStatePrefKey, 1) == 1; 138 | } 139 | 140 | private void OnGUI() 141 | { 142 | InitGUIStyleIfNeeded(); 143 | DrawOptionBar(); 144 | UpdateAssetTree(); 145 | if (m_AssetTreeView != null) 146 | { 147 | //绘制Treeview 148 | m_AssetTreeView.OnGUI(new Rect(0, toolbarGUIStyle.fixedHeight, position.width, position.height - toolbarGUIStyle.fixedHeight)); 149 | } 150 | } 151 | 152 | //绘制上条 153 | public void DrawOptionBar() 154 | { 155 | EditorGUILayout.BeginHorizontal(toolbarGUIStyle); 156 | //刷新数据 157 | if (GUILayout.Button("Refresh Data", toolbarButtonGUIStyle)) 158 | { 159 | data.CollectDependenciesInfo(); 160 | needUpdateAssetTree = true; 161 | EditorGUIUtility.ExitGUI(); 162 | } 163 | //修改模式 164 | bool PreIsDepend = isDepend; 165 | isDepend = GUILayout.Toggle(isDepend, isDepend ? "Model(Depend)" : "Model(Reference)", toolbarButtonGUIStyle,GUILayout.Width(100)); 166 | if(PreIsDepend != isDepend){ 167 | OnModelSelect(); 168 | } 169 | //是否需要更新状态 170 | bool PreNeedUpdateState = needUpdateState; 171 | needUpdateState = GUILayout.Toggle(needUpdateState, "Need Update State", toolbarButtonGUIStyle); 172 | if (PreNeedUpdateState != needUpdateState) 173 | { 174 | PlayerPrefs.SetInt(needUpdateStatePrefKey, needUpdateState ? 1 : 0); 175 | } 176 | GUILayout.FlexibleSpace(); 177 | 178 | //扩展 179 | if (GUILayout.Button("Expand", toolbarButtonGUIStyle)) 180 | { 181 | if (m_AssetTreeView != null) m_AssetTreeView.ExpandAll(); 182 | } 183 | //折叠 184 | if (GUILayout.Button("Collapse", toolbarButtonGUIStyle)) 185 | { 186 | if (m_AssetTreeView != null) m_AssetTreeView.CollapseAll(); 187 | } 188 | EditorGUILayout.EndHorizontal(); 189 | } 190 | 191 | private void OnModelSelect() 192 | { 193 | needUpdateAssetTree = true; 194 | PlayerPrefs.SetInt(isDependPrefKey, isDepend ? 1 : 0); 195 | } 196 | 197 | 198 | //生成root相关 199 | private HashSet updatedAssetSet = new HashSet(); 200 | //通过选择资源列表生成TreeView的根节点 201 | private AssetViewItem SelectedAssetGuidToRootItem(List selectedAssetGuid) 202 | { 203 | updatedAssetSet.Clear(); 204 | int elementCount = 0; 205 | var root = new AssetViewItem { id = elementCount, depth = -1, displayName = "Root", data = null }; 206 | int depth = 0; 207 | var stack = new Stack(); 208 | foreach (var childGuid in selectedAssetGuid) 209 | { 210 | var child = CreateTree(childGuid, ref elementCount, depth, stack); 211 | if (child != null) 212 | root.AddChild(child); 213 | } 214 | updatedAssetSet.Clear(); 215 | return root; 216 | } 217 | //通过每个节点的数据生成子节点 218 | private AssetViewItem CreateTree(string guid, ref int elementCount, int _depth, Stack stack) 219 | { 220 | if (stack.Contains(guid)) 221 | return null; 222 | 223 | stack.Push(guid); 224 | if (needUpdateState && !updatedAssetSet.Contains(guid)) 225 | { 226 | data.UpdateAssetState(guid); 227 | updatedAssetSet.Add(guid); 228 | } 229 | ++elementCount; 230 | var referenceData = data.assetDict[guid]; 231 | var root = new AssetViewItem { id = elementCount, displayName = referenceData.name, data = referenceData, depth = _depth }; 232 | var childGuids = isDepend ? referenceData.dependencies : referenceData.references; 233 | foreach (var childGuid in childGuids) 234 | { 235 | var child = CreateTree(childGuid, ref elementCount, _depth + 1, stack); 236 | if (child != null) 237 | root.AddChild(child); 238 | } 239 | 240 | stack.Pop(); 241 | return root; 242 | } 243 | } 244 | --------------------------------------------------------------------------------