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

news2024/11/13 1:22:34

推荐阅读

  • 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和坑,让后来人可以有些参考。

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

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

相关文章

SSH连接vmware 虚拟机 centos

检查虚拟机设置的网络连接是否为NAT模式 点击左上角“编辑” -> “虚拟网络编辑器” 在虚拟网络编辑器中查看IP地址 &#xff0c;点击NAT模式后&#xff0c;点击“NAT设置 记住自己的网关,下面在服务器中配置需要 进入服务器的 /etc/sysconfig/network-scripts/ 编辑 i…

Java性能权威指南-总结22

Java性能权威指南-总结22 对象序列化追踪对象复制 Java EE网络API小结 对象序列化 追踪对象复制 先介绍一个示例&#xff0c;如何不对对象引用进行序列化&#xff0c;以避免在反序列化时处理对象引用。然而&#xff0c;writeobject()中最有力的优化是不重复输出对象引用。 在…

强大业务体系支撑,亚马逊云科技占据近八成中国企业出海总销量

从人类发展历史上看&#xff0c;无论是刳木为舟&#xff0c;剡木为楫&#xff0c;还是郑和下西洋&#xff0c;亦或是欧洲大航海时代&#xff0c;人类对于出海探索这件事就从未停止。而在如今的时代&#xff0c;相似的故事依旧在上演。过去的十年&#xff0c;是中国互联网最为繁…

一文了解AppSec,以及如何通过Perforce工具保障应用程序防护

应用程序防护&#xff08;AppSec&#xff09;是有效且高效的安全措施&#xff0c;有助于帮助软件应用程序解决日益严重的安全威胁。本篇文章将讨论应用程序防护&#xff08;AppSec&#xff09;的原理、最佳实践以及您应该使用什么AppSec工具。 什么是AppSec&#xff1f; AppS…

近视眼选择什么台灯好一点?高品质护眼台灯推荐

目前我国儿童以及青少年的近视率达到53.7%&#xff0c;且近视发病率不断呈现低龄化趋势&#xff0c;导致近视的主要是因为用眼过度。而电子产品的普及&#xff0c;网课、打游戏、刷短视频等&#xff0c;被许多家长认为是导致近视的主要原因&#xff0c;其往往忽略掉了一个很重要…

springboot社区服务系统-计算机毕设 附源码77279

springboot社区服务系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&a…

Unity游戏开发之游戏动画(模型动画制作及导入)

一. 简单制作3D角色 在stream中下载Fuse软件在Fuse中制作人物&#xff0c;导出模型为OBJ格式&#xff08;注意&#xff1a;这里导出目录必须为英文路径&#xff0c;否则只能导出空的OBJ文件&#xff09;压缩Obj文件为zip格式打开Mixamo网站&#xff1a;https://www.mixamo.com…

刷题日记04《回溯算法》

回溯算法本质 回溯算法的本质是穷举&#xff0c;即对所有可能的情况进行一一穷举&#xff0c;如果求解过程中发现某个情况不符合求解要求&#xff0c;直接进行剪枝即可 回溯算法模板 我们可以将回溯算法理解为以下两种模板 1.递归回溯 result [] def backtrack(路径, 选择列表…

企业电子招投标系统源代码之电子招投标系统建设的重点和未来趋势+spring clould +java+二次开发

计算机与网络技术的不断发展&#xff0c;推动了社会各行业信息化的步伐。时至今日&#xff0c;电子政务、电子商务已经非常普及&#xff0c;云计算、大数据、工业4.0、“互联网”等发展理念也逐步深入人心&#xff0c;如何将传统行业与互联网科技有效结合起来&#xff0c;产生1…

港联证券:新能源汽车再迎助力 科技巨头持续加注机器人领域

