【Unity3D编辑器开发】Unity3D中初次尝试使用PropertyDrawer属性

推荐阅读

  • CSDN主页
  • GitHub开源地址
  • Unity3D插件分享
  • 简书地址
  • 我的个人博客

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、前言

前段时间一直比较忙,没有时间更新博客,最近闲下来了,就给自己充充电,学习一下新知识。

最近订上了Unity3D的编辑器开发,感觉打开了新世界的大门,特意将学习的知识进行梳理,然后分享出来。

这次主要分享的内容是编辑器开发的PropertyDrawer属性。

下面,就来了解一下PropertyDrawer属性吧。

二、正文

2-1、简介

PropertyDrawer用于自定义属性绘制器的基类。

使用此基类可以为自己的[System.Serializable]类的每个实例进行GUI,也就是重新绘制。

比如说,自定义类有[System.Serializable]属性,那么就可以使用PropertyDrawer来控制它在Inspector中的样式。

2-2、举个例子

Demo代码:

using System;
using UnityEngine;


public enum IngredientUnit { Spoon,Cup,Bowl,Piece}

[Serializable]
public class Ingredient
{
    public string name;
    public int amount = 1;
    public IngredientUnit unit;
}

public class Recipe : MonoBehaviour
{
    public Ingredient potionResult;
    public Ingredient[] pointIngredients;
}

接着,可以 使用自定义PropertyDrawer来更改Inspector中Ingredient类的每个实例的样式。

可以使用CustomPropertyDrawer 特性将 PropertyDrawer附加到 Serializable类,然后传入绘制器进行渲染。

可以使用 UIElements构建自定义 PropertyDrawer,也可以使用 IMGUI

若要使用 UIElements创建自定义 PropertyDrawer,必须对 PropertyDrawer类重写 PropertyDrawer.CreatePropertyGUI

若要使用 IMGUI创建自定义 PropertyDrawer,必须对 PropertyDrawer类重写 PropertyDrawer.OnGUI

如果在基于 UIElements的检查器或 EditorWindow内使用 PropertyDrawer,则在任何 IMGUI实现上使用回退覆盖 PropertyDrawer.CreatePropertyGUI 时,将使用 UIElements实现。

如果在基于 IMGUI的检查器或 EditorWindow内使用 PropertyDrawer,则将仅显示 IMGUI实现。UIElements无法在 IMGUI内运行。

以下是使用 IMGUI 写入的自定义 PropertyDrawer的示例:

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawerUIE : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        // label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // 控制字段缩进 设置为不缩进
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // 计算矩形范围
        var nameRect = new Rect(position.x, position.y, 30, position.height); 
        var amountRect = new Rect(position.x + 35, position.y, 50, position.height);
        var unitRect = new Rect(position.x + 90, position.y, position.width - 90, position.height);


        // 绘制字段
        EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("name"), GUIContent.none);
        EditorGUI.PropertyField(amountRect, property.FindPropertyRelative("amount"), GUIContent.none);
        EditorGUI.PropertyField(unitRect, property.FindPropertyRelative("unit"), GUIContent.none);
        

        // 控制字段缩进 设置为原来的数值
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

将Recipe脚本添加到对象上查看效果:
在这里插入图片描述
那可能有同学就会问:“嗯,很好,很强大,那有啥用呢?”
在这里插入图片描述
简单来说就是可以渲染 Serializable 类的实例中的样式,让我们可以实现一些快捷的作用,下面就是Unity3D中内置的PropertyDrawers,一起来看一下效果吧。

2-3、内置的PropertyDrawers

using UnityEngine;

public class ExampleClass : MonoBehaviour
{
    [Range(0, 20)]
    public int intValue = 10;

    [Header("名称")]
    public string nameStr;

    [SerializeField]
    private float floatValue = 10f;
}

在这里插入图片描述

如图所示,Range可以限制intValue的取值范围0~20,Header可以给字段做一些描述或备注,SerializeField允许我们讲一个Private私有字段同Public字段一样显示在Inspector检视面板上。

2-4、重写Time特性,实现秒转分钟功能

(一)新建TimeAttribute.cs,编辑代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public sealed class TimeAttribute : PropertyAttribute
{
    /// <summary>
    /// 显示小时
    /// </summary>
    public readonly bool displayHours;
    /// <summary>
    /// 显示毫秒
    /// </summary>
    public readonly bool displayMillseconds;
    /// <summary>
    /// 构造函数 
    /// </summary>
    /// <param name="displayHours">显示小时</param>
    /// <param name="displayMillseconds">显示毫秒</param>
    public TimeAttribute(bool displayHours = false, bool displayMillseconds = false)
    {
        this.displayHours = displayHours;
        this.displayMillseconds = displayMillseconds;
    }
}

