Unity 编辑器扩展七 Property Attributes

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 编辑器扩展七 Property Attributes相关的知识,希望对你有一定的参考价值。

参考技术A

参考
Unity Editor 基础篇(七):Property Attributes

我们可以对ReadOnly这种标签在面板上的显示做自定义,对比效果如下:

在Scripts文件夹中创建两个C#脚本,分别命名为:“ReadOnlyAttribute.cs”和“TestPropertyAttr.cs”

继承PropertyAttribute这个类,就能用一个自定义的PropertyDrawer类去控制继承PropertyAttribute类的变量在Inspector面板中的显示。在IDE中,按下CTRL,再点击TestPropertyAttr 中的[ReadOnly]标签,会发现跳到了ReadOnlyAttribute.cs中。 可以理解为,我们继承PropertyAttribute这个类,实现了自己定义一个标签。

在Editor文件夹中创建一个名为“ReadOnlyAttributeDrawer.cs”的脚本

MyString的颜色未生效

Unity编辑器扩展

文章目录

继承自 MonoBehaviour 的类的一些扩展

一方面继承自MonoBehaviour的类都会默认被添加到 Component->Scripts 节点下。
另一方面MonoBehaviour类都可以添加到GameObject作为Component,在Inspector界面可以显示该类的一些信息。
所有继承自UnityEngine.Object的类,如GameObject,Component,MonoBehaviour,Texture2D,AnimationClip;所有基本类型,如int,string,float,bool;一些内建类型,如Vector2,Vector3,Quaternion,Color,Rect,Layermask;序列化类型的Array,序列化类型的List;枚举Enum。默认情况下这些类型的public变量是可以在Inspector界面显示的,同时编辑器对其进行序列化。

一些涉及到变量显示和序列化的属性

  • [HideInInspector] 变量依旧可以被序列化,但是在Inspector界面上不可见
  • [NonSerialized] 变量不可被序列化,且在Inspector面板上不可见
  • [SerializeField] 可以把private类型变量变成可序列化的,在Inspector面板可见。(虽然Inspector面板可见,但仍然是private变量)

ContextMenu属性

继承自MonoBehaviour的类的内部,可以为其函数添加ContextMenu属性,在编辑器界面下执行一些操作。

public class TestContextMenu : MonoBehaviour

    [ContextMenu("DoLogTest")]
    void DoLogTest()
    
        Debug.Log("i am function DoLogTest");
    

如上述代码,当把TestContextMenu脚本附加到GameObject上后,在GameObject的Inspector界面中会有TestContextMenu组件的信息,右键该组件(或者左键点击3个.按钮)会看到DoLogTest菜单项,点击后会按照预期打印日志。

ContextMenuItem属性

可以为变量(public)添加右键弹出命令,从而执行相关的操作。
该标识接受两个变量,1个是display name,一个是右键弹出菜单点击后的方法。

public class Test : MonoBehaviour

    [ContextMenuItem("Random Age", "RandomAge")]
    public int Age;
    void RandomAge()
    
        Age = new System.Random(DateTime.Now.Millisecond).Next(1, 100);
    

    [ContextMenuItem("Random Name", "RandomName")]
    public string Name;
    private void RandomName()
    
        string[] names = new string[]  "Jack", "Jim", "Tomas", "Han", "Ann" ;
        Name = names[new System.Random(DateTime.Now.Millisecond).Next(0, 4)];
    

上述代码分别为Age和Name这两个字段添加了右键弹出菜单的功能。

Range属性

[Range(min,max)]或者[RangeAttribute(min,max)]可以对变量的输入范围进行限定,使得Inspector检视面板内的数值输入框变成Slider,且范围为(min,max)。

Header属性

[Header(“”)] Inspector面板中在目标字段顶部展示额外的说明文字

窗口扩展基础:MenuItem,ScriptableWizard,EditorWindow

MenuItem

