2024-02-25 Unity 编辑器开发之编辑器拓展7 —— Inspector 窗口拓展

news2025/3/1 10:48:52

文章目录

  • 1 SerializedObject 和 SerializedProperty
  • 2 自定义显示步骤
  • 3 数组、List 自定义显示
    • 3.1 基础方式
    • 3.2 自定义方式
  • 4 自定义属性自定义显示
    • 4.1 基础方式
    • 4.2 自定义方式
  • 5 字典自定义显示
    • 5.1 SerizlizeField
    • 5.2 ISerializationCallbackReceiver
    • 5.3 代码示例

1 SerializedObject 和 SerializedProperty

​ 在 Unity 中,可以完全自定义某一个脚本在 Inspector 窗口的相关显示。

​ SerializedObject 和 SerializedProperty 主要用于在 Unity 编辑器中操作和修改序列化对象的属性,通常在自定义编辑器中使用,以创建更灵活、可定制的属性面板。

​ 只需记住简单的规则:

  • SerializedObject:代表脚本对象。

    参考文档:https://docs.unity.cn/cn/2022.1/ScriptReference/SerializedObject.html。

  • SerializedProperty:代表脚本对象中的属性。

    参考文档:https://docs.unity.cn/cn/2022.1/ScriptReference/SerializedProperty.html。

2 自定义显示步骤

  1. 单独为某一个脚本实现一个自定义脚本,并且脚本需要继承 Editor。

    一般该脚本命名为:自定义脚本名 + Editor。

image-20240225145626645
  1. 在该脚本前加上特性。
    命名空间:UnityEditor
    特性名:CustomEditor(想要自定义脚本类名的 Type)
using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    ...
}
  1. 声明对应 SerializedProperty 序列化属性对象,主要通过它和自定义脚本中的成员进行关联。

    可以利用继承 Editor 后的成员 serializedObject 中的 FindProperty("成员变量名") 方法关联对应成员。

    一般在 OnEnable 函数中初始化。当选中对象后并在 Inspector 窗口进行显示时,OnEnable 函数会被执行;同理,选择其他对象使 Inspector 窗口取消显示时,OnDisable 函数会被执行。

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty atk;
    private SerializedProperty def;
    private SerializedProperty obj;

    private void OnEnable() {
        // 关联序列化属性
        atk = serializedObject.FindProperty("atk");
        def = serializedObject.FindProperty("def");
        obj = serializedObject.FindProperty("obj");
    }
}
  1. 重写 OnInspectorGUI 函数。

    该函数控制 Inspector 窗口中显示的内容,只需在其中重写内容便可自定义窗口。

    注意:其中的逻辑需要包裹在这两句代码之间:

    serializedObject.Update(); // 更新序列化对象的表示形式 ... serializedObject.ApplyModifiedProperties(); // 应用属性修改

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty atk;
    private SerializedProperty def;
    private SerializedProperty obj;
    
    private bool foldOut;

    private void OnEnable() {
        // 关联序列化属性
        atk = serializedObject.FindProperty("atk");
        def = serializedObject.FindProperty("def");
        obj = serializedObject.FindProperty("obj");
    }
    
    // 该函数控制了 Inspector 窗口中显示的内容
    // 只需要在其中重写内容便可以自定义窗口
    public override void OnInspectorGUI() {
        // base.OnInspectorGUI(); 不要调用父类的方法,而是去自定义
        
        serializedObject.Update(); // 更新序列化对象的表示形式
        
        // 自定义Inspector窗口的内容
        foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "基础属性");
        if (foldOut) {
            if (GUILayout.Button("测试自定义 Inspector 窗口")) {
                Debug.Log(target.name); // 获取 Lesson22 脚本对象
            }
            EditorGUILayout.IntSlider(atk, 0, 100, "攻击力");
            def.floatValue = EditorGUILayout.FloatField("防御力", def.floatValue);
            EditorGUILayout.ObjectField(obj, new GUIContent("敌对对象"));
        }
        EditorGUILayout.EndFoldoutHeaderGroup();
        
        serializedObject.ApplyModifiedProperties(); // 应用属性修改
    }
}
image-20240225150537536

3 数组、List 自定义显示

3.1 基础方式

EditorGUILayout.PropertyField(SerializedProperty对象, 标题);

​ 该 API 会按照属性类型默认处理控件绘制的逻辑。

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty strs;
    private SerializedProperty ints;
    private SerializedProperty gameObjects;
    private SerializedProperty listObjs;

    private void OnEnable() {
        // 默认得到的数组和 List 容量为空
        strs = serializedObject.FindProperty("strs");
        ints = serializedObject.FindProperty("ints");
        gameObjects = serializedObject.FindProperty("gameObjects");
        listObjs = serializedObject.FindProperty("listObjs");
    }
    
    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        EditorGUILayout.PropertyField(strs, new GUIContent("字符串数组"));
        EditorGUILayout.PropertyField(ints, new GUIContent("整形数组"));
        EditorGUILayout.PropertyField(gameObjects, new GUIContent("游戏对象数组"));
        EditorGUILayout.PropertyField(listObjs, new GUIContent("游戏对象List"));

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225152856784