(2)有了TimeAttribute后,我们来自定义它如何在Inspector上进行绘制,这个需要新建一个Editor文件夹,在里面新建TimeAttributeDrawer.cs脚本,重写OnGUI方法来实现绘制,GetPropertyHeight用来定义绘制属性的高度:

using UnityEngine;
using UnityEditor;
using System;
using UnityEngine.UIElements;
using static UnityEditor.PlayerSettings;

[CustomPropertyDrawer(typeof(TimeAttribute))]
public class TimeAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        if (property.propertyType == SerializedPropertyType.Float)
        {
            property.floatValue = EditorGUI.FloatField(new Rect(position.x, position.y, position.width * 0.6f, position.height), label, property.floatValue);
            EditorGUI.LabelField(new Rect(position.x + position.width * 0.6f, position.y, position.width * 0.4f, position.height), GetTimeFormat(property.floatValue));
        }
        else
        {
            EditorGUI.HelpBox(new Rect(position.x, position.y, position.width, position.height), "只支持float类型属性", MessageType.Error);
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label);
    }

    private string GetTimeFormat(float secondsTime)
    {
        TimeAttribute ta = attribute as TimeAttribute;
        //显示小时不显示毫秒
        if (ta.displayHours && !ta.displayMillseconds)
        {
            int l = Convert.ToInt32(secondsTime);
            int hours = l / 3600;
            int minutes = l % 3600 / 60;
            int seconds = l % 3600 % 60;
            return string.Format("{0:D2}:{1:D2}:{2:D2}", hours, minutes, seconds);
        }
        //显示毫秒不显示小时
        else if (!ta.displayHours && ta.displayMillseconds)
        {
            int l = Convert.ToInt32(secondsTime * 1000);
            int minutes = l / 60000;
            int seconds = l % 60000 / 1000;
            int millSeconds = l % 60000 % 1000;
            return string.Format("{0:D2}:{1:D2}.{2:D3}", minutes, seconds, millSeconds);
        }
        //既显示小时也显示毫秒
        else if (ta.displayHours && ta.displayMillseconds)
        {
            int l = Convert.ToInt32(secondsTime * 1000);
            int hours = l / 3600000;
            int minutes = l % 3600000 / 60000;
            int seconds = l % 3600000 % 60000 / 1000;
            int millSeconds = l % 3600000 % 60000 % 1000;
            return string.Format("{0:D2}:{1:D2}:{2:D2}.{3:D3}", hours, minutes, seconds, millSeconds);
        }
        //既不显示小时也不显示毫秒
        else
        {
            int l = Convert.ToInt32(secondsTime);
            int minutes = l / 60;
            int seconds = l % 60;
            return string.Format("{0:D2}:{1:D2})", minutes, seconds);
        }
    }
}

(3)随便新建一个类,ExampleClass.cs,来调用Time特性:

using System;
using TreeEditor;
using UnityEngine;

public class ExampleClass : MonoBehaviour
{
    [Time(true)]
    public float time = 123; 
}

(4)将ExampleClass附加到任意对象上,运行代码:

在这里插入图片描述

2-5、自定义绘制可序列化的类或结构体

假设场景,我们需要制作一个人员添加功能,人员有名字、性别、年龄等属性。

那么,比较一般的实现方式如下:

using System;
using UnityEngine;


public enum Gender
{
    Man,
    Woman
}
[Serializable]
public class Person
{
    public string Name;
    public int Age;
    public Gender Sex;
}
public class ExampleClass : MonoBehaviour
{
    public Person[] people;
}

在这里插入图片描述
接下来,就自定义绘制Person类,需要在Editor文件夹内,新建脚本PersonDrawer.cs继承PropertyDrawer类,代码参考如下:

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(Person))]
public class PersonDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);
        //FocusType.Passive 使用Tab键切换时不会被选中,FocusType.Keyboard 使用Tab键切换时会被选中,很显然这里我们不需要label能被选中进行编辑 
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
        //不让indentLevel层级影响到同一行的绘制,因为PropertyDrawer在很多地方都有可能被用到,可能出现嵌套使用
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;
        var nameRect = new Rect(position.x, position.y, 80, position.height);
        var typeRect = new Rect(position.x + 85, position.y, 30, position.height);
        var overviewRect = new Rect(position.x + 120, position.y, position.width - 120, position.height);
        EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("Name"), GUIContent.none);
        EditorGUI.PropertyField(typeRect, property.FindPropertyRelative("Age"), GUIContent.none);
        EditorGUI.PropertyField(overviewRect, property.FindPropertyRelative("Sex"), GUIContent.none);
        EditorGUI.indentLevel = indent;
        EditorGUI.EndProperty();
    }
}

重新编译完成后,查看Inspector面板:
在这里插入图片描述
非常nice!

