Unity编辑器扩展之Hierarchy面板扩展

内容将会持续更新,有错误的地方欢迎指正,谢谢!

Unity编辑器扩展之Hierarchy面板扩展


|-----------------------------------------------------|
| TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 ------ 不断努力,不断进步,不断探索 |

|-----------------------------------------------------------------|
| TechX ------ 心探索、心进取! 助力快速掌握 Hierarchy 面板扩展 为初学者节省宝贵的学习时间,避免困惑! |


文章目录


一、Hierarchy菜单扩展

1、拓展菜单(GameObject)

通过 MenuItem 属性,可以在Hierarchy窗口上下文菜单中将菜单项添加到"GameObject/"菜单。

MenuItem 属性能够将任何静态函数转变为菜单命令。仅静态函数可使用 MenuItem 属性。

之前已经写过相关文章:
https://blog.csdn.net/caiprogram123/article/details/133953622#6GameObject_593

2、GenericMenu自定义菜单扩展

当右键点击 Hierarchy 视图中的 GameObject 时,将创建一个包含 "Option 1" 和 "Option 2" 两个选项的 GenericMenu 菜单,并在点击时触发相应的方法。

之前已经写过相关文章:
https://blog.csdn.net/caiprogram123/article/details/135373693#Hierarchy_172


二、EditorApplication.hierarchyChanged 之自动重命名重复对象名称

EditorApplication.hierarchyChanged 是 Unity 编辑器中的一个事件,用于检测和响应场景层级(Hierarchy)中的变化。

当场景中的对象被添加、删除、重命名,或对象的父子关系发生变化时,Unity 会触发 hierarchyChanged 事件。

这使得开发者能够在这些变化发生时执行自定义逻辑,如自动重命名对象、更新层级视图中的图标或状态、同步层级结构与外部数据等。

csharp 复制代码
using UnityEditor;
using UnityEngine;
using System.Linq;

public class AutoRenameDuplicateObjects
{
    [InitializeOnLoadMethod]
    static void Initialize()
    {
        EditorApplication.hierarchyChanged += OnHierarchyChanged;
    }

    private static void OnHierarchyChanged()
    {
        // 查找场景中的所有对象
        GameObject[] allObjects = Object.FindObjectsOfType<GameObject>();

        // 按照父对象分组,检查每个组中是否有重复名称的对象
        foreach (var parentGroup in allObjects.GroupBy(obj => obj.transform.parent))
        {
        	if(parentGroup.Count() < 2) continue;
        	
            // 对同一层级的对象按照Hierarchy中的顺序排序
			var sortedGroup = parentGroup.OrderBy(obj => obj.transform.GetSiblingIndex()).ToList();

			var duplicates = sortedGroup.GroupBy(obj => obj.name)
                            .Where(group => group.Count() > 1);
            foreach (var duplicateGroup in duplicates)
            {
                int index = 1;
                foreach (GameObject go in duplicateGroup)
                {
                    string newName = $"{go.name}_{index++}";
                    Undo.RecordObject(go, "Auto Rename Duplicate Objects");
                    go.name = newName;
                    EditorUtility.SetDirty(go);
                    Debug.Log($"Renamed {go.name} to {newName}");
                }
            }
        }
    }
}

通过监听 EditorApplication.hierarchyChanged 事件,当层级结构发生变化时,代码会自动检查是否有同一父对象下的子对象名称重复,并按照从上到下的顺序进行重命名。


三、EditorApplication.hierarchyWindowItemOnGUI 之布局扩展

EditorApplication.hierarchyWindowItemOnGUI 是 Unity 编辑器提供的一个事件,用于在 Hierarchy 窗口中绘制自定义的 GUI。

这个事件会在每一帧渲染 Hierarchy 窗口时触发,并为每个可见的层级对象调用一次,允许开发者在 Hierarchy 窗口的每个对象旁边绘制自定义内容,如按钮、图标或文本。

