先看效果:

制表符的一个冷门使用场景。制表符的特点就是字符是与字符边界对齐的,四边边距是0,所以在没有行间距、字符间距的条件下是可以连接起来的,完全可以当作连线来使用。
实现原理其实还是比较简单的,在 Hierarchy GUI 对每一个节点显示的时候,获取节点的 Rect,根据节点的深度,计算出对应位置及要显示的制表符,同时根据深度遍历前置节点绘制竖向连线。需要注意每一个层级的 Indent 和是否有 Expander。
完整代码如下:
using UnityEditor;
using UnityEngine;
[InitializeOnLoad]
public static class HierarchyCascadeViewer
{
private static GUIStyle _tabStyle = null;
static int GetTransformDepth(Transform transform)
{
int depth = 0;
while (transform.parent != null)
{
depth++;
transform = transform.parent;
depth += GetTransformDepth(transform);
}
return depth;
}
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
EditorApplication.hierarchyWindowItemOnGUI += (instanceId, rect) =>
{
GameObject go = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
if (go == null)
return;
Rect selectionRect = rect;
if (_tabStyle == null)
{
_tabStyle = new();
_tabStyle.fontSize = 14;
_tabStyle.normal.textColor = Color.gray;
}
Transform transform = go.transform;
int depth = GetTransformDepth(transform);
if (depth > 0)
{
Rect tabRect = new Rect(selectionRect);
tabRect.x -= 27;
int siblingIndex = transform.GetSiblingIndex();
int totalSibling = transform.parent.childCount;
if (siblingIndex + 1 == totalSibling)
{
if (transform.childCount > 0) //避开有子节点的箭头
{
EditorGUI.LabelField(tabRect, "└", _tabStyle);
}
else
{
EditorGUI.LabelField(tabRect, "└─", _tabStyle);
}
}
else
{
if (transform.childCount > 0) //避开有子节点的箭头
{
EditorGUI.LabelField(tabRect, "├", _tabStyle);
}
else
{
EditorGUI.LabelField(tabRect, "├─", _tabStyle);
}
}
Transform parent = transform.parent;
for (int i = 1; i <= depth; i++)
{
if (parent == null || parent.parent == null) break;
tabRect.x -= 14;
if (parent.GetSiblingIndex() + 1 < parent.parent.childCount)
{
EditorGUI.LabelField(tabRect, "│", _tabStyle);
}
parent = parent.parent;
}
}
};
}
}