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

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

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

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

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

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


文章目录

  • 一、Inspector面板头部扩展
  • 二、原生Component扩展
    • 2.1、直接继承原生组件的编辑器脚本
    • 2.2、使用反射获取编辑器脚本的扩展方式
  • 三、自定义组件编辑器扩展
    • 3.1、使用 UIElements 构建自定义 UI
      • 3.1.1、创建 MyPlayer 类
      • 3.1.2、创建自定义编辑器脚本MyPlayerEditor
      • 3.1.3、定义 UXML 文件
      • 3.1.4、定义 USS 文件
    • 3.2、使用EditorGUILayout直接绘制
    • 3.3、使用 SerializedObject 和 SerializedProperty绘制


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/779472.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java--Super

1.super调用父类的构造方法&#xff0c;必须在构造方法的第一个 2.super必须只能出现在子类的方法或者构造方法中 3.super和this不能同时调用构造该方法 和this差别 1.代表的对象不同 this&#xff08;&#xff09;&#xff1a;代指本身调用者这个对象 super&#xff08;&a…

Docker-文件分层与数据卷挂载详解(附案例)

文章目录 文件分层数据卷挂载的含义数据卷挂载实践数据卷挂载案例数据卷挂载方式数据卷常用命令容器间数据共享 更多相关内容可查看 文件分层 例&#xff1a;拉取mysql5.7的镜像&#xff0c;在继续拉取mysql5.8的镜像&#xff0c;会出现一部分文件已存在的现象 这种分层技术 是…

同花顺问财选股,使用自然语言的形式调接口选股

http形式的接口调用问财接口&#xff1a;https://stockapi.com.cn/v1/base/xuangu?strategy创业板&#xff0c;竞价涨幅大于1&#xff0c;竞价量比大于1 代码中调用该接口调试数据。

cmake编译源码教程(一)

1、介绍 本次博客介绍使用cmake编译平面点云分割的源代码,其对室内点云以及TLS点云中平面结构进行分割,分割效果如下: 2、编译过程 2.1 源代码下载 首先,下载源代码,如下所示,在该文件夹下新建一个build文件夹,用于后续生成sln工程。 同时,由于该库依赖open…

什么是 DDoS 攻击及如何防护DDOS攻击

自进入互联网时代&#xff0c;网络安全问题就一直困扰着用户&#xff0c;尤其是DDOS攻击&#xff0c;一直威胁着用户的业务安全。而高防IP被广泛用于增强网络防护能力。今天我们就来了解下关于DDOS攻击&#xff0c;以及可以防护DDOS攻击的高防IP该如何正确选择使用。 一、什么是…

springboot云南特色民宿预约系统-计算机毕业设计源码81574

目 录 第 1 章 引 言 1.1 选题背景 1.2 选题目的 1.3 论文结构安排 第 2 章 系统的需求分析 2.1 系统可行性分析 2.1.1 技术方面可行性分析 2.1.2 经济方面可行性分析 2.1.3 法律方面可行性分析 2.1.4 操作方面可行性分析 2.2 系统功能需求分析 2.3 系统性需求分析…

文件读写操作之c语言、c++、windows、MFC、Qt

目录 一、前言 二、c语言文件读写 1.写文件 2.读文件 三、c文件读写 1.写文件 2.读文件 四、windows api文件读写 1.写文件 2.读文件 五、MFC文件读写 1.写文件 2.读文件 六、Qt文件读写 1.写文件 2.读文件 七、总结 一、前言 我们在学习过程中&#xff0c…

idea使用技巧---超实用的mybatisX插件

一、使用原因 传统创建mybatis项目之后&#xff0c;在mapper接口和xml映射文件之间手动切换非常麻烦&#xff1a;不仅需要记住文件的所在位置&#xff0c;而且每次在mapper当中添加一个新的接口&#xff0c;都需要单独手动点开xml再编写sql&#xff1b; eg&#xff1a;在item…

前端面试题22(js中sort常见的用法)

JavaScript 的 sort() 方法是数组的一个非常强大的功能&#xff0c;用于对数组的元素进行排序。这个方法直接修改原数组&#xff0c;并返回排序后的数组。sort() 的默认行为是将数组元素转换为字符串&#xff0c;然后按照字符串的 Unicode 字典顺序进行排序。这意味着如果你试图…

优化路由,优化请求url