3.2 自定义方式

​ 利用 SerializedProperty 中数组相关的 API 来完成自定义。

API说明
arraySize获取数组或 List 容量。
InsertArrayElementAtIndex(索引)为数组在指定索引插入默认元素(容量会变化)。
DeleteArrayElementAtIndex(索引)为数组在指定索引删除元素(容量会变化)。
GetArrayElementAtIndex(索引)获取数组中指定索引位置的 SerializedProperty 对象。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty strs;
    private SerializedProperty ints;
    private SerializedProperty gameObjects;
    private SerializedProperty listObjs;

    private int count;

    private void OnEnable() {
        // 默认得到的数组和 List 容量为空
        strs = serializedObject.FindProperty("strs");
        ints = serializedObject.FindProperty("ints");
        gameObjects = serializedObject.FindProperty("gameObjects");
        listObjs = serializedObject.FindProperty("listObjs");

        // 初始化当前容量,否则每次开始都是 0
        count = listObjs.arraySize;
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();

        // 容量设置
        count = EditorGUILayout.IntField("List容量", count);
        
        // 移除尾部内容,从后往前移除
        for (int i = listObjs.arraySize - 1; i >= count; i--)
            listObjs.DeleteArrayElementAtIndex(i);
        
        // 根据容量绘制需要设置的每一个索引位置的对象
        for (int i = 0; i < count; i++) {
            // 如果数组或 List 容量不够,通过插入的形式扩容
            if (listObjs.arraySize <= i)
                listObjs.InsertArrayElementAtIndex(i);
        
            SerializedProperty indexPro = listObjs.GetArrayElementAtIndex(i);
            EditorGUILayout.ObjectField(indexPro, new GUIContent($"索引{i}"));
        }

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225153514603

4 自定义属性自定义显示

4.1 基础方式

EditorGUILayout.PropertyField(SerializedProperty对象, 标题);

​ 需要为自定义类添加 Serializable 特性。

using System;
using UnityEngine;

[Serializable]
public class MyCustomPro
{
    public int   i;
    public float f;
}

public class Lesson22 : MonoBehaviour
{
    public MyCustomPro myCustom;
}
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty myCustom;

    private void OnEnable() {
        myCustom = serializedObject.FindProperty("myCustom");
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        EditorGUILayout.PropertyField(myCustom, new GUIContent("我的自定义属性"));

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225154307517

4.2 自定义方式

​ 使用如下方法寻找自定义属性的成员:

  1. SerializedProperty.FindPropertyRelative(属性)
  2. SerializedObject.FindProperty(属性.子属性)
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty myCustomI;
    private SerializedProperty myCustomF;

    private void OnEnable() {
        // myCustomI = myCustom.FindPropertyRelative("i");
        // myCustomF = myCustom.FindPropertyRelative("f");

        myCustomI = serializedObject.FindProperty("myCustom.i");
        myCustomF = serializedObject.FindProperty("myCustom.f");
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        myCustomI.intValue = EditorGUILayout.IntField("自定义属性中的I", myCustomI.intValue);
        myCustomF.floatValue = EditorGUILayout.FloatField("自定义属性中的F", myCustomF.floatValue);

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225154447107

5 字典自定义显示

5.1 SerizlizeField

​ SerizlizeField 特性让私有字段可以被序列化(能够在 Unity 的 Inspector 窗口被看到)。

5.2 ISerializationCallbackReceiver

​ 该接口是 Unity 用于序列化和反序列化时执行自定义逻辑的接口,实现该接口的类能够在对象被序列化到磁盘或从磁盘反序列化时执行一些额外代码。

​ 接口中函数:

  • OnBeforeSerialize: 在对象被序列化之前调用。
  • OnAfterDeserialize: 在对象从磁盘反序列化后调用。

​ 由于需要用两个 List 存储 Dictionary 的键值对,所以需要在

  • OnBeforeSerialize 序列化之前:将 Dictionary 里的数据存入 List 中进行序列化。
  • OnAfterDeserialize 反序列化之后:将 List 中反序列化出来的数据存储到 Dictionary 中。

5.3 代码示例

using System.Collections.Generic;
using UnityEngine;

public class Lesson22 : MonoBehaviour, ISerializationCallbackReceiver
{
    public Dictionary<int, string> myDic = new Dictionary<int, string>() { { 1, "123" }, { 2, "234" } };

    [SerializeField]
    private List<int> keys = new List<int>();

    [SerializeField]
    private List<string> values = new List<string>();

    public void OnAfterDeserialize() {
        myDic.Clear();
        for (int i = 0; i < keys.Count; i++) {
            if (!myDic.ContainsKey(keys[i]))
                myDic.Add(keys[i], values[i]);
            else
                Debug.LogWarning("字典Dictionary容器中不允许有相同的键");
        }
    }

    public void OnBeforeSerialize() {
        keys.Clear();
        values.Clear();
        foreach (var item in myDic) {
            keys.Add(item.Key);
            values.Add(item.Value);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty keys;
    private SerializedProperty values;

    private int dicCount;

    private void OnEnable() {
        keys = serializedObject.FindProperty("keys");
        values = serializedObject.FindProperty("values");

        dicCount = keys.arraySize;
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();

        dicCount = EditorGUILayout.IntField("字典容量", dicCount);
        // 容量变少时,把多的删了
        for (int i = keys.arraySize - 1; i >= dicCount; i--) {
            keys.DeleteArrayElementAtIndex(i);
            values.DeleteArrayElementAtIndex(i);
        }

        for (int i = 0; i < dicCount; i++) {
            // 如果容量不够,扩容
            if (keys.arraySize <= i) {
                keys.InsertArrayElementAtIndex(i);
                values.InsertArrayElementAtIndex(i);
            }
            // 自定义键值对的修改
            SerializedProperty indexKey   = keys.GetArrayElementAtIndex(i);
            SerializedProperty indexValue = values.GetArrayElementAtIndex(i);
            EditorGUILayout.BeginHorizontal();
            indexKey.intValue = EditorGUILayout.IntField("字典的键", indexKey.intValue);
            indexValue.stringValue = EditorGUILayout.TextField("字典的值", indexValue.stringValue);
            EditorGUILayout.EndHorizontal();
        }

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225180102531

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

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

相关文章

【Activiti7系列】Activi7简介和基于Spring Boot整合Activiti7(流程设计器)

本文将介绍Activiti7基础概念及基于Spring Boot整合Activiti7(流程设计器)的具体步骤。 作者&#xff1a;后端小肥肠 1. 前言 在企业级应用中&#xff0c;业务流程的管理和执行是至关重要的一环。Activiti7是一个强大的开源工作流引擎&#xff0c;它提供了灵活的流程定义、任务…

linux---安使用nginx

目录 一、编译安装Nginx 1、关闭防火墙&#xff0c;将安装nginx所需要软件包传到/opt目录下 ​编辑2、安装依赖包 3、创建运行用户、组 4、编译安装nginx 5、创建软链接后直接nginx启动 ​编辑 6、创建nginx自启动文件 ​编辑6.1 重新加载配置、设置开机自启并开启服务…

Kafka之Producer源码

Producer源码解读 在 Kafka 中, 我们把产生消息的一方称为 Producer 即 生产者, 它是 Kafka 的核心组件之一, 也是消息的来源所在。它的主要功能是将客户端的请求打包封装发送到 kafka 集群的某个 Topic 的某个分区上。那么这些生产者产生的消息是怎么传到 Kafka 服务端的呢&a…

unity发布webGL压缩方式的gzip,使用nginx作为web服务器时的配置文件

unity发布webGL压缩方式的gzip&#xff0c;使用nginx作为web服务器时的配置文件 Unity版本是&#xff1a;2021.3 nginx的版本是&#xff1a;nginx-1.25.4 Unity发布webgl时的测试 设置压缩方式是gzip nginx配置文件 worker_processes 1;events {worker_connections 102…

SpringBoot实现热插拔AOP

热插拔AOP执行核心逻辑 Advice&#xff1a;“通知”&#xff0c;表示 Aspect 在特定的 Join point 采取的操作。包括 “around”, “before” and “after 等 Advice&#xff0c;大体上分为了三类&#xff1a;BeforeAdvice、MethodInterceptor、AfterAdviceAdvisor&#xff1a…

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX FLASH是常用的一种非易失存储单元&#xff0c;W25QXX系列Flash有不同容量的型号&#xff0c;如W25Q64的容量为64Mbit&#xff0c;也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库Quad SPI总线实现FATS文件操作W25Q各型号…

智能SQL生成:后端技术与LLM的完美结合

文章目录 引言一、什么是大模型二、为什么选择LLM三、开发技术说明四、系统架构说明五、编码实战1. Maven2. 讯飞大模型配置类3. LLM相关的封装4. 编写LLM的service5. 编写controller6. 运行测试 六、总结 引言 本篇文章主要是关于实现一个类似Chat2DB的根据自然语言生成SQL的…

SpringMVC 学习(四)之获取请求参数

目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数&#xff08;重点&#xff09; 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…

微信小程序02: 使用微信快速验证组件code获取手机号

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.1 上文小总结1.1.2 上文传送门 2. 微信小程序获取手机号2.1 业务场景(使用与充值)2.2 准备工作2.3 具体代码使用与注释如下2.3.1 代码解释(一)[无需复制]2.3.2 代码解释(二)[无需复制] 2.4 最后一步 获取手机号信息2.4.1 两行代…

电商评价分析:NLP信息抽取技术在用户评论中的应用与挖掘

一、引言 在2019年&#xff0c;电子商务的蓬勃发展不仅推动了消费市场的增长&#xff0c;也带来了海量的用户评价数据。这些数据&#xff0c;作为消费者对商品和服务直接反馈的载体&#xff0c;蕴含着巨大的价值。然而&#xff0c;由于其非结构化的特性&#xff0c;这些文本信息…

YOLOv8改进 | Conv篇 | 全新的SOATA轻量化下采样操作ADown(参数量下降百分之二十,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块,其中YOLOv9针对于这个模块并没有介绍,只是在其项目文件中用到了,我将其整理出来用于我们的YOLOv8的项目,经过实验我发现该卷积模块(作为下采样模块)…

半导体物理基础-笔记(续)

源内容参考&#xff1a;https://www.bilibili.com/video/BV11U4y1k7zn/?spm_id_from333.337.search-card.all.click&vd_source61654d4a6e8d7941436149dd99026962 掺杂半导体的费米能级与温度及杂质浓度的关系图 在温度一定的条件下&#xff0c;施主杂质浓度越高&#xff0…

压力测试工具Jmeter的下载与使用

1、进入官网下载Jmeter https://jmeter.apache.org/ 国内镜像&#xff08;下载的慢的话可以用国内镜像下载&#xff09; https://mirrors.cloud.tencent.com/apache/jmeter/binaries/ 2、跳转到下载页面 3、根据不同系统下载相应版本的Jmeter压缩包&#xff0c;Linux系统下载…

Repeater:创建大量类似项

Repeater 类型用于创建大量类似项。与其它视图类型一样&#xff0c;Repeater有一个model和一个delegate。 首次创建Repeater时&#xff0c;会创建其所有delegate项。若存在大量delegate项&#xff0c;并且并非所有项都必须同时可见&#xff0c;则可能会降低效率。 有2种方式可…

2024年大路灯无广测评推荐:书客、柏曼、霍尼韦尔大路灯哪个品牌更好?

临近开学&#xff0c;护眼大路灯哪个品牌好的话题度在不断提高&#xff01; 有人说大路灯是“智商税”&#xff0c;但也有人说“学生党福音”、“照明神器”&#xff0c;吸引了大量人群的关注。在没用过大路灯之前我也很担心在担心是否是智商税的问题&#xff0c;直到我自己入…

BUU [CISCN2019 华东南赛区]Web4

BUU [CISCN2019 华东南赛区]Web4 题目描述&#xff1a;Click to launch instance. 开题&#xff1a; 点击链接&#xff0c;有点像SSRF 使用local_file://协议读到本地文件&#xff0c;无法使用file://协议读取&#xff0c;有过滤。 local_file://协议&#xff1a; local_file…

stable-diffusion-webui+sadTalker开启GFPGAN as Face enhancer

接上一篇&#xff1a;在autodl搭建stable-diffusion-webuisadTalker-CSDN博客 要开启sadTalker gfpgan as face enhancer&#xff0c; 需要将 1. stable-diffusion-webui/extensions/SadTalker/gfpgan/weights 目录下的文件拷贝到 :~/autodl-tmp/models/GFPGAN/目录下 2.将G…

探索创意的无尽宇宙——Photoshop 2020,你的视觉魔法棒

在数字艺术的广阔天地中&#xff0c;Photoshop 2020无疑是一颗璀璨的明星。这款由Adobe公司精心打造的图像处理软件&#xff0c;自推出以来&#xff0c;便以其强大的功能和卓越的性能&#xff0c;赢得了全球数百万设计师、摄影师和爱好者的青睐。无论是Mac还是Windows系统&…

冯诺依曼体系结构 与 操作系统

一、冯诺依曼体系结构 深入理解冯诺依曼体系结构 计算机的出现就是为了解决实际问题, 所以把问题交给计算机&#xff0c;计算机经过处理&#xff0c;得到一个结果反馈给我们&#xff0c;所以这中间就必然涉及到了输入设备&#xff0c;中央处理器(包括运算器和控制器)和输出设备…

Find My小风扇|苹果Find My技术与小风扇结合,智能防丢,全球定位

电风扇在我们的日常生活中也是经常会使用到的家电产品&#xff0c;尤其是在炎炎的夏日&#xff0c;风扇能给我们吹来清凉的凉风&#xff0c;如今随身携带的小风扇成为人们出门的必备物品&#xff0c;由于体积小方便经常会被人遗忘在某个地方导致丢失。 在智能化加持下&#x…