├── .gitignore ├── Documentation~ ├── histogrammer_1.png ├── histogrammer_2.png └── histogrammer_3.png ├── Editor.meta ├── Editor ├── HistogrammerEditorWindow.cs ├── HistogrammerEditorWindow.cs.meta ├── SearchResult.cs ├── SearchResult.cs.meta ├── TNRD.Histogrammer.Editor.asmdef ├── TNRD.Histogrammer.Editor.asmdef.meta ├── TreeView.meta ├── TreeView │ ├── HistogrammerColumnHeader.cs │ ├── HistogrammerColumnHeader.cs.meta │ ├── HistogrammerColumnHeaderState.cs │ ├── HistogrammerColumnHeaderState.cs.meta │ ├── HistogrammerTreeView.cs │ ├── HistogrammerTreeView.cs.meta │ ├── HistogrammerTreeViewItem.cs │ ├── HistogrammerTreeViewItem.cs.meta │ ├── HistogrammerTreeViewState.cs │ ├── HistogrammerTreeViewState.cs.meta │ ├── TreeViewData.cs │ └── TreeViewData.cs.meta ├── TypeUtility.cs └── TypeUtility.cs.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | /Histogrammer/.idea/ 2 | /Histogrammer/Library/ 3 | /Histogrammer/Logs/ 4 | /Histogrammer/obj/ 5 | /Histogrammer/Assets/Plugins/ 6 | /Histogrammer/Assets/Plugins.meta 7 | -------------------------------------------------------------------------------- /Documentation~/histogrammer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thundernerd/Unity3D-Histogrammer/f49aab1baac31a67fa96cc692235d2e0d9b8390a/Documentation~/histogrammer_1.png -------------------------------------------------------------------------------- /Documentation~/histogrammer_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thundernerd/Unity3D-Histogrammer/f49aab1baac31a67fa96cc692235d2e0d9b8390a/Documentation~/histogrammer_2.png -------------------------------------------------------------------------------- /Documentation~/histogrammer_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thundernerd/Unity3D-Histogrammer/f49aab1baac31a67fa96cc692235d2e0d9b8390a/Documentation~/histogrammer_3.png -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b755b552b73469942b7021db07363e62 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/HistogrammerEditorWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using UnityEditor; 8 | using UnityEngine; 9 | 10 | namespace TNRD.Histogrammer 11 | { 12 | public class HistogrammerEditorWindow : EditorWindow, ISerializationCallbackReceiver 13 | { 14 | [MenuItem("Window/TNRD/Histogrammer")] 15 | private static void Open() 16 | { 17 | HistogrammerEditorWindow wnd = GetWindow(false, "Histogrammer", true); 18 | wnd.minSize = new Vector2(320, 320); 19 | wnd.Show(); 20 | } 21 | 22 | private static GUIContent[] DEFAULT_POPUP_CONTENT = {new GUIContent("None")}; 23 | 24 | [SerializeField] private MonoScript monoScript; 25 | [SerializeField] private Vector2 scrollPosition; 26 | 27 | private Type scriptType; 28 | private List filteredFields = new List(); 29 | private GUIContent[] popupContent = DEFAULT_POPUP_CONTENT; 30 | private int selectedIndex; 31 | private FieldInfo SelectedField { get { return filteredFields[selectedIndex]; } } 32 | 33 | private GUIStyle headerStyle; 34 | 35 | private Rect actualBoxRect; 36 | 37 | private readonly Dictionary> valueToSearchResults = 38 | new Dictionary>(); 39 | 40 | private readonly Dictionary valueToFoldout = 41 | new Dictionary(); 42 | 43 | private readonly Dictionary valueToTreeViewData = 44 | new Dictionary(); 45 | 46 | private int totalSearchResults; 47 | 48 | private void OnEnable() 49 | { 50 | if (headerStyle == null) 51 | { 52 | headerStyle = new GUIStyle(EditorStyles.boldLabel) 53 | { 54 | alignment = TextAnchor.MiddleLeft 55 | }; 56 | } 57 | } 58 | 59 | private void OnGUI() 60 | { 61 | EditorGUI.BeginChangeCheck(); 62 | monoScript = (MonoScript) EditorGUILayout.ObjectField("Script", monoScript, typeof(MonoScript), false); 63 | if (EditorGUI.EndChangeCheck()) 64 | { 65 | LoadType(); 66 | CreatePopupContent(); 67 | } 68 | 69 | EditorGUI.BeginDisabledGroup(ShouldDisableFieldPopup()); 70 | selectedIndex = EditorGUILayout.Popup(new GUIContent("Field"), selectedIndex, popupContent); 71 | EditorGUI.EndDisabledGroup(); 72 | 73 | EditorGUI.BeginDisabledGroup(ShouldDisableSearchButton()); 74 | if (GUILayout.Button("Search")) 75 | { 76 | Search(); 77 | } 78 | 79 | EditorGUI.EndDisabledGroup(); 80 | 81 | OnResultsGUI(); 82 | } 83 | 84 | private void LoadType() 85 | { 86 | filteredFields.Clear(); 87 | 88 | if (monoScript == null) 89 | return; 90 | 91 | scriptType = monoScript.GetClass(); 92 | 93 | LoadFields(); 94 | } 95 | 96 | private void LoadFields() 97 | { 98 | List fields = TypeUtility.GetFieldsUpTo(scriptType, 99 | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 100 | typeof(MonoBehaviour)); 101 | 102 | foreach (FieldInfo fieldInfo in fields) 103 | { 104 | if (fieldInfo.IsPublic) 105 | { 106 | if (TypeUtility.HasCustomAttribute(fieldInfo)) 107 | continue; 108 | 109 | filteredFields.Add(fieldInfo); 110 | continue; 111 | } 112 | 113 | if (!TypeUtility.HasCustomAttribute(fieldInfo)) 114 | continue; 115 | 116 | filteredFields.Add(fieldInfo); 117 | } 118 | 119 | filteredFields = filteredFields 120 | .OrderBy(x => x.DeclaringType.Name) 121 | .ThenBy(x => x.Name) 122 | .ToList(); 123 | } 124 | 125 | private void CreatePopupContent() 126 | { 127 | if (monoScript == null || filteredFields.Count == 0) 128 | { 129 | popupContent = new[] {new GUIContent("None")}; 130 | selectedIndex = 0; 131 | return; 132 | } 133 | 134 | popupContent = new GUIContent[filteredFields.Count]; 135 | 136 | for (int i = 0; i < filteredFields.Count; i++) 137 | { 138 | FieldInfo field = filteredFields[i]; 139 | string name = string.Format("{0}/{1}", 140 | ObjectNames.NicifyVariableName(field.DeclaringType.Name), 141 | ObjectNames.NicifyVariableName(field.Name)); 142 | GUIContent content = new GUIContent(name); 143 | popupContent[i] = content; 144 | } 145 | } 146 | 147 | private bool ShouldDisableFieldPopup() 148 | { 149 | if (monoScript == null) 150 | return true; 151 | 152 | return false; 153 | } 154 | 155 | private bool ShouldDisableSearchButton() 156 | { 157 | if (monoScript == null) 158 | return true; 159 | 160 | if (filteredFields.Count == 0) 161 | return true; 162 | 163 | return false; 164 | } 165 | 166 | private void Search() 167 | { 168 | SlimClear(); 169 | SearchPrefabs(); 170 | CreateTreeViews(); 171 | 172 | totalSearchResults = valueToSearchResults.Sum(x => x.Value.Count); 173 | } 174 | 175 | private void SearchPrefabs() 176 | { 177 | string[] paths = Directory.GetFiles(Application.dataPath, "*.prefab", SearchOption.AllDirectories); 178 | 179 | FieldInfo fieldInfo = SelectedField; 180 | 181 | try 182 | { 183 | float step = 1f / paths.Length; 184 | float progress = 0f; 185 | 186 | foreach (string path in paths) 187 | { 188 | if (EditorUtility.DisplayCancelableProgressBar("Hold on...", "Scanning prefabs", progress)) 189 | { 190 | SlimClear(); 191 | break; 192 | } 193 | 194 | string trimmedPath = path.Replace(Application.dataPath, string.Empty); 195 | string relativePath = string.Format("Assets{0}", trimmedPath); 196 | GameObject prefab = AssetDatabase.LoadAssetAtPath(relativePath); 197 | Component[] components = prefab.GetComponentsInChildren(scriptType); 198 | 199 | foreach (Component component in components) 200 | { 201 | object value = fieldInfo.GetValue(component); 202 | string name = GetPath(component, prefab); 203 | SearchResult result = new SearchResult(name, value, relativePath, component); 204 | AddSearchResult(result); 205 | } 206 | 207 | progress += step; 208 | } 209 | } 210 | finally 211 | { 212 | EditorUtility.ClearProgressBar(); 213 | } 214 | } 215 | 216 | private string GetPath(Component context, GameObject upperLimit) 217 | { 218 | string path = context.GetType().Name; 219 | 220 | Transform transform = context.transform; 221 | 222 | while (transform != null) 223 | { 224 | path = string.Format("{0}/{1}", transform.name, path); 225 | if (transform.gameObject == upperLimit) 226 | break; 227 | transform = transform.parent; 228 | } 229 | 230 | return path; 231 | } 232 | 233 | private void AddSearchResult(SearchResult result) 234 | { 235 | if (!valueToSearchResults.ContainsKey(result.Value)) 236 | { 237 | valueToSearchResults.Add(result.Value, new List()); 238 | valueToFoldout.Add(result.Value, false); 239 | } 240 | 241 | valueToSearchResults[result.Value].Add(result); 242 | } 243 | 244 | private void CreateTreeViews() 245 | { 246 | foreach (KeyValuePair> result in valueToSearchResults) 247 | { 248 | HistogrammerColumnHeaderState columnHeaderState = HistogrammerColumnHeaderState.Create(); 249 | HistogrammerTreeViewState treeViewState = new HistogrammerTreeViewState(); 250 | TreeViewData treeViewData = TreeViewData.Create(treeViewState, columnHeaderState, result.Value); 251 | valueToTreeViewData.Add(result.Key, treeViewData); 252 | } 253 | } 254 | 255 | private void OnResultsGUI() 256 | { 257 | EditorGUILayout.Space(); 258 | 259 | Rect headerRect = EditorGUILayout.GetControlRect(false, 20f, GUILayout.ExpandWidth(true)); 260 | headerRect.x = 0; 261 | headerRect.width = position.size.x; 262 | GUI.Box(headerRect, string.Empty, EditorStyles.toolbar); 263 | headerRect.xMin += 5; 264 | EditorGUI.LabelField(headerRect, "Results", headerStyle); 265 | 266 | Rect boxRect = 267 | EditorGUILayout.GetControlRect(false, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); 268 | boxRect.x = 0; 269 | boxRect.y -= EditorGUIUtility.standardVerticalSpacing + 2; 270 | boxRect.width = position.size.x; 271 | boxRect.height += EditorGUIUtility.standardVerticalSpacing + 2; 272 | GUI.Box(boxRect, string.Empty); 273 | 274 | if (Event.current.type == EventType.Repaint) 275 | actualBoxRect = boxRect; 276 | 277 | if (valueToSearchResults.Count == 0) 278 | { 279 | EditorGUI.LabelField(actualBoxRect, "No results available"); 280 | return; 281 | } 282 | 283 | GUILayout.BeginArea(actualBoxRect); 284 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 285 | 286 | foreach (KeyValuePair> keyValuePair in valueToSearchResults) 287 | { 288 | OnResultGUI(keyValuePair.Key, keyValuePair.Value); 289 | } 290 | 291 | EditorGUILayout.EndScrollView(); 292 | GUILayout.EndArea(); 293 | } 294 | 295 | private void OnResultGUI(object value, List results) 296 | { 297 | Rect rect = EditorGUILayout.GetControlRect(false); 298 | float count = results.Count; 299 | EditorGUI.ProgressBar(rect, count / totalSearchResults, 300 | string.Format("{0} ({1}/{2})", ValueToString(value), count, totalSearchResults)); 301 | 302 | if (IsMouseDown(rect)) 303 | { 304 | valueToFoldout[value] = !valueToFoldout[value]; 305 | Repaint(); 306 | GUIUtility.ExitGUI(); 307 | } 308 | 309 | EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link); 310 | 311 | OnFoldoutGUI(value); 312 | } 313 | 314 | private void OnFoldoutGUI(object value) 315 | { 316 | if (!valueToFoldout[value]) 317 | return; 318 | 319 | TreeViewData treeViewData = valueToTreeViewData[value]; 320 | Rect rect = EditorGUILayout.GetControlRect(false, treeViewData.TreeView.totalHeight); 321 | treeViewData.TreeView.OnGUI(rect); 322 | } 323 | 324 | private string ValueToString(object value) 325 | { 326 | if (value == null) 327 | return "NULL"; 328 | 329 | Type valueType = value.GetType(); 330 | if (valueType == typeof(string)) 331 | { 332 | if (string.IsNullOrEmpty((string) value)) 333 | return "NULL"; 334 | } 335 | else if (valueType == typeof(LayerMask)) 336 | { 337 | return LayerMask.LayerToName(((LayerMask) value).value); 338 | } 339 | 340 | if (TypeUtility.IsEnumerable(value)) 341 | { 342 | IEnumerable enumerable = (IEnumerable) value; 343 | string[] stringified = enumerable.Cast() 344 | .Select(ValueToString) 345 | .ToArray(); 346 | return string.Format("[{0}]", string.Join(", ", stringified)); 347 | } 348 | 349 | return value.ToString(); 350 | } 351 | 352 | private bool IsMouseDown(Rect rect) 353 | { 354 | Event evt = Event.current; 355 | return evt.type == EventType.MouseDown && evt.button == 0 && rect.Contains(evt.mousePosition); 356 | } 357 | 358 | public void OnBeforeSerialize() 359 | { 360 | } 361 | 362 | public void OnAfterDeserialize() 363 | { 364 | FullReset(); 365 | } 366 | 367 | private void FullReset() 368 | { 369 | monoScript = null; 370 | selectedIndex = 0; 371 | popupContent = DEFAULT_POPUP_CONTENT; 372 | SlimClear(); 373 | } 374 | 375 | private void SlimClear() 376 | { 377 | scrollPosition = Vector2.zero; 378 | valueToFoldout.Clear(); 379 | valueToSearchResults.Clear(); 380 | valueToTreeViewData.Clear(); 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /Editor/HistogrammerEditorWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3647f0a4b5751b4ebcb0ea3faa89770 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TNRD.Histogrammer 4 | { 5 | public class SearchResult 6 | { 7 | public readonly string Name; 8 | public readonly object Value; 9 | public readonly string Path; 10 | public readonly Component Context; 11 | 12 | public SearchResult(string name, object value, string path, Component context) 13 | { 14 | Name = name; 15 | Value = value ?? string.Empty; 16 | Path = path; 17 | Context = context; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Editor/SearchResult.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8c199f250ac41f98d28e91c7321797a 3 | timeCreated: 1574945025 -------------------------------------------------------------------------------- /Editor/TNRD.Histogrammer.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TNRD.Histogrammer.Editor", 3 | "references": [], 4 | "includePlatforms": [ 5 | "Editor" 6 | ], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Editor/TNRD.Histogrammer.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0dea24535df50ba4cbcfe9222821ed62 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/TreeView.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64bb1b32f6c94ac69b610d0f4e3dc7eb 3 | timeCreated: 1574936536 -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerColumnHeader.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor.IMGUI.Controls; 2 | 3 | namespace TNRD.Histogrammer 4 | { 5 | public class HistogrammerColumnHeader : MultiColumnHeader 6 | { 7 | public HistogrammerColumnHeader(MultiColumnHeaderState state) : base(state) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerColumnHeader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac29b1c174c94356b6e54682956795b5 3 | timeCreated: 1574936656 -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerColumnHeaderState.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor.IMGUI.Controls; 2 | using UnityEngine; 3 | 4 | namespace TNRD.Histogrammer 5 | { 6 | public class HistogrammerColumnHeaderState : MultiColumnHeaderState 7 | { 8 | private HistogrammerColumnHeaderState(Column[] columns) : base(columns) 9 | { 10 | } 11 | 12 | public static HistogrammerColumnHeaderState Create() 13 | { 14 | return new HistogrammerColumnHeaderState( 15 | new[] 16 | { 17 | new Column() 18 | { 19 | autoResize = true, 20 | canSort = false, 21 | headerContent = new GUIContent("Name"), 22 | width = 100, 23 | }, 24 | new Column() 25 | { 26 | autoResize = true, 27 | canSort = false, 28 | headerContent = new GUIContent("Path"), 29 | width = 200, 30 | }, 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerColumnHeaderState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 296a10e3085a4b6fa64a3410e2ed4c21 3 | timeCreated: 1574936671 -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeView.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEditor; 4 | using UnityEditor.IMGUI.Controls; 5 | using UnityEngine; 6 | 7 | namespace TNRD.Histogrammer 8 | { 9 | public class HistogrammerTreeView : TreeView 10 | { 11 | private List items = new List(); 12 | 13 | private Dictionary hashToTreeViewItem = new Dictionary(); 14 | 15 | private HistogrammerTreeView(TreeViewState state) : base(state) 16 | { 17 | } 18 | 19 | public HistogrammerTreeView(HistogrammerTreeViewState state, HistogrammerColumnHeader multiColumnHeader) 20 | : base(state, multiColumnHeader) 21 | { 22 | showAlternatingRowBackgrounds = true; 23 | showBorder = true; 24 | } 25 | 26 | public void Initialize(List results) 27 | { 28 | items = new List(); 29 | 30 | int id = 0; 31 | foreach (SearchResult result in results) 32 | { 33 | string[] splits = result.Name.Split('/'); 34 | 35 | int depth = -1; 36 | TreeViewItem parent = null; 37 | for (int i = 0; i < splits.Length; i++) 38 | { 39 | string split = splits[i]; 40 | int hash = GetHash(i, splits.Length, result.Context); 41 | 42 | TreeViewItem treeViewItem; 43 | if (!hashToTreeViewItem.TryGetValue(hash, out treeViewItem)) 44 | { 45 | treeViewItem = new HistogrammerTreeViewItem(result) 46 | { 47 | id = ++id, 48 | depth = ++depth, 49 | displayName = split, 50 | icon = i == splits.Length - 1 51 | ? (Texture2D) EditorGUIUtility.ObjectContent(null, typeof(MonoScript)).image 52 | : (Texture2D) EditorGUIUtility.ObjectContent(null, typeof(GameObject)).image 53 | }; 54 | 55 | hashToTreeViewItem[hash] = treeViewItem; 56 | 57 | if (i == 0) // Only add top level to the list, others are children 58 | items.Add(treeViewItem); 59 | } 60 | else 61 | { 62 | ++depth; 63 | } 64 | 65 | if (parent != null) 66 | parent.AddChild(treeViewItem); 67 | 68 | parent = treeViewItem; 69 | } 70 | } 71 | 72 | Reload(); 73 | } 74 | 75 | private int GetHash(int index, int total, Component context) 76 | { 77 | // Root/GameObject/Component 78 | // loop/total-1/total-2 79 | 80 | if (index == total - 1) // context itself 81 | return context.GetHashCode(); 82 | if (index == total - 2) // context's gameobject 83 | return context.transform.GetHashCode(); 84 | 85 | int count = total - index - 2; 86 | for (int i = 0; i < count; i++) 87 | { 88 | context = context.transform.parent; 89 | } 90 | 91 | return context.GetHashCode(); 92 | } 93 | 94 | protected override TreeViewItem BuildRoot() 95 | { 96 | TreeViewItem root = new TreeViewItem(0, -1, "Root"); 97 | 98 | foreach (TreeViewItem item in items) 99 | { 100 | root.AddChild(item); 101 | } 102 | 103 | return root; 104 | } 105 | 106 | protected override void RowGUI(RowGUIArgs args) 107 | { 108 | for (int i = 0; i < args.GetNumVisibleColumns(); i++) 109 | { 110 | Rect rect = args.GetCellRect(i); 111 | TreeViewItem item = args.item; 112 | int column = args.GetColumn(i); 113 | CellGUI(rect, item, column, ref args); 114 | } 115 | } 116 | 117 | private void CellGUI(Rect rect, TreeViewItem item, int column, ref RowGUIArgs args) 118 | { 119 | HistogrammerTreeViewItem histogrammerTreeViewItem = (HistogrammerTreeViewItem) item; 120 | 121 | switch (column) 122 | { 123 | case 0: 124 | { 125 | args.rowRect = rect; 126 | base.RowGUI(args); 127 | break; 128 | } 129 | case 1: 130 | { 131 | if (item.depth == 0) 132 | { 133 | EditorGUI.LabelField(rect, histogrammerTreeViewItem.SearchResult.Path); 134 | } 135 | 136 | break; 137 | } 138 | } 139 | } 140 | 141 | protected override void SingleClickedItem(int id) 142 | { 143 | HistogrammerTreeViewItem item = FindItem(id); 144 | GameObject root = GetRootForSelection(item.SearchResult.Context); 145 | EditorGUIUtility.PingObject(root); 146 | } 147 | 148 | protected override void DoubleClickedItem(int id) 149 | { 150 | HistogrammerTreeViewItem item = FindItem(id); 151 | GameObject root = GetRootForSelection(item.SearchResult.Context); 152 | AssetDatabase.OpenAsset(root); 153 | } 154 | 155 | private HistogrammerTreeViewItem FindItem(int id) 156 | { 157 | IList rows = FindRows(new List {id}); 158 | if (rows.Count != 1) 159 | return null; 160 | 161 | HistogrammerTreeViewItem item = (HistogrammerTreeViewItem) rows.First(); 162 | return item; 163 | } 164 | 165 | private GameObject GetRootForSelection(Component context) 166 | { 167 | Transform root = context.transform; 168 | 169 | while (root.parent != null) 170 | { 171 | root = context.gameObject.transform.parent; 172 | } 173 | 174 | return root.gameObject; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 471b759bcc7544e9bf0ce745cfb4cdee 3 | timeCreated: 1574936263 -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor.IMGUI.Controls; 2 | 3 | namespace TNRD.Histogrammer 4 | { 5 | public class HistogrammerTreeViewItem : TreeViewItem 6 | { 7 | public readonly SearchResult SearchResult; 8 | 9 | public HistogrammerTreeViewItem(SearchResult searchResult) 10 | { 11 | SearchResult = searchResult; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeViewItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8fc5fd344a1f4410af24910e304ba992 3 | timeCreated: 1574936517 -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeViewState.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor.IMGUI.Controls; 2 | 3 | namespace TNRD.Histogrammer 4 | { 5 | public class HistogrammerTreeViewState : TreeViewState 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Editor/TreeView/HistogrammerTreeViewState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 420494de7fe9468ea48393578edd61d3 3 | timeCreated: 1574936405 -------------------------------------------------------------------------------- /Editor/TreeView/TreeViewData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TNRD.Histogrammer 4 | { 5 | public class TreeViewData 6 | { 7 | public readonly HistogrammerTreeView TreeView; 8 | public readonly HistogrammerTreeViewState TreeViewState; 9 | public readonly HistogrammerColumnHeader ColumnHeader; 10 | public readonly HistogrammerColumnHeaderState ColumnHeaderState; 11 | 12 | private TreeViewData(HistogrammerTreeViewState treeViewState, HistogrammerColumnHeaderState columnHeaderState) 13 | { 14 | TreeViewState = treeViewState; 15 | ColumnHeaderState = columnHeaderState; 16 | ColumnHeader = new HistogrammerColumnHeader(columnHeaderState); 17 | TreeView = new HistogrammerTreeView(treeViewState, ColumnHeader); 18 | } 19 | 20 | public static TreeViewData Create(HistogrammerTreeViewState treeViewState, 21 | HistogrammerColumnHeaderState columnHeaderState, List results) 22 | { 23 | TreeViewData treeViewData = new TreeViewData(treeViewState, columnHeaderState); 24 | treeViewData.TreeView.Initialize(results); 25 | return treeViewData; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Editor/TreeView/TreeViewData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b043f10b15146ef82998e18f589199e 3 | timeCreated: 1574949847 -------------------------------------------------------------------------------- /Editor/TypeUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace TNRD.Histogrammer 8 | { 9 | public class TypeUtility 10 | { 11 | public static List GetFieldsUpTo(Type type, BindingFlags bindingAttr, Type stopWhenReached) 12 | { 13 | List fields = new List(); 14 | 15 | while (type != null && type != stopWhenReached) 16 | { 17 | fields.AddRange(type.GetFields(bindingAttr)); 18 | type = type.BaseType; 19 | } 20 | 21 | return fields; 22 | } 23 | 24 | public static bool IsEnumerable(object value) 25 | { 26 | if (value == null) 27 | return false; 28 | 29 | Type type = value.GetType(); 30 | return typeof(IEnumerable).IsAssignableFrom(type); 31 | } 32 | 33 | public static bool HasCustomAttribute(MemberInfo memberInfo) 34 | { 35 | return memberInfo.GetCustomAttributes(typeof(T), false).Any(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Editor/TypeUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d25e6fb7c0740a880c050dc53473cbd 3 | timeCreated: 1574938766 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Christiaan Bloemendaal 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 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67675c0625488dc4b99d944120816ca0 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Histogrammer 2 | 3 |

4 | GitHub package.json version 5 | 6 | GitHub issues 7 | 8 | 9 | GitHub pull requests 10 | 11 | 12 | GitHub license 13 | 14 | GitHub last commit 15 |

16 | 17 | Histogrammer is a tool for Unity3D that helps you pinpoint redundant data in your project. 18 | 19 | Nobody likes a messy project. You know how it goes. You work on your project for a long time and things just stack up. Hundreds of prefabs, many more scripts, and you just start to lose a bit of oversight. 20 | 21 | Another issue is that sometimes you just expose too much in the inspector. Every time you look at a prefab you see too many options you can customize. Most stay the same, and half of them you don't even know what they do anymore. 22 | 23 | This is where *Histogrammer* comes in. With this tool you can more easily pinpoint data in your project that isn't actually being used all that often, or just straight up is the same value across all prefabs. 24 | 25 |

26 | 27 |

28 | 29 | ## Usage 30 | 31 | *These steps assume that Unity has been opened and Histogrammer has been added to the project.* 32 | 33 | 1. Open *Histogrammer* by selecting *Window/TNRD/Histogrammer* from the top menu 34 | 35 | 2. Select a script using the script field or drag-and-drop one from your project browser 36 | 37 | 3. Select the field you wish to inspect from the popup browser next to the *Field* label 38 | 39 | 4. Press the search button and wait for *Histogrammer* to complete scanning your project 40 | 41 | #### Expanding results 42 | 43 | To show the detailed results of one of the values shown in the Results section. Click on one of the bars as shown below. 44 | 45 | | | | 46 | | --- | --- | 47 | | ![](https://github.com/Thundernerd/Unity3D-Histogrammer/blob/master/Documentation%7E/histogrammer_2.png) | ![](https://github.com/Thundernerd/Unity3D-Histogrammer/blob/master/Documentation%7E/histogrammer_3.png) | 48 | 49 | #### Pinging objects 50 | 51 | Instead of looking for the objects in your project browser manually. You can click on one of the lines in the detailed results views to highlight the object in the project browser. 52 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5011b8f3c6dd6a54bb8aa5130b02cda1 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "net.tnrd.histogrammer", 3 | "version": "1.0.0", 4 | "displayName": "Histogrammer", 5 | "description": "A that helps you pinpoint redundant data in your project", 6 | "unity": "2019.1", 7 | "keywords": [ 8 | "editor", 9 | "help", 10 | "helper", 11 | "data", 12 | "redundant" 13 | ], 14 | "author": { 15 | "name": "Christiaan Bloemendaal", 16 | "email": "unity3d@tnrd.net", 17 | "url": "https://www.tnrd.net" 18 | }, 19 | "dependencies": {} 20 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 507169b1321379f428be5fe01347ef94 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------