首页 > 其他分享 >Unity编辑器扩展之Inspector面板扩展

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

时间:2024-07-07 12:29:58浏览次数:20  
标签:自定义 扩展 Unity 编辑器 Editor go Inspector public

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

Unity编辑器扩展之Inspector面板扩展
     
TechX 坚持将创新的科技带给世界!

拥有更好的学习体验 —— 不断努力,不断进步,不断探索
TechX —— 心探索、心进取!

助力快速掌握 Inspector 编辑器扩展

为初学者节省宝贵的学习时间,避免困惑!


文章目录


一、Inspector面板头部扩展


通过订阅 Editor.finishedDefaultHeaderGUI 事件,这个事件在 Inspector 面板的默认 GUI 绘制完成后触发。

我们可以在 Unity Editor 的 Inspector 面板顶部添加自定义按钮,从而实现对选中对象的自定义操作。

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

[InitializeOnLoadAttribute]
static class EditorHeaderGUID
{
    static EditorHeaderGUID()
    {
        Editor.finishedDefaultHeaderGUI += DisplayGUIDIfPersistent;
    }

    static void DisplayGUIDIfPersistent(Editor editor)
    {
        // 确保当前对象是在场景中的对象
        if (editor.target is GameObject go && !EditorUtility.IsPersistent(go))
        {
            GUI.color = Color.green;
            // 在顶部添加一个按钮
            if (GUILayout.Button("Add Cube Components", GUILayout.Height(30)))
            {
                // 为选中的物体添加组件
                AddComponents(go);
            }

            // 在顶部添加一个按钮
            if (GUILayout.Button("Remove Cube Components", GUILayout.Height(30)))
            {
                // 为选中的物体移除组件
                RemoveComponents(go);
            }

            GUI.color = Color.white;
        }
    }

    static void AddComponents(GameObject go)
    {
        if (go != null)
        {
            // 检查并添加 BoxCollider 组件
            if (go.GetComponent<BoxCollider>() == null)
            {
                go.AddComponent<BoxCollider>();
            }

            // 检查并添加 Rigidbody 组件
            if (go.GetComponent<Rigidbody>() == null)
            {
                go.AddComponent<Rigidbody>();
            }

            // 标记场景已更改
            EditorSceneManager.MarkSceneDirty(go.scene);
        }
    }

    static void RemoveComponents(GameObject go)
    {
        if (go != null)
        {
            // 删除 BoxCollider 组件
            BoxCollider boxCollider = go.GetComponent<BoxCollider>();
            if (boxCollider != null)
            {
                Object.DestroyImmediate(boxCollider);
            }

            // 删除 Rigidbody 组件
            Rigidbody rigidbody = go.GetComponent<Rigidbody>();
            if (rigidbody != null)
            {
                Object.DestroyImmediate(rigidbody);
            }

            // 标记场景已更改
            EditorSceneManager.MarkSceneDirty(go.scene);
        }
    }
}

在本示例中,我们实现了添加和删除BoxCollider 和Rigidbody组件的功能。这种方法可以极大地提高我们在 Unity 编辑器中的工作效率。

在这里插入图片描述



二、原生Component扩展


在 Unity 开发过程中,原生组件(如 Transform、Camera、Text、BoxCollider等组件)是 Unity 自带的基本组件。

2.1、直接继承原生组件的编辑器脚本


适用于可以直接访问和继承原生组件的编辑器脚本,比如 TextEditor等。

[CustomEditor(typeof(Text), true)]
    public class TextExtension : TextEditor
    {
        private Text Target { get { return (Text)target; } }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            GUILayout.Space(10);

            GUI.color = new Color(0, 1, 0, 0.5f);
            GUILayout.Space(10);
            if (GUILayout.Button("Set Text", GUILayout.Height(30)))
            {
                Target.fontSize = 20;
                Target.alignment = TextAnchor.MiddleCenter;
                Target.color = Color.white;
            }
            GUI.color = Color.white;
        }
    }

TextEditor外部能够访问,可以直接继承,通过base.OnInspectorGUI();可以绘制父类的GUI,可以保持原先布局不变。

在这里插入图片描述

2.2、使用反射获取编辑器脚本的扩展方式


某些原生组件的编辑器脚本并不直接暴露给开发者,需要通过反射方式获取才能进行扩展,例如某些内部或私有的编辑器脚本。

实现方法:通过 Assembly、Type 和反射来获取和操作目标组件的编辑器脚本

