#unity/日常积累 表现效果: ![[Pasted image 20230609105638.png]] ``` cs using UnityEngine; using UnityEditor; using System.Collections.Generic; namespace shaco.Instance.ProjectFolderAsset { [CanEditMultipleObjects, CustomEditor(typeof(DefaultAsset))] public class ProjectFolderAssetInspector : UnityEditor.Editor { private List _assetsPath = new List(); private string _comment = string.Empty; private bool _isCommentChanged = false; void OnEnable() { _assetsPath.Clear(); for (int i = 0; i < targets.Length; ++i) { var assetPathTmp = AssetDatabase.GetAssetPath(targets[i]); if (!AssetDatabase.IsValidFolder(assetPathTmp)) continue;//@@@要支持多个设置 _assetsPath.Add(assetPathTmp); } if (_assetsPath.Count > 0) _comment = ProjectFolderAssetDataManager.instance.AssetPathToComment(_assetsPath[0]); } void OnDestroy() { if (_isCommentChanged) { ProjectFolderAssetDataManager.instance.SaveToFile(); } } public override void OnInspectorGUI() { base.OnInspectorGUI(); //只处理文件夹显示 if (0 == _assetsPath.Count) return; bool enabled = GUI.enabled; GUI.enabled = true; DrawFolder(); GUI.enabled = enabled; } private void DrawFolder() { EditorGUILayout.PrefixLabel("Comment"); GUI.changed = false; _comment = EditorGUILayout.TextArea(_comment, GUILayout.MinHeight(48)); if (GUI.changed) { for (int i = 0; i < _assetsPath.Count; ++i) ProjectFolderAssetDataManager.instance.SetComment(_assetsPath[i], _comment); _isCommentChanged = true; EditorUtility.SetDirty(target); EditorApplication.RepaintProjectWindow(); } } } /// /// 编辑器文件夹绘制使用的相关数据管理器 /// [InitializeOnLoad] public class ProjectFolderAssetDataManager { public class ProjectFolderAssetData { //该路径仅仅作为参考(方便查看配置文本),并无实际意义 public string assetPath; public string comment; } static public ProjectFolderAssetDataManager instance { get { if (null == _instance) { _instance = new ProjectFolderAssetDataManager(); _instance.LoadFromFile(_instance.SAVE_DATA_PATH); } return _instance; } } static private ProjectFolderAssetDataManager _instance = null; //保存路径 private string SAVE_DATA_PATH { get { return Application.dataPath + "/../ProjectSettings/shacoProjectFolderAssetData.txt"; } } //文件夹注释字典 private Dictionary _folderDatas = new Dictionary(); static ProjectFolderAssetDataManager() { EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI; } /// /// 获取文件夹注释 /// 文件夹路径 /// 文件夹注释,如果没有则返回空字符串 /// public string AssetPathToComment(string assetPath) { var guid = AssetDatabase.AssetPathToGUID(assetPath); return AssetGUIDToComment(guid); } /// /// 获取文件夹注释 /// 文件夹唯一id /// 文件夹注释,如果没有则返回空字符串 /// public string AssetGUIDToComment(string guid) { string retValue = null; ProjectFolderAssetData findData = null; if (_folderDatas.TryGetValue(guid, out findData)) retValue = findData.comment; else retValue = string.Empty; return retValue; } /// /// 获取文件夹数据 /// 文件夹唯一id /// 文件夹数据,没有则返回空 /// public ProjectFolderAssetData GetFolderData(string guid) { ProjectFolderAssetData findData = null; _folderDatas.TryGetValue(guid, out findData); return findData; } /// /// 设置文件夹注释,并自动保存到文件 /// 文件夹路径 /// 文件夹注释 /// public void SetComment(string assetPath, string comment) { if (string.IsNullOrEmpty(assetPath)) { Debug.LogError("ProjectFolderAssetDataManager SetComment erorr: assetPath is empty"); return; } if (!assetPath.StartsWith("Assets")) { Debug.LogError("ProjectFolderAssetDataManager SetComment erorr: not Unity project path=" + assetPath); return; } ProjectFolderAssetData findData = null; var guid = AssetDatabase.AssetPathToGUID(assetPath); if (!_folderDatas.TryGetValue(guid, out findData)) { findData = new ProjectFolderAssetData(); findData.assetPath = assetPath; findData.comment = comment; _folderDatas.Add(guid, findData); } else { if (string.IsNullOrEmpty(comment)) _folderDatas.Remove(guid); else { findData.assetPath = assetPath; findData.comment = comment; } } } /// /// 保存文件夹信息到文件中 /// 保存路径 /// public void SaveToFile() { SaveToFile(SAVE_DATA_PATH); } private void SaveToFile(string path) { if (_folderDatas.Count == 0) { System.IO.File.Delete(path); return; } var saveStr = new System.Text.StringBuilder(); foreach (var iter in _folderDatas) { saveStr.Append(iter.Key); saveStr.Append('\n'); saveStr.Append(iter.Value.assetPath); saveStr.Append('\n'); saveStr.Append(iter.Value.comment); saveStr.Append('\n'); } if (saveStr.Length > 0) saveStr.Remove(saveStr.Length - 1, 1); var directoryIath = new System.IO.DirectoryInfo(path).Parent; if (!directoryIath.Exists) { directoryIath.Create(); } System.IO.File.WriteAllText(path, saveStr.ToString(), System.Text.Encoding.UTF8); } /// /// 从文件读取文件夹信息 /// 保存路径 /// private void LoadFromFile(string path) { _folderDatas.Clear(); if (!System.IO.File.Exists(path)) return; try { using (var readStream = new System.IO.StreamReader(path)) { while (true) { var guid = readStream.ReadLine(); if (string.IsNullOrEmpty(guid)) break; var assetPath = readStream.ReadLine(); if (string.IsNullOrEmpty(assetPath)) { throw new System.Exception("ProjectFolderAssetDataManager LoadFromFile error: not found assetPath, guid=" + guid); } var comment = readStream.ReadLine(); if (string.IsNullOrEmpty(comment)) { throw new System.Exception("ProjectFolderAssetDataManager LoadFromFile error: not found comment, name=" + assetPath); } var folderAssestData = new ProjectFolderAssetData(); folderAssestData.assetPath = assetPath; folderAssestData.comment = comment; _folderDatas.Add(guid, folderAssestData); } readStream.Close(); } } catch (System.Exception e) { Debug.LogError("ProjectFolderAssetDataManager LoadFromFile exception: e=" + e); System.IO.File.Delete(path); } } static private GUIStyle _guiStyleLabelTree = null; static private GUIStyle _guiStyleLabelNotTree = null; static private void ProjectWindowItemOnGUI(string guid, Rect selectionRect) { var folderData = ProjectFolderAssetDataManager.instance.GetFolderData(guid); if (null == folderData || string.IsNullOrEmpty(folderData.comment)) return; if (null == _guiStyleLabelTree) { _guiStyleLabelTree = EditorStyles.label; _guiStyleLabelNotTree = EditorStyles.label; _guiStyleLabelTree.fontSize = 12; _guiStyleLabelNotTree.fontSize = 10; } var aliasContent = new GUIContent(folderData.comment); var isTree = IsTreeView(selectionRect); var labelStyle = isTree ? _guiStyleLabelTree : _guiStyleLabelNotTree; Vector2 labelSize = labelStyle.CalcSize(aliasContent); float offsetYWhenCurrentSelectAsset = 0; if (!isTree) { IsIconSmall(ref selectionRect); if (null != Selection.objects) { if (null != System.Array.Find(Selection.objects, v => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(v)) == guid)) offsetYWhenCurrentSelectAsset = -labelSize.y * 0.167f; } } Rect textRect = new Rect( selectionRect.x + Mathf.Max(0, (selectionRect.width - labelSize.x) * 0.5f), selectionRect.yMax + (isTree ? -labelSize.y - labelSize.y * 0.167f : labelSize.y * 0.33f - labelSize.y) + offsetYWhenCurrentSelectAsset, labelSize.x, labelSize.y); float cropWidth = selectionRect.width; if (isTree) { textRect.width = System.Math.Min(labelSize.x, selectionRect.width / 3); cropWidth = textRect.width; textRect.x = selectionRect.xMax - textRect.width; selectionRect.y = selectionRect.y; } aliasContent.text = CropText(labelStyle, aliasContent.text, cropWidth); EditorGUI.LabelField(textRect, aliasContent, labelStyle); } private static bool IsTreeView(Rect rect) { return rect.height <= 21f; } // https://github.com/PhannGor/unity3d-rainbow-folders/blob/master/Assets/Plugins/RainbowFolders/Editor/Scripts/RainbowFoldersBrowserIcons.cs private static bool IsIconSmall(ref Rect rect) { var isSmall = rect.width > rect.height; if (isSmall) rect.width = rect.height; else rect.height = rect.width; return isSmall; } static private System.Reflection.MethodInfo _getNumCharactersThatFitWithinWidth; /// /// UnityEditor.ObjectListArea - GetCroppedLabelText (G:1211) /// /// /// /// /// /// public static string CropText(GUIStyle self, string text, in float cropWidth, string symbol = "…") { if (null == _getNumCharactersThatFitWithinWidth) { _getNumCharactersThatFitWithinWidth = typeof(GUIStyle).GetMethod("GetNumCharactersThatFitWithinWidth", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); } int thatFitWithinWidth = (int)_getNumCharactersThatFitWithinWidth.Invoke(self, new object[] { text, cropWidth }); int num; switch (thatFitWithinWidth) { case -1: return text; case 0: case 1: num = 0; break; default: num = thatFitWithinWidth != text.Length ? 1 : 0; break; } text = num == 0 ? text : text.Substring(0, thatFitWithinWidth - 1) + symbol; return text; } } } ```