2-6、根据bool属性,来开启或者关闭某个对象实例

假设这么一种情况,比如勾选了isCollider,那么就应该显示BoxCollider的卡槽,如果没有勾选isCollider,就不需要显示BoxCollider的卡槽。

这种情况少,但是应该是有的,那么就来实现一下吧。

using System;
using UnityEngine;

[System.Serializable]
public class BoxColliderAttribute
{
    [SerializeField] private bool m_IsCollider;
    [SerializeField] private BoxCollider m_BoxCollider;
}

public class ExampleClass : MonoBehaviour
{
    public BoxColliderAttribute box;
}

在这里插入图片描述
接下来,就自定义绘制BoxColliderAttribute类,需要在Editor文件夹内,新建脚本BoxColliderAttributeDrawer.cs继承PropertyDrawer类,代码参考如下:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(BoxColliderAttribute), true)]
public class BoxColliderAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        Rect drawRect = position;
        drawRect.height = EditorGUIUtility.singleLineHeight;
        EditorGUI.LabelField(position, "是否显示");
        SerializedProperty m_Selected = property.FindPropertyRelative("m_IsCollider");
        SerializedProperty m_Panel = property.FindPropertyRelative("m_BoxCollider");
        drawRect.x += 80;
        EditorGUI.PropertyField(drawRect, m_Selected, GUIContent.none);
        if (m_Selected.boolValue)
        {
            drawRect.x += 45;
            drawRect.width = position.width - 125;
            EditorGUI.PropertyField(drawRect, m_Panel, GUIContent.none);
        }

    }

    public override float GetPropertyHeight(SerializedProperty prop, GUIContent label)
    {
        return 1 * EditorGUIUtility.singleLineHeight + 1 * EditorGUIUtility.standardVerticalSpacing;
    }
}

重新编译完成后,查看Inspector面板:
在这里插入图片描述

三、后记

PropertyDrawer还是很强大的,我们可以定义很多非常方便的Attribute去使用。

合理使用的话可以提高工作效率,省去重复工作。

如果觉得本篇文章有用别忘了点个关注,关注不迷路,持续分享更多Unity干货文章。


你的点赞就是对博主的支持,有问题记得留言:

博主主页有联系方式。

博主还有跟多宝藏文章等待你的发掘哦:

专栏方向简介
Unity3D开发小游戏小游戏开发教程分享一些使用Unity3D引擎开发的小游戏,分享一些制作小游戏的教程。
Unity3D从入门到进阶入门从自学Unity中获取灵感,总结从零开始学习Unity的路线,有C#和Unity的知识。
Unity3D之UGUIUGUIUnity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。
Unity3D之读取数据文件读取使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。
Unity3D之数据集合数据集合数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。
Unity3D之VR/AR(虚拟仿真)开发虚拟仿真总结博主工作常见的虚拟仿真需求进行案例讲解。
Unity3D之插件插件主要分享在Unity开发中用到的一些插件使用方法,插件介绍等
Unity3D之日常开发日常记录主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等
Unity3D之日常BUG日常记录记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。

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

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

相关文章

2011年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

tomcat接入skywalking

tomcat接入skywalking 一、说明二、步骤2.1 准备java-agent包2.2 tomcat部署2.2.1 下载2.2.2 tomcat修改catalina.sh文件2.2.3 tomcat修改启动端口2.2.4 启动tomcat 三、验证四、问题排查4.1 tomcat的启动日志 一、说明 服务器中已经运行着skywalking&#xff0c;准备在同一台…

7-WebApis-5

Web APIs - 5 目标&#xff1a; 能够利用JS操作浏览器,具备利用本地存储实现学生就业表的能力 BOM操作综合案例 js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。比如&#xff1a;变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型&#xff…

2023全云在线联合微软AIGC专场沙龙:人工智能与企业创新,促进创造力的数字化转型

6月29日&#xff0c;由全云在线平台和微软联合主办的人工智能与企业创新&#xff1a;促进创造力的数字化转型——2023AIGC微软专场沙龙在广州天河区正佳万豪酒店举行。 关于2023AIGC微软专场沙龙 GPT翻开了AGI新的一页&#xff0c;也翻开了各行各业的新篇章。 2022年11月30日…

使用Jmeter读取和使用Redis数据

目录 前言 缓存 Redis服务和客户端安装 Jmeter使用Redis 前言 在使用 JMeter 进行性能测试时&#xff0c;有时需要读取和使用 Redis 数据。Redis 是一个开源的内存数据存储系统&#xff0c;常用于缓存、消息队列和数据存储等场景。 缓存 Web系统通常使用数据库来存储数据…

性能测试讲解超详细Jmeter