csharp 复制代码
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using System.Reflection;
using System.Linq;

public class HierarchyEditor
{
    private const float MinWindowWidth = 240f; // 设置显示图标和Toggle的最小窗口宽度

    // 在加载时初始化
    [InitializeOnLoadMethod]
    static void HierarchyExtensionIcon()
    {
        var activeStyle = new GUIStyle() { normal = { textColor = Color.green } };
        var inactiveStyle = new GUIStyle() { normal = { textColor = new Color(0, 1, 0, 0.5F) } };

        EditorApplication.hierarchyWindowItemOnGUI += (int instanceID, Rect selectionRect) =>
        {
            GameObject go = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
            if (go == null) return;

            int index = 0;
            //绘制对象激活状态切换按钮
            DrawActiveToggle(go, selectionRect, ref index);
            //绘制静态标记
            DrawStatic(go, selectionRect, ref index);
            //绘制组件ICON
            DrawRectIcon<BoxCollider>(go, selectionRect, ref index);
            //重绘对象名称
            DrawGameObjectName(go, selectionRect, activeStyle, inactiveStyle);
        };
    }

    // 获取 Hierarchy 窗口的宽度
    private static float GetHierarchyWindowWidth()
    {
        PropertyInfo hierarchyInfo = typeof(Editor).Assembly
            .GetType("UnityEditor.SceneHierarchyWindow")
            ?.GetProperty("lastInteractedHierarchyWindow", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

        EditorWindow hierarchyWindow = (EditorWindow)hierarchyInfo?.GetValue(null);
        return hierarchyWindow?.position.width ?? 0;
    }

    /// <summary>
    /// 获取Rect
    /// </summary>
    private static Rect GetRect(Rect selectionRect, int index)
    {
        Rect rect = new Rect(selectionRect);
        if (GetHierarchyWindowWidth() >= MinWindowWidth)
        {
            rect.x += rect.width - (18 * index);
        }
        else
        {
            rect.x += rect.width + (18 * index);

        }
        rect.width = 18;
        return rect;
    }
}

1、绘制ActiveToggle

为每个对象绘制一个激活状态的 Toggle 按钮,允许用户直接在 Hierarchy 窗口中切换对象的激活状态。

csharp 复制代码
/// <summary>
/// 绘制激活状态的Toggle按钮
/// </summary>
private static void DrawActiveToggle(GameObject go, Rect selectionRect, ref int index)
{
    index++;
    Rect rect = GetRect(selectionRect, index);
    bool currentActiveState = go.activeSelf;

    // 检查鼠标是否在当前对象的 ActiveToggle 区域内
    if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
    {
        // 获取选中的所有对象
        GameObject[] selectedObjects = Selection.gameObjects;

        // 如果未选中对象,则只更改当前对象的状态
        if (!selectedObjects.Contains(go))
        {
            Selection.activeGameObject = go;
            selectedObjects = new GameObject[] { go };
        }

        bool newActiveState = !currentActiveState;
        Undo.RecordObjects(selectedObjects, "Toggle Active State");
        foreach (GameObject selectedObject in selectedObjects)
        {
            selectedObject.SetActive(newActiveState);
        }
        EditorSceneManager.MarkAllScenesDirty();
        Event.current.Use(); // 使用事件,避免被传递
    }
    GUI.Toggle(rect, currentActiveState, string.Empty);
}

如果用户改变了对象的激活状态,系统会将所有选中的对象的激活状态同步改变,并标记场景为脏数据(MarkAllScenesDirty),以确保更改会被保存。

2、绘制Static静态标记

如果当前对象是静态的,则在其旁边绘制一个 "S" 标记,以表示该对象被标记为静态

csharp 复制代码
/// <summary>
/// 绘制静态标记
/// </summary>
private static void DrawStatic(GameObject go, Rect selectionRect, ref int index)
{
    if (go.isStatic)
    {
        index++;
        Rect rect = GetRect(selectionRect, index);
        GUI.Label(rect, "S");
    }
}

使用 GUI.Label 方法在计算出的位置上绘制 "S" 标记,以标识静态对象。

3、绘制组件的Icon

如果对象具有特定的组件(例如 BoxCollider),则在该对象的 Hierarchy 列表项旁边绘制一个图标。

csharp 复制代码
/// <summary>
/// 绘制组件的Icon
/// </summary>
private static void DrawRectIcon<T>(GameObject go, Rect selectionRect, ref int index) where T : Component
{
    if (go.GetComponent<T>() != null)
    {
        index++;
        Rect rect = GetRect(selectionRect, index);
        DrawIcon<T>(rect);
    }
}

/// <summary>
/// 绘制Unity原生Icon
/// </summary>
private static void DrawIcon<T>(Rect rect)
{
    var icon = EditorGUIUtility.ObjectContent(null, typeof(T)).image;
    GUI.Label(rect, icon);
}

该方法检查当前对象是否具有 BoxCollider 组件。

如果对象包含该组件,调用 DrawIcon 方法在计算好的矩形区域中绘制组件的图标。

4、重绘对象名称

根据对象的激活状态,以不同的颜色在 Hierarchy 窗口中显示对象的名称。

csharp 复制代码
/// <summary>
/// 绘制对象名称
/// </summary>
private static void DrawGameObjectName(GameObject go, Rect selectionRect, GUIStyle activeStyle, GUIStyle inactiveStyle)
{
    selectionRect.x += 18;
    GUIStyle style = go.activeSelf ? activeStyle : inactiveStyle;
    if (PrefabUtility.IsPartOfAnyPrefab(go)) return;
    GUI.Label(selectionRect, go.name, style);
}

activeStyle 和 inActiveStyle 是用于绘制对象名称的 GUIStyle,分别用于激活状态和非激活状态的对象。

PrefabUtility.IsPartOfAnyPrefab(go) 检查对象是否属于某个预制体(Prefab)。如果对象是预制体的一部分,不执行此绘制,以避免重复显示预制体的名称。


|-----------------------------------------------|
| TechX ------ 心探索、心进取! 每一次跌倒都是一次成长 每一次努力都是一次进步 |


END 感谢您阅读本篇博客!希望这篇内容对您有所帮助。如果您有任何问题或意见,或者想要了解更多关于本主题的信息,欢迎在评论区留言与我交流。我会非常乐意与大家讨论和分享更多有趣的内容。
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!

相关推荐
nnsix11 小时前
Unity PicoVR开发 实时预览Unity场景 在Pico设备中(串流)
unity·游戏引擎
一只一只17 小时前
Unity之UGUI Button按钮组件详细使用教程
unity·游戏引擎·ugui·button·ugui button
智源研究院官方账号19 小时前
众智FlagOS 1.6发布,以统一架构推动AI硬件、软件技术生态创新发展
数据库·人工智能·算法·架构·编辑器·硬件工程·开源软件
WarPigs20 小时前
Unity阴影
unity·游戏引擎
一只一只21 小时前
Unity之Invoke
unity·游戏引擎·invoke
咬人喵喵1 天前
SVG 答题类互动模板汇总(共 16 种/来自 E2 编辑器)
编辑器·svg·e2 编辑器
tealcwu1 天前
【Unity踩坑】Simulate Touch Input From Mouse or Pen 导致检测不到鼠标点击和滚轮
unity·计算机外设·游戏引擎
漫步星河1 天前
unityEditor Note 编辑器笔记本
编辑器
ThreePointsHeat1 天前
Unity WebGL打包后启动方法,部署本地服务器
unity·游戏引擎·webgl
咬人喵喵1 天前
16 类春节核心 SVG 交互方案拆解(E2 编辑器实战)
前端·css·编辑器·交互·svg