使用MenuItem标识可以为编辑器添加新的菜单,也可以为Inspector上下文添加菜单。点击后执行一些特定的逻辑,没有额外的操作界面。只有静态方法可以使用该标识,该标识可以把静态方法转换为菜单命令。
另外可以为菜单创建快捷方式(hotkey),你可以使用如下特殊修饰字符:%(Windows系统上的ctrl,OS X系统上的cmd),#(shift),&(alt)。比如快捷方式“shift-alt-g”的写法为[MenuItem(“XX/XX/XXX #&g”)]。如果不需要为hotkey提供修饰符需要用字符_修饰,如快捷方式“g”的写法为[MenuItem(“XX/XX/XXX _g”)]
另外还可以支持一些特殊的字符:LEFT、RIGHT、UP、DOWN、F1…F12、HOME、END、PGUP、PGDN。
示例代码:

using UnityEngine;  
using System.Collections;  
using UnityEditor;  

public class AddChild

    [MenuItem("Custom/Create Child For Selected GameObjects")]
    static void MenuAddChild()
    
        Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable);
        foreach (Transform transform in transforms)
        
            GameObject aChild = new GameObject("_Child");
            aChild.transform.parent = transform;
        
    
    [MenuItem("Custom/Create Child For Selected GameObjects", true)]
    static bool ValidateMenuAddChild()
    
        return Selection.activeGameObject != null;
    

上述示例可以在Unity菜单项创建新的目录菜单:Custom/Create Child For Selected GameObjects,同时提供了Validate函数确保当前若没有物体被选中的话,该菜单项是灰化不可点击的。

需要注意的是,入口函数和Validate函数都必须是静态的,Validate函数名由Validate和入口函数名组合而成,Validate函数的MenuItem标识中的第1个参数和入口函数的一致,第2个参数为true。

排序、分组

MenuItem函数原型为:public MenuItem(string itemName, bool isValidateFunction, int priority);易知通过控制第3个参数便可以设置分组。
第3个int参数,值越小越排在上面,Unity会自动分组,50为单位。如下Option1和Option2会自动被分成不同的组,具体表现就是有个分割线。

[MenuItem("NewMenu/Option1", false, 3)]
private static void NewMenuOption1()



[MenuItem("NewMenu/Option2", false, 51)]
private static void NewMenuOption2()


勾选

下述代码实现Test1和Test2两项,互斥选择,且默认情况下选择Test1

[InitializeOnLoad]
public class TestMenuItem  

    static string menuPath1 = "Custom/Test1";
    static string menuPath2 = "Custom/Test2";
    static TestMenuItem()
    
        //默认选择Test1
        if (Menu.GetChecked(menuPath1) == false && Menu.GetChecked(menuPath2) == false)
        
            Menu.SetChecked(menuPath1, true);
        
    
    [MenuItem("Custom/Test1", false, 1)]
    static void Test1()
    
        if (Menu.GetChecked(menuPath1)==false)
        
            Menu.SetChecked(menuPath1, true);
            Menu.SetChecked(menuPath2, false);
        
    
    [MenuItem("Custom/Test2", false, 2)]
    static void Test2()
    
        if (Menu.GetChecked(menuPath2)==false)
        
            Menu.SetChecked(menuPath1, false);
            Menu.SetChecked(menuPath2, true);
        
    

一些特定目录结构

上述提到通过MenuItem标识可以为Unity创建新的目录,不仅如此还可以为已有的菜单增加内容。

  • MenuItem(“GameObject/”) 新建菜单项出现在GameObject节点下。
  • MenuItem(“Assets/”) 新建菜单项出现在Assets节点下(Project面板下右键弹出的面板内容和Assets节点展开的是一致的)
  • MenuItem(“CONTEXT/组件名/XXX”) 可以为某个具体组件的Inspector上下文菜单增加内容。打开方式为右键组件或者左键组件最右边的“齿轮”按钮,需要注意的是Inspector上下文添加菜单不支持多重层次。
    如下代码为Transform组件扩展其Inspector上下文菜单:
public class AddChild

    [MenuItem("CONTEXT/Transform/Create Child For Selected GameObjects")]
    static void MenuAddChild()
    
        Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable);
        foreach (Transform transform in transforms)
        
            GameObject aChild = new GameObject("_Child");
            aChild.transform.parent = transform;
        
    
    [MenuItem("CONTEXT/Transform/Create Child For Selected GameObjects", true)]
    static bool ValidateMenuAddChild()
    
        return Selection.activeGameObject != null;
    