[CustomEditor(typeof(Transform), true)]
    public class ComponentExtension : Editor
    {
        private Editor m_Editor;
        private Transform Target { get { return (Transform)target; } }

        private void OnEnable()
        {
            Type type = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.TransformInspector", false);
            m_Editor = Editor.CreateEditor(target, type);
        }

        public override void OnInspectorGUI()
        {
            if (m_Editor == null) return;
            m_Editor.OnInspectorGUI();

            GUILayout.Space(10);

            GUI.color = new Color(0, 1, 0, 0.5f);
            GUILayout.Space(10);
            if (GUILayout.Button("Reset Position", GUILayout.Height(30)))
            {
                Target.position = new Vector3(0, 0, 0);
                Target.rotation = Quaternion.identity;
                Target.localScale = new Vector3(1, 1, 1);
            }
            GUI.color = Color.white;
        }
    }

Transform的编辑器脚本外界并不能直接访问,这里通过反射Assembly.GetAssembly(typeof(Editor)).GetType(“UnityEditor.TransformInspector”, false);来获取到Transform的编辑器类型TransformInspector

同时通过调用m_Editor.OnInspectorGUI();来绘制原编辑器的GUI,保证原先布局不变。

在这里插入图片描述



三、自定义组件编辑器扩展


3.1、使用 UIElements 构建自定义 UI


在 Unity 中,使用 UIElements 可以更加直观和灵活地设计和实现自定义的 Inspector 面板。以下是一个完整的示例,展示如何使用 UIElements 和 UXML 文件来定义自定义 Inspector 布局,并将其绑定到一个 MyPlayer 对象。

3.1.1、创建 MyPlayer 类


using UnityEngine;

public class MyPlayer : MonoBehaviour
{
    public int damage = 50;
    public int armor = 30;
    public GameObject gun;
}

3.1.2、创建自定义编辑器脚本MyPlayerEditor


首先,我们创建一个自定义编辑器脚本 MyPlayerEditor,继承自 Editor 类,并覆盖 CreateInspectorGUI 方法来加载和设置 UXML 文件和样式表。

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

[CustomEditor(typeof(MyPlayer))]
public class MyPlayerEditor : Editor
{
    const string resourceFilename = "custom-editor-uie";

    public override VisualElement CreateInspectorGUI()
    {
        // 创建一个新的 VisualElement 作为自定义 Inspector 的根元素
        VisualElement customInspector = new VisualElement();

        // 加载 UXML 资源文件
        var visualTree = Resources.Load<VisualTreeAsset>(resourceFilename);
        if (visualTree != null)
        {
            // 克隆 UXML 定义的层级视图到自定义 Inspector 中
            visualTree.CloneTree(customInspector);

            // 加载并添加样式表
            var styleSheet = Resources.Load<StyleSheet>($"{resourceFilename}-style");
            if (styleSheet != null)
            {
                customInspector.styleSheets.Add(styleSheet);
            }
        }
        else
        {
            Debug.LogError($"Could not find UXML resource: {resourceFilename}");
        }

        return customInspector;
    }
}

3.1.3、定义 UXML 文件


创建一个 UXML 文件来定义自定义 Inspector 面板的布局。将该文件保存为 custom-editor-uie.uxml,并将其放置在 Resources 文件夹中。

<UXML xmlns="UnityEngine.UIElements" xmlns:e="UnityEditor.UIElements">
    <VisualElement class="player-property">
        <VisualElement class="slider-row">
            <Label class="player-property-label" text="Damage"/>
            <VisualElement class="input-container">
                <SliderInt class="player-slider" name="damage-slider" high-value="100" direction="Horizontal" binding-path="damage"/>
                <e:IntegerField class="player-int-field" binding-path="damage"/>
            </VisualElement>
        </VisualElement>
        <e:ProgressBar class="player-property-progress-bar" name="damage-progress" binding-path="damage" title="Damage"/>
    </VisualElement>

    <VisualElement class="player-property">
        <VisualElement class="slider-row">
            <Label class="player-property-label" text="Armor"/>
            <VisualElement class="input-container">
                <SliderInt class="player-slider" name="armor-slider" high-value="100" direction="Horizontal" binding-path="armor"/>
                <e:IntegerField class="player-int-field" binding-path="armor"/>
            </VisualElement>
        </VisualElement>
        <e:ProgressBar class="player-property-progress-bar" name="armor-progress" binding-path="armor" title="Armor"/>
    </VisualElement>

    <e:PropertyField class="gun-field" binding-path="gun" label="Gun Object"/>