目录 什么是性能 性能测试的目的 功能测试和性能测试 基准测试 负载测试 稳定性测试 压力测试 并发测试 总结 性能测试指标 响应时间 并发数 吞吐量 点击数 错误率 资源使用率 总结 性能测试流程 性能测试需求分析 性能测试计划和方案 ​编辑性能测试用例​编辑 性…

【MySQL】MySQL数据库,RDBMS 术语,使用说明和报错解决的详细讲解

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

CommonJS 和 ES6 module

本文主要自己觉得要记录的点记录下来,不耽误大家时间&#xff0c;会持续更新。 Module对象 Module {id: xxx/demo/1.js, //加载文件的绝对路径path: xxx/demo,// 加载文件所在目录的绝对路径exports: [Function (anonymous)],filename: xxx/demo/1.js,加载文件的绝对路径load…

蓝桥杯专题-试题版-【圆的面积】【字符串对比】【字母图形】【核桃的数量】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

Lesson1-1:OpenCV简介

图像处理 学习目标 了解图像的起源知道数字图像的表示 1 图像的起源 1.1 图像是什么 图像是人类视觉的基础&#xff0c;是自然景物的客观反映&#xff0c;是人类认识世界和人类本身的重要源泉。“图”是物体反射或透射光的分布&#xff0c;“像“是人的视觉系统所接受的图在…

【数据库】MySQL 高级(进阶) SQL 语句

文章目录 前提条件一、常用查询1. SELECT&#xff08;显示查询&#xff09;2. DISTINCT&#xff08;不重复查询&#xff09;3. WHERE&#xff08;有条件查询&#xff09;4. AND/OR&#xff08;且/或&#xff09;5. IN &#xff08;显示已知值的字段&#xff09;6. BETWEEN&…

【探索 Kubernetes|作业管理篇 系列 14】StatefulSet 存储状态

前言 大家好&#xff0c;我是秋意零。 在上一篇中&#xff0c;我们讲解了 StatefulSet 的拓扑状态&#xff1b;我们发现&#xff0c;它的拓扑状态&#xff0c;就是顺序启动/删除、Pod 名称编号命名、将 Pod 名称设为 Hostname 名称、通过 Service 无头服务的 DNS 记录访问。 …

4通道AD采集子卡模块有哪些推荐?

FMC134是一款4通道3.2GSPS&#xff08;2通道6.4GSPS&#xff09;采样率12位AD采集FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4规范&#xff0c;可以作为一个理想的IO模块耦合至FPGA前端&#xff0c;16通道的JESD204B接口通过FMC连接器连接至FPGA的高速串行…

Excel的一些操作:移动列,添加索引

移动列 在 Excel 中移动列的 5 种方法——分步教程 选中某一列&#xff0c;然后鼠标放在边缘&#xff0c;移动到你想移动到的列 添加索引 例如&#xff0c;我想添加的索引列名为“index”&#xff0c;然后选中想要添加序列的行&#xff0c;点击“填充-->录入123序列”

MySQL的体系架构

文章目录 前言MySQL的Server层MySQL的存储引擎1&#xff09;InnoDB 存储引擎2&#xff09;MyISAM 存储引擎3&#xff09;Memory 存储引擎 前言 在学习一种事务之前&#xff0c;我们需要先了解事物的基本组成结构&#xff0c;清楚了事物的基本组成结构之后&#xff0c;我们才能…

水站桶装水订水小程序

水站桶装水订水小程序正式上线&#xff0c;支持多种商品展示形式&#xff0c;会员卡、积分、分销等功能&#xff0c;有需要的老板可以先看演示&#xff01;​​​​​​​​​​​​​​​​​​​​​

nacos批量信息获取-GitNacosConfig

声明&#xff1a;文中涉及到的技术和工具&#xff0c;仅供学习使用&#xff0c;禁止从事任何非法活动&#xff0c;如因此造成的直接或间接损失&#xff0c;均由使用者自行承担责任。 点点关注不迷路&#xff0c;每周不定时持续分享各种干货。 原文链接&#xff1a;众亦信安&a…

基于SpringBoot+vue的旅游管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Playwright 和 Selenium 的区别是什么?

目录 前言 Playwright 和 Selenium 的区别 前言 Playwright和Selenium都是用于自动化Web应用程序测试的工具&#xff0c;它们都提供了编程接口&#xff0c;用于模拟用户操作和执行自动化测试脚本。然而&#xff0c;Playwright和Selenium在一些方面有所不同。 最近有不少同学…

计算机视觉:多通道卷积操作

本文重点 前面我们学习了对灰度图的卷积操作(二维图像),本节课程我们学习RGB 彩色图像的卷积操作(三维立体)也就是说现在我们不仅想检测灰度图像的特征,也想检测 RGB 彩色图像的特征。 彩色图片的表示方法 彩色图片通常使用RGB(Red、Green、Blue)三个颜色通道来表示…