如下代码为获得Inspector上下文菜单对应的组件:

[MenuItem("CONTEXT/Rigidbody/DoSomething")]
static void DoSomething(OnInspectorGUI command)

    Rigidbody body=command.context;
    body.mass=5;

代码执行菜单项

EditorApplication.ExecuteMenuItem(“XX/XX/XX”)

ScriptableWizard

通过继承ScriptableWizard可以创建编辑器向导,Unity已经为我们封装好了一些变量、方法、消息。如:

  • 变量:errorString(设置向导的错误提示信息)、helpString(设置向导的帮助提示)、isValid(可以控制向导的Create Button和Other Button能否点击)。
  • 静态方法:DisplayWizard
  • 消息:OnWizardCreate(当点击Create按钮的时候触发)、OnWizardOtherButton(当点击Other按钮的时候)、OnWizardUpdate(当向导打开的瞬间或者向导中有输入参数的变化时触发)。

示例:

public class CreateACube : ScriptableWizard

    public float size = 1f;//声明为public可以被向导序列化显示在界面上,且可以改变其值
    [MenuItem("Custom/CreateACube")]
    static void CreateACubeWizard()
            
        ScriptableWizard.DisplayWizard("创建一个Cube", typeof(CreateACube), "确定", "取消");
        //OR
        //ScriptableWizard.DisplayWizard<CreateACube>("创建一个Cube", "确定", "取消"); 
     
    void OnWizardCreate()
    
        GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
        obj.transform.localScale = new Vector3(size, size, size);
    

    void OnWizardOtherButton()
    
        Close();//点击otherButton,需要调用Close()方法,才关闭向导。但是点击createButton,并无需调用Close()方法而自动关闭。
    

    void OnWizardUpdate()
    
        helpString = "输入Cube的Size,Size要大于等于3,创建一个Cube";

        if (size < 3)
        
            errorString = "size要大于等于3";
            isValid = false;
        
        else
        
            errorString = "";
            isValid = true;
        
    

EditorWindow

继承自EditorWindow的类,可以实现更复杂的编辑器窗口功能。且这种窗口是可以自由内嵌到Unity编辑器内,共同组成编辑器的Layout。
通过在OnGUI()函数内调用GUILayout、EditorGUILayout、GUI等类的一些方法来实现复杂的界面。
下面通过EditorWindow来实现上述的编辑器向导,相关代码如下:

public class CreateACube2 : EditorWindow

    float size = 1f;
    string helpString = "输入Cube的Size,Size要大于等于3,创建一个Cube";
    string errorString = "size需要大于等于3";
    bool enableConfromButton = false;
    static Color originColor;
    [MenuItem("Custom/CreateACube2")]
    static void Init()
    
        //获取已经打开的window,如果不存在则new一个
        CreateACube2 window = EditorWindow.GetWindow(typeof(CreateACube2)) as CreateACube2;
        originColor = GUI.color;
    

    void OnGUI()
    
        GUILayout.Label(helpString, EditorStyles.boldLabel);
        size = EditorGUILayout.FloatField("size:", size);

        enableConfromButton = size >= 3 ? true : false;
        if (enableConfromButton)
        
            GUI.enabled = true;
        
        else
        
            GUI.enabled = false;//设置error的时候Button按钮不可点击
            GUI.color=Color.red;//设置errorString的颜色为红色
            GUILayout.Label(errorString);
        
        GUI.color = originColor;
        if (GUILayout.Button("确定"))
        
            DoCreate();
        
    

    void DoCreate()
    
        GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
        obj.transform.localScale = new Vector3(size, size, size);
    
 

除了可以使用OnGUI()回调,还有如下一些函数可以丰富EditorWindow:

  • OnDestroy()当EditorWindow关闭的时候触发。
  • OnFocus()当Window获得焦点触发。
  • OnHierarchyChange()当Hierarchy面板内有改变的时候触发。
  • OnInspectorUpdate()每秒十帧的触发,来判断Inspector界面是否有改变。
  • OnLostFocus()当Window失去焦点触发。
  • Update()每秒100次的触发,在所有可见的Windows。

更多参考:Editor Windows

Property Drawers