</UXML>

3.1.4、定义 USS 文件


创建一个 USS 文件来定义 Inspector 面板的样式。将该文件保存为 custom-editor-uie-style.uss,并将其放置在 Resources 文件夹中。

.slider-row {
    flex-direction: row;
    justify-content: space-between;
    margin-top: 4px;
}
.input-container {
    flex-direction: row;
    flex-grow: .6;
    margin-right: 4px;
}
.player-property {
    margin-bottom: 4px;
}
.player-property-label {
    flex: 1;
    margin-left: 16px;
}
.player-slider {
    flex: 3;
    margin-right: 4px;
}
.player-property-progress-bar {
    margin-left: 16px;
    margin-right: 4px;
}
.player-int-field {
    min-width: 48px;
}
.gun-field {
    justify-content: space-between;
    margin-left: 16px;
    margin-right: 4px;
    margin-top: 6px;
    flex-grow: .6;
}

在这里插入图片描述

3.2、使用EditorGUILayout直接绘制


在 Unity 中,自定义 Inspector 面板时,可以直接通过编辑器修改脚本变量。虽然这种方式不支持多对象编辑、撤销和 Prefab 覆盖,但它实现起来更加简单直接,适用于一些简单的自定义编辑器需求。

using UnityEditor;
using UnityEngine;

// 自定义编辑器,直接修改脚本变量,不处理多对象编辑、撤销和 Prefab 覆盖
[CustomEditor(typeof(MyPlayer))]
public class MyPlayerEditorAlternative : Editor
{
    public override void OnInspectorGUI()
    {
        MyPlayerAlternative mp = (MyPlayerAlternative)target;

        mp.damage = EditorGUILayout.IntSlider("Damage", mp.damage, 0, 100);
        ProgressBar(mp.damage / 100.0f, "Damage");

        mp.armor = EditorGUILayout.IntSlider("Armor", mp.armor, 0, 100);
        ProgressBar(mp.armor / 100.0f, "Armor");

        bool allowSceneObjects = !EditorUtility.IsPersistent(target);
        mp.gun = (GameObject)EditorGUILayout.ObjectField("Gun Object", mp.gun, typeof(GameObject), allowSceneObjects);
    }

    // 自定义 GUILayout 进度条
    void ProgressBar(float value, string label)
    {
        // 获取进度条的矩形区域,使用与文本字段相同的边距
        Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
        EditorGUI.ProgressBar(rect, value, label);
        EditorGUILayout.Space();
    }
}

3.3、使用 SerializedObject 和 SerializedProperty绘制


使用 SerializedObject 和 SerializedProperty 提供了一种更结构化的方法,带来一些显著的优势:

  • 自动管理数据更改:
    无需手动调用 EditorUtility.SetDirty,因为 ApplyModifiedProperties 会自动处理数据更改。

  • 撤销/重做支持:
    自动支持撤销和重做功能,无需额外代码。

  • 多对象编辑:
    可以轻松实现多个对象的编辑,并自动处理它们的属性同步。

  • 数据一致性:
    确保数据的一致性和正确性,因为 SerializedObject 和 SerializedProperty 直接与 Unity 的序列化系统交互。

using UnityEditor;
using UnityEngine;

// 自定义编辑器,适用于 MyPlayer 脚本
[CustomEditor(typeof(MyPlayer))]
[CanEditMultipleObjects]
public class MyPlayerEditor : Editor
{
    SerializedProperty damageProp;
    SerializedProperty armorProp;
    SerializedProperty gunProp;

    // 在启用编辑器时初始化 SerializedProperties
    void OnEnable()
    {
        // 获取 SerializedProperties
        damageProp = serializedObject.FindProperty("damage");
        armorProp = serializedObject.FindProperty("armor");
        gunProp = serializedObject.FindProperty("gun");
    }

    // 绘制自定义 Inspector GUI
    public override void OnInspectorGUI()
    {
        // 更新 serializedObject,在 OnInspectorGUI 开头调用
        serializedObject.Update();

        // 显示自定义 GUI 控件
        EditorGUILayout.IntSlider(damageProp, 0, 100, new GUIContent("Damage"));
		ProgressBar(damageProp.intValue / 100.0f, "Damage");
       
        EditorGUILayout.IntSlider(armorProp, 0, 100, new GUIContent("Armor"));
		ProgressBar(armorProp.intValue / 100.0f, "Armor");
      
        EditorGUILayout.PropertyField(gunProp, new GUIContent("Gun Object"));

        // 在 OnInspectorGUI 末尾应用属性更改
        serializedObject.ApplyModifiedProperties();
    }

