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.mzph.cn/pingmian/41824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云服务器配置、搭建(针对Spring boot和MySQL项目)

这是一篇极其详细且痛苦的文章&#xff0c;还是在两位后端的大佬手把手教导下、以及我找遍全网所有资料、问了N遍AI、甚至直接申请阿里云工单一对一询问客服一整天、连续清空再上传反复30多次整个项目jar包......总结出来的终极要人命踩坑的问题总结 一、首先购买服务器 其实不…

磁盘分区工具 -- 傲梅分区助手 v10.4.1 技术员版

软件简介 傲梅分区助手是一款功能强大的磁盘分区工具&#xff0c;它专为Windows系统设计&#xff0c;帮助用户更高效地管理他们的硬盘。该软件支持多种分区操作&#xff0c;包括创建、格式化、调整大小、移动、合并和分割分区。此外&#xff0c;它还提供了复制硬盘和分区的功能…

06-6.4.4 拓扑排序

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…

java基础:方法

一、方法 1、Java方法是语句的集合&#xff0c;它们在一起执行一个功能。 方法是解决一类问题的步骤的有序集合方法包含于类或对象中方法在程序中被创建&#xff0c;在其他地方被引用 2、设计方法的原则&#xff1a;方法的本意是功能块&#xff0c;就是实现某个功能的语句块…

如何选择一家适合自己的商城源码?

商城源码的选择取决于多个因素&#xff0c;包括商城的功能需求、稳定性、易用性、可定制性以及价格等。启山智软作为在市场上被广泛认可且表现优异的商城源码提供商&#xff0c;具有以下的特点和优势&#xff1a; 特点①&#xff1a;国内知名的B2B2C开源商城源码系统&#xff…

BufferReader/BufferWriter使用时出现的问题

项目场景&#xff1a; 在一个文件中有一些数据&#xff0c;需要读取出来并替换成其他字符再写回文件中&#xff0c;需要用Buffer流。 问题描述 文件中的数据丢失&#xff0c;并且在读取前就为空&#xff0c;读取不到数据。 问题代码&#xff1a; File f new File("D:\\…

Python排序,你用对了吗?一文教你sorted和sort的正确姿势!

目录 1、sorted基础用法 🍏 1.1 列表排序入门 1.2 自定义排序规则 1.3 排序稳定性和key函数 2、sort内置方法操作 🔍 2.1 直接修改原列表 2.2 sort高级技巧与性能考量 2.3 案例:数据预处理实战 2.4 高级用法:reverse与cmp_to_key 3、应对复杂数据结构 🌐 3.1…

Yolo系列再次更新——清华发布Yolov10端到端实时对象检测模型

前期我们刚介绍过Yolo系列模型,还以为Yolov9刚刚发布,也许今年不会再有什么更新。但是没有想到打脸如此之快,Yolov10端到端实时对象检测模型强势回归发布。Yolov10端到端实时对象检测 YOLOv10 是清华大学研究人员在YOLO软件包的基础上,引入了一种新的实时目标检测方法,解决…

HTTP协议格式

目录 正文&#xff1a; 1.概述 2.主要特点 3.请求协议格式 4.响应协议格式 5.响应状态码 总结&#xff1a; 正文&#xff1a; 1.概述 HTTP 协议是用于传输超文本数据&#xff08;如 HTML&#xff09;的应用层协议&#xff0c;它建立在传输层协议 TCP/IP 之上。当我们在…

视频参考帧和重构帧复用

1、 视频编码中的参考帧和重构帧 从下图的编码框架可以看出&#xff0c;每编码一帧需要先使用当前帧CU(n)减去当前帧的参考帧CU&#xff08;n&#xff09;得到残差。同时&#xff0c;需要将当前帧的重构帧CU*&#xff08;n&#xff09;输出&#xff0c;然后再读取重构帧进行预测…

七、MyBatis-Plus高级用法:最优化持久层开发-个人版

