using UnityEditor; using UnityEngine; using System; using System.IO; using System.Collections; using System.Collections.Generic; class AssetData { // 枚举值为遍历的优先级,用于AssetData.Dependcies和AssetDataManager.Assets public static readonly int AssetTypeCount = 9; public enum AssetType { Scene = 0, Prefab, Mesh, Material, Asset, Texture, Shader, Script, CSV, Unknown = 255, } public AssetData(FileSystemInfo f) { for (int i = 0; i < AssetTypeCount; ++i) Dependencies[i] = new List(); AssetDataManager manager = AssetDataManager.Get(); AssetPath = manager.FullPathToAssetPath(f.FullName); GUID = AssetDatabase.AssetPathToGUID(AssetPath); Name = f.Name; ObjType = AssetDatabase.GetMainAssetTypeAtPath(AssetPath); FileType = ExtensionToAssetType(f.Extension); Extension = f.Extension; string[] depsPath = AssetDatabase.GetDependencies(AssetPath, false); for(int i = 0; i < depsPath.Length; ++i) { string path = depsPath[i]; string guid = AssetDatabase.AssetPathToGUID(path); if (guid == GUID) continue; string extension = manager.GetExtension(path); AssetType type = ExtensionToAssetType(extension); if (type == AssetType.Unknown) continue; Dependencies[(int)type].Add(guid); ++RefCount; List data; if (!AssetDataManager.AssetsReverse.TryGetValue(guid, out data)) { data = new List(); AssetDataManager.AssetsReverse.Add(guid, data); } data.Add(GUID); } FileInfo finfo = new FileInfo(f.FullName); DiskSize = finfo.Length; DiskSizeStr = EditorUtility.FormatBytes(DiskSize); } #region 资源属性 public string GUID; public string Name; public int RefCount = 0; public int RevRefCount = 0; public long DiskSize; public string DiskSizeStr; public string AssetPath; public Type ObjType; public string Extension; public AssetType FileType; public List[] Dependencies = new List[AssetTypeCount]; #endregion private AssetType ExtensionToAssetType(string extension) { if (extension == ".unity") return AssetType.Scene; else if (extension == ".prefab") return AssetType.Prefab; else if (extension == ".mat") return AssetType.Material; else if (extension == ".asset") return AssetType.Asset; else if (extension == ".fbx" || extension == ".FBX") return AssetType.Mesh; else if (extension == ".png" || extension == ".jpg" || extension == ".bmp") return AssetType.Texture; else if (extension == ".shader") return AssetType.Shader; else if (extension == ".cs") return AssetType.Script; else if (extension == ".csv") return AssetType.CSV; else return AssetType.Unknown; } } class AssetDataManager { private static AssetDataManager manager; public static AssetDataManager Get() { if (manager == null) manager = new AssetDataManager(); return manager; } public AssetDataManager() { for (int i = 0; i < AssetData.AssetTypeCount; ++i) Assets[i] = new Dictionary(); } #region Asset字典 // 所有资源 public static Dictionary AllAssets = new Dictionary(); // 分类统计 public static Dictionary[] Assets = new Dictionary[AssetData.AssetTypeCount]; // 反向依赖 public static Dictionary> AssetsReverse = new Dictionary>(); #endregion #region 过滤 public static List ValidExtension = new List { ".unity", // scene ".prefab", // prefab ".mat", // material ".asset", // asset ".fbx", ".FBX", // mesh ".png", ".jpg", ".bmp", // texture ".shader", // shader ".cs", // script ".csv" // CSV }; public string GetExtension(string file) { if (file == null || file.Length == 0) return null; int len = file.Length; int idx = file.LastIndexOf('.'); return file.Substring(idx, len - idx); } public bool IsValidFile(string file) { string extension = GetExtension(file); foreach (var ext in ValidExtension) { if (ext == extension) return true; } return false; } public List InvalidFolder = new List { }; public bool IsValidFolder(string folderName) { foreach(var dir in InvalidFolder) { if(dir == folderName) return false; } return true; } #endregion public string FullPathToAssetPath(string fullpath) { fullpath = fullpath.Replace('\\', '/'); string path = "Assets" + fullpath.Replace(Application.dataPath, ""); path = path.Replace('/', '\\'); return path; } public string AssetPathToFullPath(string assetpath) { string fullpath = Application.dataPath.Replace("/Assets", "") + "/" + assetpath; return fullpath; } public void LoadAssets(string fullpath) { DirectoryInfo directoryInfo = new DirectoryInfo(fullpath); FileSystemInfo[] filesInfo = directoryInfo.GetFileSystemInfos(); foreach(FileSystemInfo f in filesInfo) { if(f is DirectoryInfo && IsValidFolder(f.Name)) { LoadAssets(f.FullName); } else if(f.Extension != ".meta" && IsValidFile(f.Name)) { AssetData asset = new AssetData(f); AllAssets.Add(asset.GUID, asset); Assets[(int)asset.FileType].Add(asset.GUID, asset); } } } public void ClearAssets() { AllAssets.Clear(); foreach (var assets in Assets) assets.Clear(); AssetsReverse.Clear(); } } class Drawer { private Drawer() { SelectorButtonStyle = new GUIStyle(GUIStyle.none); SelectorButtonStyle.normal.background = CreateTexture(1, 1, new Color32(62, 95, 150, 255)); } private Texture2D CreateTexture(int w, int h, Color col) { Color[] pixels = new Color[w * h]; for(int i = 0; i < w * h; ++i) pixels[i] = col; Texture2D texture = new Texture2D(w, h); texture.SetPixels(pixels); return texture; } private static Drawer drawer = null; public static Drawer Get() { if (drawer == null) drawer = new Drawer(); return drawer; } public void DrawButtonMid(Rect rect, string content) { GUI.Button(rect, content, EditorStyles.miniButtonMid); } public void DrawButton(Rect rect, string content, out bool click) { click = GUI.Button(rect, content); } public void DrawLabel(Rect rect, string content) { GUI.Label(rect, content); } public void DrawTextfield(Rect rect, string content, ref string value) { value = GUI.TextField(rect, content); } public void DrawCheckBox(Rect rect, string content, bool check, ref bool ischeck) { ischeck = GUI.Toggle(rect, check, content); } public void DrawBackground(Rect rect, Color col) { Color old = GUI.backgroundColor; GUI.backgroundColor = col; GUI.Box(rect, ""); GUI.backgroundColor = old; } public void DrawFoldout(Rect rect, ref bool foldout) { foldout = EditorGUI.Foldout(rect, foldout, ""); } public void DrawAssetIcon(Rect rect, AssetData asset) { GUIContent content = EditorGUIUtility.ObjectContent(null, asset.ObjType); content.text = asset.Name; GUI.Label(rect, content); } public void DrawSelector(Rect rect, bool selected, out bool clicked) { SelectorButtonStyle.normal.background = CreateTexture(1, 1, new Color32(62, 95, 150, 255)); clicked = GUI.Button(rect, "",selected ? SelectorButtonStyle : GUIStyle.none); } #region 配置项 public Color ColorHeavy = new Color(0.2f, 0.2f, 0.2f); public Color ColorLight = new Color(0.3f, 0.3f, 0.3f); public GUIStyle SelectorButtonStyle; #endregion } // 文件树 class FileTree { public class Node { public Node(string guid) { GUID = guid; Foldout = false; } public bool Foldout; // 是否展开 public string GUID; // GUID public List Children = new List(); // 子节点 } public List root = new List(); } class AssetBrowser : EditorWindow { private static AssetBrowser editor; private static Drawer drawer; private FileTree assetsTree = new FileTree(); #region GUI 组件配置数值 private Rect window = new Rect(); private int contentWidth = 1600; private int contentHeight = 1000; private readonly int kLineHeight = 20; // 行高 private Vector2 scrollPos = new Vector2(0, 0); private int directoryOffsetY = 5; // 资源目录Y偏移度 private int filtersOffsetY = 27; // 过滤器Y偏移度 private int headerOffsetY = 50; // 标题Y偏移度 private int assetsTreeOffsetY = 70; // 列表偏移度 private int kTabWidth = 15; #endregion #region 数据 private string directory = ""; private FileTree.Node selectedNode = null; #endregion #region 过滤器 private string filterFile = ""; private bool filterScene = false; private bool filterPrefab = false; private bool filterMaterial = false; private bool filterAsset = false; private bool filterMesh = false; private bool filterTexture = false; private bool filterShader = false; private bool filterScript = false; private bool filterCSV = false; #endregion [MenuItem("Window/Asset Browser")] public static void Show() { editor = GetWindow(); editor.titleContent = new GUIContent("Asset Browser"); drawer = Drawer.Get(); } private enum Colum { Name, // 资源名 Ref, // 引用数 RevRef, // 被引用数 Type, // 资源类型 DiskSize, // 硬盘大小 Path, // 资源路径 } private Dictionary ColumHeader = new Dictionary { {Colum.Name, "资源引用关系"}, {Colum.Ref, "引用数"}, {Colum.RevRef, "被引用数"}, {Colum.Type, "资源类型"}, {Colum.DiskSize, "硬盘大小"}, {Colum.Path, "资源路径"}, }; private Dictionary ColumWidth = new Dictionary { {Colum.Name, 500}, {Colum.Ref, 100}, {Colum.RevRef, 100}, {Colum.Type, 100}, {Colum.DiskSize, 100}, {Colum.Path, 500}, }; private Dictionary ColumOffsetX = new Dictionary(); private void AdjustColumOffsetX() { if (ColumOffsetX == null) return; ColumOffsetX.Clear(); ColumOffsetX.Add(Colum.Name, 0); ColumOffsetX.Add(Colum.Ref, ColumOffsetX[Colum.Name] + ColumWidth[Colum.Name]); ColumOffsetX.Add(Colum.RevRef, ColumOffsetX[Colum.Ref] + ColumWidth[Colum.Ref]); ColumOffsetX.Add(Colum.Type, ColumOffsetX[Colum.RevRef] + ColumWidth[Colum.RevRef]); ColumOffsetX.Add(Colum.DiskSize, ColumOffsetX[Colum.Type] + ColumWidth[Colum.Type]); ColumOffsetX.Add(Colum.Path, ColumOffsetX[Colum.DiskSize] + ColumWidth[Colum.DiskSize]); } private void AdjustWindow() { window = editor.position; } public void OnGUI() { // 调试 if (editor == null) editor = GetWindow(); drawer = Drawer.Get(); AdjustColumOffsetX(); AdjustWindow(); BeginScrollView(); // 绘制工具栏 OnToolset(); // 绘制资源列表 OnAsset(); EndScrollView(); } private void BeginScrollView() { scrollPos = GUI.BeginScrollView(new Rect(0, 0, window.width, window.height), scrollPos, new Rect(0, 0, contentWidth, contentHeight), true, true); } private void EndScrollView() { GUI.EndScrollView(); } private void OnToolset() { OnDirectory(); OnFilters(); } private void OnDirectory() { float offsetY = directoryOffsetY + scrollPos.y; drawer.DrawLabel(new Rect(0, offsetY, 95, kLineHeight), "资源目录 Assets/"); drawer.DrawTextfield(new Rect(95, offsetY, 250, kLineHeight), directory, ref directory); bool load; drawer.DrawButton(new Rect(350, offsetY, 50, kLineHeight), "载入", out load); if(load) { ProcessAssets(); ProcessTree(); EditorUtility.DisplayDialog("载入完毕", "载入完毕", "确定"); } } private void OnFilters() { // 文件匹配 drawer.DrawLabel(new Rect(0, filtersOffsetY + scrollPos.y, 100, kLineHeight), "匹配文件"); drawer.DrawTextfield(new Rect(50, filtersOffsetY + scrollPos.y, 150, kLineHeight), filterFile, ref filterFile); // 类型过滤 float offsetX = 250; float offsetY = filtersOffsetY + scrollPos.y; int width = 70; drawer.DrawCheckBox(new Rect(offsetX, offsetY, width, kLineHeight), "场景", filterScene, ref filterScene); drawer.DrawCheckBox(new Rect(offsetX + width, offsetY, width, kLineHeight), "Prefab", filterPrefab, ref filterPrefab); drawer.DrawCheckBox(new Rect(offsetX + width * 2, offsetY, width, kLineHeight), "材质", filterMaterial, ref filterMaterial); drawer.DrawCheckBox(new Rect(offsetX + width * 3, offsetY, width, kLineHeight), "Asset", filterAsset, ref filterAsset); drawer.DrawCheckBox(new Rect(offsetX + width * 4, offsetY, width, kLineHeight), "Mesh", filterMesh, ref filterMesh); drawer.DrawCheckBox(new Rect(offsetX + width * 5, offsetY, width, kLineHeight), "贴图", filterTexture, ref filterTexture); drawer.DrawCheckBox(new Rect(offsetX + width * 6, offsetY, width, kLineHeight), "Shader", filterShader, ref filterShader); drawer.DrawCheckBox(new Rect(offsetX + width * 7, offsetY, width, kLineHeight), "脚本", filterScript, ref filterScript); drawer.DrawCheckBox(new Rect(offsetX + width * 8, offsetY, width, kLineHeight), "CSV", filterCSV, ref filterCSV); } private void OnAsset() { OnHeader(); BeginFileTreeView(); OnAssetsTree(); EndFileTreeView(); } private void BeginFileTreeView() { scrollPos = GUI.BeginScrollView( new Rect(scrollPos.x, scrollPos.y + assetsTreeOffsetY, window.width, window.height - assetsTreeOffsetY), scrollPos, new Rect(0, 0, contentWidth, contentHeight - assetsTreeOffsetY), false , false ); } private void EndFileTreeView() { GUI.EndScrollView(); } private void OnHeader() { float scrollY = scrollPos.y; drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.Name], headerOffsetY + scrollY, ColumWidth[Colum.Name], kLineHeight), ColumHeader[Colum.Name]); drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.Ref], headerOffsetY + scrollY, ColumWidth[Colum.Ref], kLineHeight), ColumHeader[Colum.Ref]); drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.RevRef], headerOffsetY + scrollY, ColumWidth[Colum.RevRef], kLineHeight), ColumHeader[Colum.RevRef]); drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.Type], headerOffsetY + scrollY, ColumWidth[Colum.Type], kLineHeight), ColumHeader[Colum.Type]); drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.DiskSize], headerOffsetY + scrollY, ColumWidth[Colum.DiskSize], kLineHeight), ColumHeader[Colum.DiskSize]); drawer.DrawButtonMid(new Rect(ColumOffsetX[Colum.Path], headerOffsetY + scrollY, ColumWidth[Colum.Path], kLineHeight), ColumHeader[Colum.Path]); } private void OnAssetsTree() { int count = 0; foreach(FileTree.Node node in assetsTree.root) { DrawNode(node, 0, ref count); } } private bool Filter(FileTree.Node node, int hierachy) { if(hierachy == 0) { AssetData asset; if (!AssetDataManager.AllAssets.TryGetValue(node.GUID, out asset)) return false; // 文件类型过滤 if (asset.FileType == AssetData.AssetType.Scene && !filterScene) return false; else if (asset.FileType == AssetData.AssetType.Prefab&& !filterPrefab) return false; else if (asset.FileType == AssetData.AssetType.Material&& !filterMaterial) return false; else if (asset.FileType == AssetData.AssetType.Asset&& !filterAsset) return false; else if (asset.FileType == AssetData.AssetType.Mesh&& !filterMesh) return false; else if (asset.FileType == AssetData.AssetType.CSV&& !filterCSV) return false; else if (asset.FileType == AssetData.AssetType.Script&& !filterScript) return false; else if (asset.FileType == AssetData.AssetType.Shader&& !filterShader) return false; else if (asset.FileType == AssetData.AssetType.Texture&& !filterTexture) return false; // 文件名 if (asset.Name.IndexOf(filterFile) == -1) return false; } return true; } // 按照深度绘制 private void DrawNode(FileTree.Node node, int hierachy, ref int count) { if (!Filter(node, hierachy)) return; ++count; contentHeight = count * kLineHeight + assetsTreeOffsetY; DrawAssetInfo(node, hierachy, count - 1); if (!node.Foldout || node.Children.Count == 0) return; foreach (FileTree.Node child in node.Children) { DrawNode(child, hierachy + 1, ref count); } } // 绘制单个 private void DrawAssetInfo(FileTree.Node node, int hierachy, int index) { Drawer drawer = Drawer.Get(); AssetData asset; if (!AssetDataManager.AllAssets.TryGetValue(node.GUID, out asset)) return; int offsetY = index * kLineHeight; // 绘制背景 drawer.DrawBackground(new Rect(0, offsetY, contentWidth, kLineHeight), (index & 1) == 1 ? drawer.ColorHeavy : drawer.ColorLight); // 绘制折叠按钮 if (node.Children.Count > 0) drawer.DrawFoldout(new Rect(hierachy * kTabWidth, offsetY, 10, kLineHeight), ref node.Foldout); // 绘制选择按钮 bool clicked; drawer.DrawSelector(new Rect(hierachy * kTabWidth + 12, offsetY, contentWidth, kLineHeight), selectedNode == node, out clicked); if (clicked) { if(selectedNode != node) selectedNode = node; else { AssetReverse.Show(node.GUID); selectedNode = null; } } // 绘制图标 drawer.DrawAssetIcon(new Rect(hierachy * kTabWidth + 12, offsetY, ColumWidth[Colum.Name], kLineHeight), asset); // 绘制引用数 drawer.DrawLabel(new Rect(ColumOffsetX[Colum.Ref], offsetY, 100, kLineHeight), asset.RefCount.ToString()); // 绘制反向引用数 List revRef; int revRefCount = 0; if (AssetDataManager.AssetsReverse.TryGetValue(node.GUID, out revRef)) revRefCount = revRef.Count; drawer.DrawLabel(new Rect(ColumOffsetX[Colum.RevRef], offsetY, 100, kLineHeight), revRefCount.ToString()); // 绘制资源类型 drawer.DrawLabel(new Rect(ColumOffsetX[Colum.Type], offsetY, 100, kLineHeight), asset.Extension); // 绘制硬盘大小 drawer.DrawLabel(new Rect(ColumOffsetX[Colum.DiskSize], offsetY, 100, kLineHeight), asset.DiskSizeStr); // 绘制资源路径 drawer.DrawLabel(new Rect(ColumOffsetX[Colum.Path], offsetY, 1000, kLineHeight), asset.AssetPath); } private void ProcessAssets() { AssetDataManager assetManager = AssetDataManager.Get(); assetManager.ClearAssets(); assetManager.LoadAssets(assetManager.AssetPathToFullPath("Assets/" + directory)); } private void ProcessTree() { AssetDataManager assetManager = AssetDataManager.Get(); assetsTree.root.Clear(); for(int i = 0; i < AssetData.AssetTypeCount; ++i) { Dictionary assets = AssetDataManager.Assets[i]; foreach(var asset in assets) { AssetData data = asset.Value; FileTree.Node node = ProcessNode(data); assetsTree.root.Add(node); } } } private FileTree.Node ProcessNode(AssetData data) { AssetDataManager assetManager = AssetDataManager.Get(); FileTree.Node node = new FileTree.Node(data.GUID); foreach(List deps in data.Dependencies) { foreach(string guid in deps) { AssetData dep; if(AssetDataManager.AllAssets.TryGetValue(guid, out dep)) { FileTree.Node child = ProcessNode(dep); node.Children.Add(child); } } } return node; } } class AssetReverse : EditorWindow { private static AssetReverse editor; private static Drawer drawer; private static string GUID; private static AssetData asset; private Vector2 scrollPos = new Vector2(0, 0); private int kLineHeight = 20; private int contentHeight = 0; public static void Show(string guid) { editor = GetWindow(); editor.titleContent = new GUIContent("Asset Reverse"); GUID = guid; if(AssetDataManager.AllAssets.TryGetValue(GUID, out asset)) { } Drawer dawer = Drawer.Get(); } public void OnGUI() { if (asset == null) return; drawer = Drawer.Get(); drawer.DrawAssetIcon(new Rect(0, 0, 200, kLineHeight), asset); scrollPos = GUI.BeginScrollView(new Rect(0, kLineHeight, editor.position.width, editor.position.height - kLineHeight), scrollPos, new Rect(0, 0, 1500, contentHeight)); // 标题 drawer.DrawButtonMid(new Rect(0, 0, 400, 20), "资源名"); drawer.DrawButtonMid(new Rect(400, 0, 400, 20), "资源路径"); // List revRef; if(AssetDataManager.AssetsReverse.TryGetValue(GUID, out revRef)) { int count = 0; foreach(string guid in revRef) { AssetData data; if(AssetDataManager.AllAssets.TryGetValue(guid, out data)) { drawer.DrawAssetIcon(new Rect(0, kLineHeight + count * kLineHeight, 400, kLineHeight), data); drawer.DrawLabel(new Rect(400, kLineHeight + count * kLineHeight, 400, kLineHeight), data.AssetPath); ++count; contentHeight = count * kLineHeight + kLineHeight; } } } GUI.EndScrollView(); } }