├── LICENSE.md ├── .gitattributes ├── Assets~ ├── icons.png ├── lines.png ├── lines.png.meta ├── difference.png └── multi_prefab.gif ├── Editor ├── ObjectIcon.cs.meta ├── CoreHierarchyPass.cs.meta ├── PreferencesWindow.cs.meta ├── MultiObjectPrefabSupport.cs.meta ├── com.nomnom.hierarchy-window-extensions.Editor.asmdef.meta ├── ObjectConnector.cs.meta ├── com.nomnom.hierarchy-window-extensions.Editor.asmdef ├── ObjectIcon.cs ├── MultiObjectPrefabSupport.cs ├── CoreHierarchyPass.cs ├── PreferencesWindow.cs └── ObjectConnector.cs ├── Resources ├── Dashed_Line.png ├── Connector_Line.png ├── Connector_End_Line.png ├── Icon_Mat.mat.meta ├── Dashed_Line_Mat.mat.meta ├── Icon_Mat.mat ├── Dashed_Line_Mat.mat ├── Dashed_Line.png.meta ├── Connector_Line.png.meta └── Connector_End_Line.png.meta ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Resources.meta ├── .gitignore ├── package.json ├── .github └── workflows │ └── main.yml └── README.md /LICENSE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Assets~/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Assets~/icons.png -------------------------------------------------------------------------------- /Assets~/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Assets~/lines.png -------------------------------------------------------------------------------- /Assets~/lines.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f875eb3811ac45f99567b4ac8fdb82d5 3 | timeCreated: 1641418906 -------------------------------------------------------------------------------- /Assets~/difference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Assets~/difference.png -------------------------------------------------------------------------------- /Editor/ObjectIcon.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 49517792a0794b28ac0cfb3bfe43da48 3 | timeCreated: 1641401781 -------------------------------------------------------------------------------- /Assets~/multi_prefab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Assets~/multi_prefab.gif -------------------------------------------------------------------------------- /Editor/CoreHierarchyPass.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 00fb729823db4dae873474621cdfc6fa 3 | timeCreated: 1641398670 -------------------------------------------------------------------------------- /Editor/PreferencesWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a7da73d6ad54abe87148744eadcf029 3 | timeCreated: 1641407798 -------------------------------------------------------------------------------- /Resources/Dashed_Line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Resources/Dashed_Line.png -------------------------------------------------------------------------------- /Resources/Connector_Line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Resources/Connector_Line.png -------------------------------------------------------------------------------- /Editor/MultiObjectPrefabSupport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6efedc1e3dd642b5a86fed9e4742be23 3 | timeCreated: 1641420435 -------------------------------------------------------------------------------- /Resources/Connector_End_Line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomnomab/Hierarchy-Window-Extensions/HEAD/Resources/Connector_End_Line.png -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd56807b6d170bc44b7678eb3ec7b3ca 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b3cc64c3cd31354488c70e1cf71a47e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 74f05093160b8774c9be17879f7d0322 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a277ab1a60427f94482c1bfbe96c8fe9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d77e0e783b9830445bd5133c5c9bce34 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/Icon_Mat.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b8277a20fa3c764fb91886665e41cb5 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/com.nomnom.hierarchy-window-extensions.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e9fb233a633c9e4ba476ca4461cbdb0 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Resources/Dashed_Line_Mat.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f32981495e6e1994bba5e966a60e741d 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/ObjectConnector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5ba9e85c09d24c4494290f697c78549 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | *.VC.db 27 | 28 | # Unity3D generated meta files 29 | *.pidb.meta 30 | *.pdb.meta 31 | 32 | # Unity3D Generated File On Crash Reports 33 | sysinfo.txt 34 | 35 | # Builds 36 | *.apk 37 | *.unitypackage 38 | -------------------------------------------------------------------------------- /Editor/com.nomnom.hierarchy-window-extensions.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.nomnom.hierarchy-window-extensions.Editor", 3 | "rootNamespace": "Nomnom.HierarchyWindowExtensions", 4 | "references": [ 5 | "com.nomnom.easier-custom-preferences.Editor" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.nomnom.hierarchy-window-extensions", 3 | "version": "1.1.7", 4 | "displayName": "Hierarchy Window Extensions", 5 | "description": "This aims to improve the usability of the hierarchy window.", 6 | "unity": "2020.3", 7 | "unityRelease": "20f1", 8 | "licensesUrl": "https://choosealicense.com/licenses/mit/", 9 | "dependencies": { 10 | "com.nomnom.easier-custom-preferences": "1.1.2" 11 | }, 12 | "devDependencies": {}, 13 | "samples": [], 14 | "keywords": [ 15 | "nomnom", 16 | "hierarchy", 17 | "window", 18 | "tool" 19 | ], 20 | "author": { 21 | "name": "Andrew Burke", 22 | "email": "", 23 | "url": "" 24 | }, 25 | "type": "library" 26 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | name: Create release 4 | 5 | jobs: 6 | build: 7 | name: Create release 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@master 12 | 13 | - name: Extract version 14 | id: extract_version 15 | uses: Saionaro/extract-package-version@v1.0.6 16 | - name: Print version 17 | run: echo ${{ steps.extract_version.outputs.version }} 18 | - name: Create Release 19 | id: create_release 20 | uses: actions/create-release@v1 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 23 | with: 24 | tag_name: v${{ steps.extract_version.outputs.version }} 25 | release_name: v${{ steps.extract_version.outputs.version }} 26 | draft: false 27 | prerelease: false 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hierarchy Window Extensions 2 | This aims to improve the usability of the hierarchy window. 3 | 4 | ![Difference image](./Assets~/difference.png) 5 | 6 | ## Installation 7 | - OpenUPM 8 | - `openupm add com.nomnom.hierarchy-window-extensions` 9 | - Package Manager 10 | - Add through git url `https://github.com/nomnomab/Hierarchy-Window-Extensions.git` 11 | 12 | ### Package Settings 13 | - Parts of this package can be enabled/disabled via the preferences menu, located in `Edit/Preferences/Hierarchy Window Extensions` 14 | 15 | ## Current Enhancements 16 | #### Custom Lines 17 | ![Lines image](./Assets~/lines.png) 18 | 19 | #### Object Aware Icons 20 | ![Icons image](./Assets~/icons.png) 21 | - The first-most component on an object will take priority 22 | - If the object only has a single component, the transform is shown 23 | - If it has more than a single component, the next component will be shown 24 | - Canvas renderer components are ignored for UI so the elements can be seen more clearly 25 | 26 | #### Multi-Object Prefab Creation 27 | ![Multi prefab creation](./Assets~/multi_prefab.gif) 28 | - Select multiple scene objects, right-click, and select `Prefab/Multi-Prefab` to select a folder for the prefabs to be placed into -------------------------------------------------------------------------------- /Editor/ObjectIcon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEditor.IMGUI.Controls; 4 | using UnityEngine; 5 | 6 | namespace Nomnom.HierarchyWindowExtensions.Editor { 7 | /// 8 | /// Replaces the default "cube" icon on GameObjects with their highest order 9 | /// component. 10 | /// 11 | internal static class ObjectIcon { 12 | private static Dictionary _components; 13 | 14 | [InitializeOnLoadMethod] 15 | private static void OnLoad() { 16 | _components = new Dictionary(); 17 | 18 | EditorApplication.hierarchyChanged += OnHierarchyChanged; 19 | 20 | OnHierarchyChanged(); 21 | } 22 | 23 | private static void OnHierarchyChanged() { 24 | _components.Clear(); 25 | 26 | GameObject[] objects = Object.FindObjectsOfType(); 27 | foreach (GameObject gameObject in objects) { 28 | Component[] components = gameObject.GetComponents(); 29 | Component toUse = components.Length > 1 ? components[1] : components[0]; 30 | 31 | if (toUse is CanvasRenderer && components.Length > 2) { 32 | toUse = components[2]; 33 | } 34 | 35 | Texture newIcon = EditorGUIUtility.ObjectContent(null, toUse.GetType()).image; 36 | 37 | if (newIcon == null) { 38 | // probably script 39 | newIcon = EditorGUIUtility.ObjectContent(toUse, toUse.GetType()).image; 40 | } 41 | 42 | _components.Add(gameObject, newIcon); 43 | } 44 | } 45 | 46 | public static void Draw(TreeViewItem item, Event e, int instanceId, in Rect rect) { 47 | Object obj = EditorUtility.InstanceIDToObject(instanceId); 48 | if (!(obj is GameObject gameObject)) { 49 | return; 50 | } 51 | 52 | // get the first component for now 53 | if (!_components.TryGetValue(gameObject, out Texture texture)) { 54 | return; 55 | } 56 | 57 | if (item == null) { 58 | return; 59 | } 60 | 61 | item.icon = (Texture2D)texture; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Editor/MultiObjectPrefabSupport.cs: -------------------------------------------------------------------------------- 1 | #if NOM_HIERARCHY_PREFAB 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Nomnom.HierarchyWindowExtensions.Editor { 9 | internal static class MultiObjectPrefabSupport { 10 | private static List _objects; 11 | private static string _directory; 12 | 13 | [MenuItem("GameObject/Prefab/Multi-Prefab", false, 0)] 14 | private static void MakePrefab() { 15 | if (_objects == null) { 16 | _objects = new List(); 17 | 18 | GameObject[] tmpObjects = Selection.gameObjects; 19 | 20 | string directory = EditorUtility.OpenFolderPanel("Open Folder", "Assets/", string.Empty); 21 | string assetsPath = Application.dataPath; 22 | 23 | if (assetsPath.Length > directory.Length || string.IsNullOrEmpty(directory)) { 24 | Debug.LogError("You selected an invalid folder"); 25 | } else { 26 | _directory = directory; 27 | } 28 | 29 | // only use objects without parent selectors 30 | for (int i = 0; i < tmpObjects.Length; i++) { 31 | GameObject gameObject = tmpObjects[i]; 32 | Transform currentTransform = gameObject.transform; 33 | bool isValid = true; 34 | 35 | Transform parent = currentTransform.parent; 36 | while (parent) { 37 | if (tmpObjects.Contains(parent.gameObject)) { 38 | isValid = false; 39 | } 40 | 41 | parent = parent.parent; 42 | } 43 | 44 | if (isValid) { 45 | _objects.Add(gameObject); 46 | } 47 | } 48 | } 49 | 50 | GameObject obj = _objects[0]; 51 | 52 | if (!string.IsNullOrEmpty(_directory)) { 53 | PrefabUtility.SaveAsPrefabAssetAndConnect(obj, $"{_directory}/{obj.name}.prefab", InteractionMode.UserAction); 54 | } 55 | 56 | _objects.RemoveAt(0); 57 | 58 | if (_objects.Count == 0) { 59 | _objects = null; 60 | _directory = null; 61 | } 62 | } 63 | 64 | [MenuItem("GameObject/Prefab/Multi-Prefab", true)] 65 | private static bool DoThingValidate() { 66 | return Selection.gameObjects != null && Selection.gameObjects.Length > 1; 67 | } 68 | } 69 | } 70 | #endif -------------------------------------------------------------------------------- /Editor/CoreHierarchyPass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UnityEditor; 4 | using UnityEditor.IMGUI.Controls; 5 | using UnityEngine; 6 | using Object = UnityEngine.Object; 7 | 8 | namespace Nomnom.HierarchyWindowExtensions.Editor { 9 | internal static class CoreHierarchyPass { 10 | private static readonly Type _sceneHierarchyWindowType = Type.GetType( 11 | "UnityEditor.SceneHierarchyWindow, UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); 12 | private static readonly PropertyInfo _lastInteractedHierarchyWindow = _sceneHierarchyWindowType 13 | .GetProperty("lastInteractedHierarchyWindow", BindingFlags.Public | BindingFlags.Static); 14 | 15 | private static object _treeView; 16 | private static MethodInfo _findItem; 17 | private static MethodInfo _initTree; 18 | private static Object _lastSelection; 19 | 20 | [InitializeOnLoadMethod] 21 | private static void OnLoad() { 22 | EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI; 23 | } 24 | 25 | private static void HierarchyWindowItemOnGUI(int instanceId, Rect rect) { 26 | if (_treeView == null) { 27 | AssignTreeView(); 28 | } 29 | 30 | if (PreferencesWindow.UseCustomLines) { 31 | ObjectConnector.Draw(instanceId, rect); 32 | } 33 | 34 | if (PreferencesWindow.UseCustomIcons) { 35 | Event e = Event.current; 36 | TreeViewItem item = GetItem(instanceId); 37 | 38 | ObjectIcon.Draw(item, e, instanceId, rect); 39 | } 40 | } 41 | 42 | public static void ResetTree() { 43 | object lastWindow = _lastInteractedHierarchyWindow.GetValue(null); 44 | object sceneHierarchy = lastWindow 45 | .GetType() 46 | .GetField("m_SceneHierarchy", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(lastWindow); 47 | _initTree.Invoke(sceneHierarchy, null); 48 | 49 | EditorApplication.RepaintHierarchyWindow(); 50 | } 51 | 52 | private static TreeViewItem GetItem(int id) { 53 | return (TreeViewItem)_findItem?.Invoke(_treeView, new object[] { id }); 54 | } 55 | 56 | private static void AssignTreeView() { 57 | object lastWindow = _lastInteractedHierarchyWindow.GetValue(null); 58 | object sceneHierarchy = lastWindow 59 | .GetType() 60 | .GetField("m_SceneHierarchy", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(lastWindow); 61 | _treeView = sceneHierarchy.GetType() 62 | .GetField("m_TreeView", BindingFlags.NonPublic | BindingFlags.Instance) 63 | .GetValue(sceneHierarchy); 64 | _findItem = _treeView.GetType().GetMethod("FindItem", BindingFlags.Public | BindingFlags.Instance); 65 | _initTree = sceneHierarchy.GetType() 66 | .GetMethod("Init", BindingFlags.NonPublic | BindingFlags.Instance); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Resources/Icon_Mat.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: Icon_Mat 11 | m_Shader: {fileID: 10760, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: _ALPHATEST_ON 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _DetailTex: 39 | m_Texture: {fileID: 2800000, guid: dac76d2aa88d3e54fa6d488fe429be9b, type: 3} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _EmissionMap: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MainTex: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _MetallicGlossMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _OcclusionMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | - _ParallaxMap: 59 | m_Texture: {fileID: 0} 60 | m_Scale: {x: 1, y: 1} 61 | m_Offset: {x: 0, y: 0} 62 | m_Floats: 63 | - _BumpScale: 1 64 | - _ColorMask: 15 65 | - _Cutoff: 0.5 66 | - _DetailNormalMapScale: 1 67 | - _DstBlend: 0 68 | - _GlossMapScale: 1 69 | - _Glossiness: 0.5 70 | - _GlossyReflections: 1 71 | - _Metallic: 0 72 | - _Mode: 1 73 | - _OcclusionStrength: 1 74 | - _Parallax: 0.02 75 | - _SmoothnessTextureChannel: 0 76 | - _SpecularHighlights: 1 77 | - _SrcBlend: 1 78 | - _Stencil: 0 79 | - _StencilComp: 8 80 | - _StencilOp: 0 81 | - _StencilReadMask: 255 82 | - _StencilWriteMask: 255 83 | - _Strength: 0.2 84 | - _UVSec: 0 85 | - _UseUIAlphaClip: 0 86 | - _ZWrite: 1 87 | m_Colors: 88 | - _Color: {r: 1, g: 1, b: 1, a: 1} 89 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 90 | m_BuildTextureStacks: [] 91 | -------------------------------------------------------------------------------- /Resources/Dashed_Line_Mat.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: Dashed_Line_Mat 11 | m_Shader: {fileID: 10760, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: _ALPHATEST_ON 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _DetailTex: 39 | m_Texture: {fileID: 2800000, guid: dac76d2aa88d3e54fa6d488fe429be9b, type: 3} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _EmissionMap: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MainTex: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _MetallicGlossMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _OcclusionMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | - _ParallaxMap: 59 | m_Texture: {fileID: 0} 60 | m_Scale: {x: 1, y: 1} 61 | m_Offset: {x: 0, y: 0} 62 | m_Floats: 63 | - _BumpScale: 1 64 | - _ColorMask: 15 65 | - _Cutoff: 0.5 66 | - _DetailNormalMapScale: 1 67 | - _DstBlend: 0 68 | - _GlossMapScale: 1 69 | - _Glossiness: 0.5 70 | - _GlossyReflections: 1 71 | - _Metallic: 0 72 | - _Mode: 1 73 | - _OcclusionStrength: 1 74 | - _Parallax: 0.02 75 | - _SmoothnessTextureChannel: 0 76 | - _SpecularHighlights: 1 77 | - _SrcBlend: 1 78 | - _Stencil: 0 79 | - _StencilComp: 8 80 | - _StencilOp: 0 81 | - _StencilReadMask: 255 82 | - _StencilWriteMask: 255 83 | - _Strength: 0.2 84 | - _UVSec: 0 85 | - _UseUIAlphaClip: 0 86 | - _ZWrite: 1 87 | m_Colors: 88 | - _Color: {r: 1, g: 1, b: 1, a: 0.5019608} 89 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 90 | m_BuildTextureStacks: [] 91 | -------------------------------------------------------------------------------- /Resources/Dashed_Line.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dac76d2aa88d3e54fa6d488fe429be9b 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 0 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 1 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 0 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 0 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 128 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | - serializedVersion: 3 79 | buildTarget: Standalone 80 | maxTextureSize: 128 81 | resizeAlgorithm: 0 82 | textureFormat: -1 83 | textureCompression: 1 84 | compressionQuality: 50 85 | crunchedCompression: 0 86 | allowsAlphaSplitting: 0 87 | overridden: 0 88 | androidETC2FallbackOverride: 0 89 | forceMaximumCompressionQuality_BC6H_BC7: 0 90 | - serializedVersion: 3 91 | buildTarget: WebGL 92 | maxTextureSize: 128 93 | resizeAlgorithm: 0 94 | textureFormat: -1 95 | textureCompression: 1 96 | compressionQuality: 50 97 | crunchedCompression: 0 98 | allowsAlphaSplitting: 0 99 | overridden: 0 100 | androidETC2FallbackOverride: 0 101 | forceMaximumCompressionQuality_BC6H_BC7: 0 102 | spriteSheet: 103 | serializedVersion: 2 104 | sprites: [] 105 | outline: [] 106 | physicsShape: [] 107 | bones: [] 108 | spriteID: 109 | internalID: 0 110 | vertices: [] 111 | indices: 112 | edges: [] 113 | weights: [] 114 | secondaryTextures: [] 115 | spritePackingTag: 116 | pSDRemoveMatte: 0 117 | pSDShowRemoveMatteOption: 0 118 | userData: 119 | assetBundleName: 120 | assetBundleVariant: 121 | -------------------------------------------------------------------------------- /Resources/Connector_Line.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4add1f7d2539b204aa4d09272f3b4db9 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 0 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 1 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 0 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 0 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 128 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | - serializedVersion: 3 79 | buildTarget: Standalone 80 | maxTextureSize: 128 81 | resizeAlgorithm: 0 82 | textureFormat: -1 83 | textureCompression: 1 84 | compressionQuality: 50 85 | crunchedCompression: 0 86 | allowsAlphaSplitting: 0 87 | overridden: 0 88 | androidETC2FallbackOverride: 0 89 | forceMaximumCompressionQuality_BC6H_BC7: 0 90 | - serializedVersion: 3 91 | buildTarget: WebGL 92 | maxTextureSize: 128 93 | resizeAlgorithm: 0 94 | textureFormat: -1 95 | textureCompression: 1 96 | compressionQuality: 50 97 | crunchedCompression: 0 98 | allowsAlphaSplitting: 0 99 | overridden: 0 100 | androidETC2FallbackOverride: 0 101 | forceMaximumCompressionQuality_BC6H_BC7: 0 102 | spriteSheet: 103 | serializedVersion: 2 104 | sprites: [] 105 | outline: [] 106 | physicsShape: [] 107 | bones: [] 108 | spriteID: 109 | internalID: 0 110 | vertices: [] 111 | indices: 112 | edges: [] 113 | weights: [] 114 | secondaryTextures: [] 115 | spritePackingTag: 116 | pSDRemoveMatte: 0 117 | pSDShowRemoveMatteOption: 0 118 | userData: 119 | assetBundleName: 120 | assetBundleVariant: 121 | -------------------------------------------------------------------------------- /Resources/Connector_End_Line.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b451fb98c4e99e4cabcdf3e3276093c 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 0 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 1 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 0 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 0 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 128 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | - serializedVersion: 3 79 | buildTarget: Standalone 80 | maxTextureSize: 128 81 | resizeAlgorithm: 0 82 | textureFormat: -1 83 | textureCompression: 1 84 | compressionQuality: 50 85 | crunchedCompression: 0 86 | allowsAlphaSplitting: 0 87 | overridden: 0 88 | androidETC2FallbackOverride: 0 89 | forceMaximumCompressionQuality_BC6H_BC7: 0 90 | - serializedVersion: 3 91 | buildTarget: WebGL 92 | maxTextureSize: 128 93 | resizeAlgorithm: 0 94 | textureFormat: -1 95 | textureCompression: 1 96 | compressionQuality: 50 97 | crunchedCompression: 0 98 | allowsAlphaSplitting: 0 99 | overridden: 0 100 | androidETC2FallbackOverride: 0 101 | forceMaximumCompressionQuality_BC6H_BC7: 0 102 | spriteSheet: 103 | serializedVersion: 2 104 | sprites: [] 105 | outline: [] 106 | physicsShape: [] 107 | bones: [] 108 | spriteID: 109 | internalID: 0 110 | vertices: [] 111 | indices: 112 | edges: [] 113 | weights: [] 114 | secondaryTextures: [] 115 | spritePackingTag: 116 | pSDRemoveMatte: 0 117 | pSDShowRemoveMatteOption: 0 118 | userData: 119 | assetBundleName: 120 | assetBundleVariant: 121 | -------------------------------------------------------------------------------- /Editor/PreferencesWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Nomnom.EasierCustomPreferences.Editor; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace Nomnom.HierarchyWindowExtensions.Editor { 8 | [PreferencesName("Hierarchy Window Extensions", "Nomnom")] 9 | internal static class PreferencesWindow { 10 | public static bool UseCustomLines { get; private set; } 11 | public static bool UseCustomIcons { get; private set; } 12 | 13 | private const string DEF_PREFAB = "NOM_HIERARCHY_PREFAB"; 14 | 15 | private const string KEY_USE_CUSTOM_LINES = "nomnom.hierarchy-window-extensions.use-lines"; 16 | private const string KEY_USE_CUSTOM_ICONS = "nomnom.hierarchy-window-extensions.use-icons"; 17 | private const string KEY_USE_CUSTOM_PREFAB = "nomnom.hierarchy-window-extensions.use-prefab"; 18 | 19 | private static GUIContent _useCustomLinesText = new GUIContent("Use Custom Lines"); 20 | private static GUIContent _useCustomIconsText = new GUIContent("Use Custom Icons"); 21 | private static GUIContent _useMultiPrefabText = new GUIContent("Use Multi-Select Prefab Creation"); 22 | 23 | [InitializeOnLoadMethod] 24 | private static void AssignFromPrefs() { 25 | UseCustomLines = EditorPrefs.GetBool(KEY_USE_CUSTOM_LINES, true); 26 | UseCustomIcons = EditorPrefs.GetBool(KEY_USE_CUSTOM_ICONS, true); 27 | } 28 | 29 | [SettingsProvider] 30 | public static SettingsProvider CreateProvider() => CustomPreferences.GetProvider(typeof(PreferencesWindow)); 31 | 32 | public static Settings OnDeserialize() { 33 | AssignFromPrefs(); 34 | 35 | return new Settings { 36 | UseLines = UseCustomLines, 37 | UseIcons = UseCustomIcons, 38 | UseMultiPrefab = EditorPrefs.GetBool(KEY_USE_CUSTOM_PREFAB, false), 39 | }; 40 | } 41 | 42 | public static void OnSerialize(Settings settings) { 43 | EditorPrefs.SetBool(KEY_USE_CUSTOM_LINES, settings.UseLines); 44 | EditorPrefs.SetBool(KEY_USE_CUSTOM_ICONS, settings.UseIcons); 45 | EditorPrefs.SetBool(KEY_USE_CUSTOM_PREFAB, settings.UseMultiPrefab); 46 | 47 | if (settings.UseIcons != UseCustomIcons && !settings.UseIcons) { 48 | CoreHierarchyPass.ResetTree(); 49 | } 50 | 51 | AssignFromPrefs(); 52 | } 53 | 54 | public static void OnGUI(string searchContext, Settings obj) { 55 | GUI.enabled = !EditorApplication.isCompiling; 56 | 57 | if (!GUI.enabled) { 58 | EditorGUILayout.HelpBox("The Editor is currently recompiling...", MessageType.Info); 59 | } 60 | 61 | EditorGUI.indentLevel++; 62 | EditorGUILayout.HelpBox("Shows custom line connectors across scene objects.", MessageType.Info); 63 | obj.UseLines = EditorGUILayout.ToggleLeft(_useCustomLinesText, obj.UseLines); 64 | EditorGUILayout.HelpBox("Shows icons on scene objects that reflect object state.", MessageType.Info); 65 | obj.UseIcons = EditorGUILayout.ToggleLeft(_useCustomIconsText, obj.UseIcons); 66 | 67 | EditorGUI.BeginChangeCheck(); 68 | EditorGUILayout.HelpBox("Shows a context option for creating multiple prefabs from a multi-selection in \"Prefab/Multi-Prefab\".", MessageType.Info); 69 | obj.UseMultiPrefab = EditorGUILayout.ToggleLeft(_useMultiPrefabText, obj.UseMultiPrefab); 70 | 71 | if (EditorGUI.EndChangeCheck()) { 72 | // update preprocessors 73 | string definesString = 74 | PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); 75 | List allDefines = definesString.Split(';').ToList(); 76 | 77 | bool containsMultiPrefab = allDefines.Contains(DEF_PREFAB); 78 | 79 | if (containsMultiPrefab && !obj.UseMultiPrefab) { 80 | allDefines.Remove(DEF_PREFAB); 81 | } else if (!containsMultiPrefab && obj.UseMultiPrefab) { 82 | allDefines.Add(DEF_PREFAB); 83 | } 84 | 85 | PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, string.Join(";", allDefines)); 86 | AssetDatabase.Refresh(); 87 | 88 | OnSerialize(obj); 89 | } 90 | 91 | EditorGUI.indentLevel--; 92 | } 93 | 94 | internal sealed class Settings { 95 | public bool UseLines; 96 | public bool UseIcons; 97 | public bool UseMultiPrefab; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Editor/ObjectConnector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEditor.Callbacks; 4 | using UnityEngine; 5 | using UnityEngine.SceneManagement; 6 | 7 | namespace Nomnom.HierarchyWindowExtensions.Editor { 8 | /// 9 | /// Adds a custom line type between hierarchy objects 10 | /// 11 | internal static class ObjectConnector { 12 | private static Texture _dashedLineTexture; 13 | private static Texture _connectorLineTexture; 14 | private static Texture _connectorLineEndTexture; 15 | private static Material _textureMaterial; 16 | private static Dictionary _nodes; 17 | private static int _rootChildCount; 18 | 19 | [DidReloadScripts] 20 | private static void ReloadResources() { 21 | _dashedLineTexture = Resources.Load("Dashed_Line"); 22 | _connectorLineTexture = Resources.Load("Connector_Line"); 23 | _connectorLineEndTexture = Resources.Load("Connector_End_Line"); 24 | _textureMaterial = Resources.Load("Dashed_Line_Mat"); 25 | 26 | UpdateNodes(); 27 | } 28 | 29 | [InitializeOnLoadMethod] 30 | private static void OnLoad() { 31 | _nodes = new Dictionary(); 32 | 33 | EditorApplication.hierarchyChanged += OnHierarchyChanged; 34 | 35 | OnHierarchyChanged(); 36 | ReloadResources(); 37 | } 38 | 39 | private static void OnHierarchyChanged() { 40 | UpdateNodes(); 41 | 42 | _rootChildCount = SceneManager.GetActiveScene().GetRootGameObjects().Length; 43 | } 44 | 45 | public static void Draw(int instanceId, in Rect rect) { 46 | // draw dashed line when in a parent, but not the root parent 47 | Object obj = EditorUtility.InstanceIDToObject(instanceId); 48 | if (!(obj is GameObject gameObject)) { 49 | return; 50 | } 51 | 52 | Rect localDashRect = rect; 53 | localDashRect.x -= 16; 54 | localDashRect.width = 16; 55 | localDashRect.height = 16; 56 | 57 | // draw persistent dashed line 58 | Rect initialDashedRect = localDashRect; 59 | initialDashedRect.x = 30; 60 | 61 | if (!_nodes.TryGetValue(gameObject, out HierarchyNode node)) { 62 | return; 63 | } 64 | 65 | Transform transform = node.Transform; 66 | 67 | for (int i = 0; i < node.ParentCount; i++) { 68 | DrawDashedLine(initialDashedRect); 69 | 70 | initialDashedRect.x += 14; 71 | } 72 | 73 | if (transform.childCount > 0) { 74 | return; 75 | } 76 | 77 | bool isEnd = transform.parent && node.Index == transform.parent.childCount - 1 || 78 | !transform.parent && node.Index == _rootChildCount - 1; 79 | DrawConnectorLine(localDashRect, isEnd); 80 | } 81 | 82 | private static void DrawDashedLine(in Rect rect) { 83 | if (!_dashedLineTexture || !_textureMaterial) { 84 | ReloadResources(); 85 | return; 86 | } 87 | 88 | EditorGUI.DrawPreviewTexture(rect, _dashedLineTexture, _textureMaterial); 89 | } 90 | 91 | private static void DrawConnectorLine(in Rect rect, bool isEnd) { 92 | if (!_connectorLineEndTexture || !_connectorLineTexture || !_textureMaterial) { 93 | ReloadResources(); 94 | return; 95 | } 96 | 97 | EditorGUI.DrawPreviewTexture(rect, isEnd ? _connectorLineEndTexture : _connectorLineTexture, _textureMaterial); 98 | } 99 | 100 | private static void UpdateNodes() { 101 | // update tree 102 | _nodes.Clear(); 103 | 104 | GameObject[] objects = Object.FindObjectsOfType(); 105 | foreach (GameObject gameObject in objects) { 106 | Transform transform = gameObject.transform; 107 | 108 | _nodes.Add(gameObject, new HierarchyNode { 109 | Transform = transform, 110 | Index = transform.GetSiblingIndex(), 111 | ParentCount = countParents(transform), 112 | ChildCount = transform.childCount 113 | }); 114 | } 115 | 116 | int countParents(Transform transform) { 117 | int count = 1; 118 | 119 | while (transform.parent) { 120 | transform = transform.parent; 121 | count++; 122 | } 123 | 124 | return count; 125 | } 126 | } 127 | 128 | private class HierarchyNode { 129 | public Transform Transform; 130 | public int Index; 131 | public int ParentCount; 132 | public int ChildCount; 133 | } 134 | } 135 | } --------------------------------------------------------------------------------