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;// 在启用编辑器时初始化 SerializedPropertiesvoid OnEnable(){// 获取 SerializedPropertiesdamageProp = serializedObject.FindProperty("damage");armorProp = serializedObject.FindProperty("armor");gunProp = serializedObject.FindProperty("gun");}// 绘制自定义 Inspector GUIpublic 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
感谢您阅读本篇博客!希望这篇内容对您有所帮助。如果您有任何问题或意见,或者想要了解更多关于本主题的信息,欢迎在评论区留言与我交流。我会非常乐意与大家讨论和分享更多有趣的内容。
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript 原型链那些事

在讲原型之前我们先来了解一下函数。 在JS中&#xff0c;函数的本质就是对象&#xff0c;它与其他对象不同的是&#xff0c;创建它的构造函数与创建其他对象的构造函数不一样。那产生函数对象的构造函数是什么呢&#xff1f;是一个叫做Function的特殊函数&#xff0c;通过newFu…

数据库、创建表、修改表

一、数据库 1、登陆数据库 2、创建数据库zoo 3、修改数据库zoo字符集为gbk 4、选择当前数据库为zoo 5、查看创建数据库zoo信息 6、删除数据库zoo 二、创建表 1、创建一个名称为db_system的数据库 2、在该数据库下创建两张表&#xff0c;具体要求如下 员工表 user…

VMware虚拟机配置桥接网络

转载&#xff1a;虚拟机桥接网络配置 一、VMware三种网络连接方式 VMware提供了三种网络连接方式&#xff0c;VMnet0, VMnet1, Vmnet8&#xff0c;分别代表桥接&#xff0c;Host-only及NAT模式。在VMware的编辑-虚拟网络编辑器可看到对应三种连接方式的设置&#xff08;如下图…

【JVM系列】Full GC(完全垃圾回收)的原因及分析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【C++ OpenCV】机器视觉-二值图像和灰度图像的膨胀、腐蚀、开运算、闭运算

原图 结果图 //包含头文件 #include <opencv2/opencv.hpp>//命名空间 using namespace cv; using namespace std;//全局函数声明部分//我的腐蚀运算 Mat Erode(Mat src, Mat Mask, uint32_t x0, uint32_t y0) {uint32_t x 0, y 0;Mat dst(src.rows, src.cols, CV_8U…

MySQL:保护数据库

保护数据库 1. 用户1.1 创建用户1.2 查看用户1.3 删除用户1.4 修改密码 2. 权限2.1 授予权限2.2 查看权限2.3 撤销权限 之前都是介绍本地数据库而你自己就是数据库的唯一用户&#xff0c;所以不必考虑安全问题。但实际业务中数据库大多放在服务器里&#xff0c;你必须妥善处理好…

001,函数指针是一种特殊的指针,它指向的是一个函数地址,可以存储函数并作为参数传递,也可以用于动态绑定和回调函数

函数指针是一种特殊的指针 001&#xff0c;函数指针是一种特殊的指针&#xff0c;它指向的是一个函数地址&#xff0c;可以存储函数并作为参数传递&#xff0c;也可以用于动态绑定和回调函数 文章目录 函数指针是一种特殊的指针前言总结 前言 这是ai回答的标准答案 下面我们…

set的应用(C++)

set的使用 【基本用法】 大家可以敲一下这段代码体会一下set的基本初始化和使用 #include <iostream> #include <set> #include <vector> using namespace std;int main() {set<int> st1; // 空的set// 使用迭代器构造string str("abcdef"…

element-ui输入框如何实现回显的多选样式?

废话不多说直接上效果&#x1f9d0; 效果图 <template><div><el-form:model"params"ref"queryForm"size"small":inline"true"label-width"68px"><el-form-item label"标签" prop"tag&q…

常用SQL语句(基础篇)

前言 查询的sql的结构是 select...from...where...group by...having...order by...limit... 写查询sql的时候需要按照如下顺序写 from&#xff0c;where&#xff08;and&#xff0c;or&#xff0c;&#xff01;&#xff09;&#xff0c;group by&#xff0c;select&#xf…

如何在忘记密码的情况下解锁Android手机?

您的 Android 设备密码有助于保护您的数据并防止您的个人信息被滥用。但是&#xff0c;如果您被锁定在Android设备之外怎么办&#xff1f;我们知道忘记您的 Android 手机密码是多么令人沮丧&#xff0c;因为它会导致您的设备和数据无法访问。在本技术指南中&#xff0c;我们将向…

CSRF靶场通关合集

目录 前言 CSRF漏洞总结 1.PiKachu靶场 1.1CSRF(get) 1.2 CSRF(post)请求 1.3 CSRF Token 2.DVWA靶场 难度低 难度中 难度高 前言 最近系统的将从web渗透到内网渗透的知识点做一个回顾,同时结合一些实战的案例来演示,下面是对刚开始学习时对靶场的一个总结. CSRF漏洞…

快手大模型首次集体亮相 获《焦点访谈》报道关注

7月6日,2024世界人工智能大会暨人工智能全球治理高级别会议在上海闭幕。 据央视《焦点访谈》报道,今年的大会展览持续扩容升级,展览规模、参展企业数、亮点展品数、首发新品数均创历史新高。大会聚焦大模型、算力、机器人、自动驾驶等重点领域,集中展示了一批“人工智能”创新…

流程表单设计器开源优势多 助力实现流程化!

实现流程化办公是很多职场企业的发展目标。应用什么样的软件可以实现这一目的&#xff1f;低代码技术平台、流程表单设计器开源的优势特点多&#xff0c;在推动企业降本增效、流程化办公的过程中作用明显&#xff0c;是理想的软件平台。那么&#xff0c;流程表单设计器开源的优…

开发个人Go-ChatGPT--5 模型管理 (二)

开发个人Go-ChatGPT–5 模型管理 (二) ChatGPT 这是该项目的最终效果&#xff0c;使用ollama的open-webui进行人与机器的对话功能&#xff0c;对话的后端服务则完全对接自己开发的Go项目。 如何实现呢&#xff1f;则通过这篇文章&#xff0c;一一给大家剖析后端的原理及功能…

Apache Seata配置管理原理解析

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata配置管理原理解析 说到Seata中的配置管理&#xff0c;大家可能会想到Seata中适配…

c++ word转换为pdf

在windows系统下&#xff0c;使用QAxObject效果是最好的 转60多兆的文件速度还是可以的&#xff0c;不建议使用多线程&#xff0c;因为多线程会多次调用转换函数&#xff0c;导致程序一直运行&#xff0c;只有全部转换完成后&#xff0c;程序才能继续向下运行&#xff0c;但是c…

dependencyManagement的作用、nacos的学习

使用SpringCloudAlibaba注意各组件的版本适配 SpringCloudAlibaba已经包含了适配的各组件&#xff08;nacos、MQ等&#xff09;的版本号&#xff0c;也是一个版本仲裁者&#xff0c;但是可能已经有了父项目Spring-Boot-Starter-Parent这个版本仲裁者&#xff0c;又不能加多个父…

three.js实现3D模型任意方向旋转

因为业务需要&#xff0c;我们在公司官网使用Three.js实现了可鼠标拖动任意旋转的3D旋转模型&#xff0c;可是实现之后发现除了Z轴方向都可以任意旋转&#xff0c;但是Z轴方向最多只能旋转180度。找遍全网也没有找到相关的资料来解释错在了哪一步&#xff0c;最终还是在github仓…

IT之家最新科技热点 | 小米 AI 研究院开创多模态通用模型

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…