上周五&#xff0c;A股再度反弹拉升&#xff0c;两市股指盘中全线走高&#xff0c;沪指突破3200点&#xff0c;深成指涨逾1%&#xff0c;创业板指一度涨超2%。截至收盘&#xff0c;沪指涨0.62%报3200.06点&#xff0c;深成指涨1.02%报11026.59点&#xff0c;创业板指涨1.6%报22…

【梦辛工作室】java实现简易消息队列处理器 可分区 分区顺序消费MxMQ

大家好哇&#xff0c;又是我&#xff0c;梦辛工作室的灵&#xff0c;最近在巩固JUC并发包&#xff0c;突然想到如果自己的应用体量不大&#xff0c;但有需要消息队列来实现应用解耦和削峰来缓解服务器突增压力&#xff0c;比如抢票时&#xff0c;突然有比较用户同时抢票&#x…

C++之GNU C的__attribute__((constructor))优先级使用(一百四十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

C++ 设计模式----“对象性能“模式

“对象性能”模式 面向对象很好地解决了“抽象”的问题&#xff0c;但是必不可免地要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大都可以忽略不计。但是某些情况&#xff0c;面向对象所带来的成本必须谨慎处理。 典型模式 Singleto Flyweighta Singlet…

51单片机笔记

51单片机笔记 一、编程区域 1.1 用户应用程序区&#xff08;AP区&#xff09; 是指用户自己编写的程序区 1.2 ISP监控程序区&#xff1a; ISP区是指芯片出厂时就已经固化在单片机内部的一段程序&#xff0c;STC单片机可以进行ISP串行下载程序&#xff0c;这就是因为芯片在出…

在Blender和Zbrush中创建激光指示器,新手硬表面建模码住!

大家好&#xff0c;今天云渲染小编给大家带来的分享是硬表面建模&#xff0c;CG艺术家Lyubov使用Blender和Zbrush创建激光指示器的幕后花絮。 介绍 我叫 Lyubov&#xff0c;来自俄罗斯圣彼得堡&#xff0c;是一名 3D 建模的初学者。虽然学习还不到一年&#xff0c;但是我对它…

etcd安装

ETCD安装 windows版本 下载 下载地址https://github.com/etcd-io/etcd/releases 安装 其实也不用安装&#xff0c;下载解压后&#xff0c;得到如下 选中etcd.exe&#xff0c;右键→属性→兼容性→以管理员身份运行此程序勾上&#xff0c;当然&#xff0c;每次运行时候右键…

kettle作业循环实现

kettle作业循环实现 使用kettle作业中的JavaScript实现作业循环&#xff0c;这里是固定循环10次 JavaScript2 parent_job.setVariable("max",10); parent_job.setVariable("count",1); true;检验字段的值 JavaScript var current parent_job.getVari…

MySQL事务+存储引擎

文章目录 MySQL事务存储引擎1 事务1.1 事务的概念1.2 事务的ACID特点1.3 导致问题1.4 事务控制语句1.4.1 查看修改隔离级别1.4.2 使用set设置控制事务1.4.3 查看事务自动提交功能 2 存储引擎2.1 存储格式2.2 常用存储引擎2.3 查看表使用的存储引擎2.4 修改存储引擎2.5 InnoDB行…

证券市场基本概念

证券市场基本概念 一、 指数分类1.1 什么是指数1.2 指数分类 二 、交易所及板块2.1 交易所及板块2.2 股票代码规则 三 、指数、ETF、股票的关系3.1 指数和股票的关系3.2 指数和指数ETF的关系3.3 ETF概念 四、 股票行业分类4.1 申万行业分类4.2 股票与申万行业分类的关系 五 、指…

idea - 插件之 codeium(安装篇)

idea - 插件之 codeium 插件官网地址&#xff1a;https://codeium.com/ Idea 版本&#xff1a;2021.3.2 关于插件作用就不多做介绍&#xff0c;接下来开始正文。 由于目前有很多博客文章没有对 Idea 安装进行详细的讲解和遇到问题的处理讲解&#xff0c;所以我经过踩坑后&am…