//TODO:
–TODO:https://docs.unity3d.com/2021.3/Documentation/Manual/editor-PropertyDrawers.html

Custom Editors

Custom Editors是相对来说更强大的一种编辑器扩展。主要实现对Inspector界面和SceneView界面的扩展。

下面以一个最简单的实例来说明如何对Inpector界面扩展。
我们定义一个Player:

public class Player : MonoBehaviour
    
    public int armor = 75;
    public int damage = 20;
    public GameObject gun;
 

默认情况下,其对应的Inspector界面如下:

要自定义Player组件的Inspector界面,需要新建脚本PlayerEdiotr.cs并继承自Editor。把PlayerEditor.cs放到Editor目录下,代码如下:

[CanEditMultipleObjects]
[CustomEditor(typeof(Player))]
public class PlayerEditor : Editor

    SerializedProperty armorProp;
    SerializedProperty damageProp;
    SerializedProperty gunProp;

    void OnEnable()
    
        armorProp = serializedObject.FindProperty("armor");
        damageProp = serializedObject.FindProperty("damage");
        gunProp = serializedObject.FindProperty("gun");
    

    public override void OnInspectorGUI()
    
        //更新serializedProperty,一般在OnInspector函数的一开始使用。
        serializedObject.Update();

        //依次重写Player.cs脚本中的armor,damage,gun
        EditorGUILayout.IntSlider(armorProp, 0, 100, new GUIContent("Armor"));
        if (!armorProp.hasMultipleDifferentValues)//当该值相同的时候才显示ProgressBar
        
            ProgressBar(armorProp.intValue / 100.0f, "ArmorCount");
        

        EditorGUILayout.IntSlider(damageProp, 0, 100, new GUIContent("Damage"));
        if (!damageProp.hasMultipleDifferentValues)
        
            ProgressBar(damageProp.intValue / 100.0f, "DamagePower");
        

        EditorGUILayout.PropertyField(gunProp, new GUIContent("Gun Object"));

        //使更改生效
        serializedObject.ApplyModifiedProperties();
    

    private void ProgressBar(float value, string str)
    
        Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
        EditorGUI.ProgressBar(rect, value, str);
        EditorGUILayout.Space();
    

重写后界面如下:

上述代码使用了较常用的SerializedProperty和SerializedObject来实现对Inspector界面的重写。

  • [CustomEditor(typeof(Player))] 这个编辑器属性可以把Editor脚本和原始cs脚本建立关系,从而实现重写。

  • [CanEditMultipleObjects] 使用了这个编辑器属性,可以确保当选择的多个物体具有相同组件的时候不会报错“Multi-object editing not supported”,而且可以支持多个物体编辑。

  • OnInspectorGUI() 重写该方法可以实现对默认Inspector界面的重写。该方法也是最常用的一个方法。

还有一种重写Inspector界面的方式

代码如下:

[CanEditMultipleObjects]
[CustomEditor(typeof(Player))]
public class PlayerEditor : Editor

    Player player;
    //or: Player player  get  return target as Player;  
    void OnEnable()
    
        player = target as Player;
    

    public override void OnInspectorGUI()
    
        //base.OnInspectorGUI(); //调用父类方法绘制一次GUI,Player中原本的可序列化数据等会在这里绘制一次。 如果不调用父类方法,则这个Mono的Inspector全权由下面代码绘制。

        player.armor = EditorGUILayout.IntSlider("Armor", player.armor, 0, 100);
        ProgressBar(player.armor / 100f, "ArmorCount");

        player.damage = EditorGUILayout.IntSlider("Damage", player.damage, 0, 100);
        ProgressBar(player.damage / 100f, "DamagePower");

        bool allowSceneObjects = !EditorUtility.IsPersistent(player);
        player.gun = EditorGUILayout.ObjectField("Gun Object", player.gun, typeof(GameObject), allowSceneObjects) as GameObject;

        //数据有改变的话SetDirty
        if (GUIUnity开发:Material xxx doesn‘t have _Stencil property问题记录

Unity3D编辑器扩展——扩展自己的组件

Unity编辑器扩展

unity shader 编辑器扩展类 ShaderGUI

Unity 扩展编辑器

Unity编辑器扩展chapter1