1、使用父子关系调整下使其更加整洁 2、比如说我修改了下url,那所有的页面都要更改 优化&#xff1a;把这个url抽出来&#xff0c;新建一个Api文件夹用于存放所有接口的url&#xff0c;在业务里只需要关注业务就可以 使用时 导包 发请求 如果想要更改路径&#xff0c;在这里…

docker-compose Install gitlab 17.1.1

gitlab 前言 GitLab 是一个非常流行的开源 DevOps 平台,用于软件开发项目的整个生命周期管理。它提供了从版本控制、持续集成/持续部署(CI/CD)、项目规划到监控和安全的一系列工具。 前提要求 Linux安装 docker docker-compose 参考Windows 10 ,11 2022 docker docker-c…

Zookeeper分布式锁原理说明【简单易理解】

Zookeeper 非公平锁/公平锁/共享锁 。 1.zookeeper分布式锁加锁原理 如上实现方式在并发问题比较严重的情况下&#xff0c;性能会下降的比较厉害&#xff0c;主要原因是&#xff0c;所有的连接都在对同一个节点进行监听&#xff0c;当服务器检测到删除事件时&#xff0c;要通知…

【Kafka】Kafka生产者开启幂等性后报错:Cluster authorization failed.

文章目录 背景解决服务端配置ACL增加授权 背景 用户业务需求&#xff0c;需要开启生产者的幂等性&#xff0c;生产者加了配置&#xff1a;enable.idempotence true用户使用的集群开启了ACL认证&#xff1a;SASL_PLAINTEXT/SCRAM-SHA-512用户生产消息时报错&#xff1a;org.ap…

惕佫酰假托品合酶的发现-文献精读28

Discovering a mitochondrion-localized BAHD acyltransferase involved in calystegine biosynthesis and engineering the production of 3β-tigloyloxytropane 发现一个定位于线粒体的BAHD酰基转移酶&#xff0c;参与打碗花精生物合成&#xff0c;并工程化生产惕佫酰假托品…

Git在多人开发中的常见用例

前言 作为从一个 svn 转过来的 git 前端开发&#xff0c;在经历过git的各种便捷好处后&#xff0c;想起当时懵懂使用git的胆颤心惊&#xff1a;总是害怕用错指令&#xff0c;又或者遇到报错就慌的场景&#xff0c;想起当时查资料一看git指令这么多&#xff0c;看的头晕眼花&am…

Java继承和多态

一.继承 继承顾名思义即一方可以把另一方的东西啊传承到自己手里。 例如猫和狗都是动物。动物都有吃饭&#xff0c;喝水等行为&#xff0c;也有年龄&#xff0c;体重的属性。 那么我们在定义猫和狗的时候就没必要去重复写&#xff0c;而是我们可以定义一个动物类&#xff0c…

[Labview] 改写表格内容并储存覆盖Excel

在上一个功能的基础上&#xff0c;新增表格改写保存功能 [Labview] Excel读表 & 输出表单中选中的单元格内容https://blog.csdn.net/Katrina419/article/details/140120584 Excel修改前&#xff1a; 修改保存后&#xff0c;动态改写储存Excel&#xff0c;并重新写入新的表…

这款新的 AI 语音助手击败了 OpenAI,成为 ChatGPT 最受期待的功能之一

OpenAI 推迟了 ChatGPT 令人印象深刻的语音模式&#xff0c;这让许多 AI 聊天机器人的粉丝感到不安&#xff0c;但他们现在可能已经被挖走了。法国人工智能开发商 Kyutai 推出了一款名为 Moshi 的实时语音 AI 助手。 Moshi 旨在通过语音&#xff08;如 Alexa 或 Google Assista…

matlab 花瓣线绘制

matlab 花瓣线绘制 clc,clear,close all; % 创建一个范围内的 x 和 y 值 x linspace(-1.5, 1.5, 100); y linspace(-1.5, 1.5, 100);% 创建一个网格来表示 x 和 y 值的组合 [X, Y] meshgrid(x, y);% 计算方程的左边和右边的值 LHS1 X.^2 Y.^2; RHS1 X.^4 Y.^4;LHS2 X.…

如何在前端网页实现live2d的动态效果

React如何在前端网页实现live2d的动态效果 业务需求&#xff1a; 因为公司需要做机器人相关的业务&#xff0c;主要是聊天形式的内容&#xff0c;所以需要一个虚拟的卡通形象。而且为了更直观的展示用户和机器人对话的状态&#xff0c;该live2d动画的嘴型需要根据播放的内容来…