七、MyBatis-Plus高级用法&#xff1a;最优化持久层开发 目录 文章目录 七、MyBatis-Plus高级用法&#xff1a;最优化持久层开发目录 一、MyBatis-Plus快速入门1.1 简介1.2 快速入门回顾复习 二、MyBatis-Plus核心功能2.1 基于Mapper接口CRUDInsert方法Delete方法Update方法Se…

PyQt5中如何实现指示灯点亮和指示灯熄灭功能

一般上位机界面都会涉及指示灯点亮和指示灯熄灭功能&#xff0c;从网上下载该功能的上位机界面&#xff0c;学习如何使用PyQt5搭建具备指示灯点亮和指示灯熄灭效果的界面。 1. 上位机界面的效果展示 使用PyQt5实现以下界面&#xff0c;界面效果如下&#xff0c;界面图片是从网…

基于SpringBoot的招聘信息管理系统的详细设计和实现(源码+lw+部署文档+讲解等,欢迎咨询我!!)

文章目录 目录 文章目录 详细视频展示&#xff1a; 系统具体实现效果&#xff08;看看我的实力&#xff09; 技术栈&#xff08;详细的描述提供给同学思路参考&#xff09; 2.1 Java语言介绍 2.2 B/S架构 2.3 MySQL 数据库介绍 2.4 MySQL环境配置 2.5 SpringBoot框…

C++之static关键字

文章目录 前提正文多重定义extern关键字使用staticstatic 全局变量(在.cpp文件中定义)static变量存放在哪里static变量可不可以放在.h文件中 static 函数static局部变量static 成员变量static 成员函数 总结参考链接 前提 好吧&#xff0c;八股&#xff0c;我又回来了。这次想…

[图解]企业应用架构模式2024新译本讲解23-标识映射2

1 00:00:00,950 --> 00:00:02,890 好&#xff0c;我们往下走 2 00:00:04,140 --> 00:00:04,650 一样的 3 00:00:04,660 --> 00:00:07,170 这前面也见过了&#xff0c;定义一个对象数组 4 00:00:07,870 --> 00:00:12,820 数组的长度就是字段的数量&#xff0c;4个…

一.1.(3)半导体二极管基本电路的分析方法及常见应用电路

1.二极管基本电路的分析方法 先标正负极&#xff0c;再看是否理想二极管 将二极管视为断路&#xff0c;求两端电压 两端电压均大于导通电压&#xff0c;压差大的先导通&#xff08;由于电源不是完全的阶跃&#xff0c;而是有一个电压爬升的过程&#xff09; 2.常见应用电路 1.求…

日期选取限制日期范围antdesign vue

限制选取的日期范围 效果图 <a-date-pickerv-model"dateTime"format"YYYY-MM-DD":disabled-date"disabledDate"valueFormat"YYYY-MM-DD"placeholder"请选择日期"allowClear />methods:{//回放日期选取范围限制&…

网安小贴士(4)哈希函数

一、前言 哈希函数是密码学中的基础工具&#xff0c;哈希函数在密码学中扮演着至关重要的角色&#xff0c;广泛应用于确保数据的安全性和完整性。随着技术的发展&#xff0c;新的哈希算法和应用场景也在不断出现。 二、定义 哈希函数是一种数学函数&#xff0c;它接受一个输…

Intellj idea无法启动

个人电脑上安装的是2024.01版本的intellj idea作为开发工具&#xff0c;引入了javaagent作为工具包 但是在一次invaliad cache操作后&#xff0c;intellj idea就无法启动了&#xff0c;双击无响应。 重装了idea后也无效&#xff08;这个是有原因的&#xff0c;下面会讲&#…

springboot服务启动读取不到application.yml中的nacos.config信息

我的版本&#xff1a; 可以添加bootstrap.yml文件&#xff0c;在里面添加nacos.config的配置信息 也可以添加VM参数 -Dspring.cloud.nacos.discovery.server-addr -Dspring.cloud.nacos.config.server-addr -Dspring.cloud.nacos.config.namespace -Dspring.cloud.nacos.discov…