    // 自定义 GUILayout 进度条
    void ProgressBar(float value, string label)
    {
        // 获取进度条的矩形区域,使用与文本字段相同的边距
        Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
        EditorGUI.ProgressBar(rect, value, label);
        EditorGUILayout.Space();
    }
}



TechX —— 心探索、心进取!

每一次跌倒都是一次成长

每一次努力都是一次进步


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

标签:自定义,扩展,Unity,编辑器,Editor,go,Inspector,public
From: https://blog.csdn.net/caiprogram123/article/details/140216693

相关文章

  • 准大一萌新学习C/C++的第二天:扩展:Fitten Code的学习使用和快捷键的的学习了解
    1.fittencode:一款功能丰富的AI助手(在扩展中下载)1.1.智能补全代码:在输入一串代码后会跳出自动补全的代码,tab键可以接受补全.如果只想接受单个单词ctrl+→可只接受单个补全1.2.AI解释代码:ctrl+alt+c或者点击此键当用户选中代码段再进行对话时,FittenCode 会自动引用用户......
  • LINQ扩展方法
    余生只想牵你的手从天光乍破走到暮雪白头。--zhu常用LINQ控制方法大部分是IEnumerable的扩展方法(数组,List,Dictionary,set)(1)WherestaticvoidMain(string[]args){List<Employee>list=newList<Employee>();list.Add(newEmployee{Id=1,Name="jerry",Age=26......
  • Unity Shader技巧:实现带投影机效果,有效避免边缘拉伸问题
    这个是原始的projector投影组件,边缘会有拉伸经过修改shader后边缘就没有拉伸了(实现代码在文章最后)这个着色器通过检查每个像素的UV坐标是否在定义的边界内,来确定是否应用黑色边框。如果UV坐标处于边缘区域,那么像素颜色会被强制设为黑色,从而在投影图像周围形成一个黑色......
  • 八叉树-Unity
    八叉树八叉树简介八叉树(Octree)是一种在三维空间中进行数据组织和存储的树型数据结构。它的工作原理是将一个大的三维空间递归地分割成八个相等的小空间,每个小空间又可以继续分割成八个更小的空间,以此类推,直到达到某个预定的深度或者满足特定的终止条件(例如,空间内元素数量少于一......
  • 【Unity几种数据存储之间的区别】PlayerPrefs、Json、XML、二进制、SQLite数据存储之
    ......
  • Unity海面效果——4、法线贴图和高光
    Unity引擎制作海面效果  大家好,我是阿赵。  继续做海面效果,上次做完了漫反射颜色和水波动画,这次来做法线和高光效果。一、高光的计算  之前介绍过高光的光照模型做法,比较常用的是Blinn-Phong  所以我这里也稍微连线实现了一下  为了能看得更清楚......
  • 微信SDK与Unity的Addressables发生引用冲突的解决办法
    当我使用Unity的Addressables和微信的minigame-SDK时,会发生一个CS0433的报错,如下图所示: 关于CS0433错误,微软的官方文档中是这么描述的: 因此,根据报错信息,我揣测是Unity的Compat与mscorlib发生了重复,所以将mscorlib.dll文件全部删除了,但是问题没有得到解决,后面在一个大佬的帮......
  • 禅道二次开发——禅道zentaoPHP框架扩展机制——对控制层扩展
    对现有模块的control层的扩展有两种,一种是覆盖现有的方法,一种是增加新的方法。下面我们来看下如何进行扩展。......
  • 【CMD 延迟扩展】FOR 语句读不到值的问题
    延迟扩展将导致批处理文件中的变量在执行时而不是在解析时扩展(变量赋值),此选项通过SETLOCALEnableDelayedExpansion命令打开。延迟扩展意味着用它的值替换它的变量。默认情况下,在执行每行之前,扩展只会发生一次,启用延迟扩展,可以使每次执行该行时都会进行扩展,或者说,对于FOR......
  • unity canvas显示相机照射画面的方法
    1. 使用 Image 组件显示处理后的图像如果你的图像数据已经是一个 Texture2D 或 Sprite,你可以将它直接显示在Canvas上的 Image 组件中:创建 Sprite:将你的 Texture2D 数据转换为 Sprite,以便可以在 Image 组件中使用。publicSpriteCreateSpriteFromTexture(......