unity 抽屉_Unity 4中的属性抽屉

unity 抽屉_Unity 4中的属性抽屉

unity 抽屉

A new exciting editor extension feature is coming for Unity 4 – Property Drawers!

Unity 4 – Property Drawers即将推出新的令人兴奋的编辑器扩展功能!

What started out as a NinjaCamp project between Rune and I has now been included in Unity. Property Drawers significantly reduce the amount of work you have to do in order to customize how your scripts look in the Inspector. You no longer have to write an entire Custom Editor (although that is still supported as well). Instead, you can just apply appropriate attributes to variables in your scripts to tell the editor how you want those properties to be drawn.

最初由我和Rune之间的NinjaCamp项目开始,现已包含在Unity中。 “属性抽屉”极大地减少了自定义脚本在Inspector中的外观所需要做的工作量。 您不再需要编写整个“自定义编辑器”(尽管仍然受支持)。 相反,您可以将适当的属性应用于脚本中的变量,以告诉编辑器您希望如何绘制这些属性。

This allows you to enforce sensible settings on your properties, create “intelligent” properties that gather data from a data source to show a set of valid options, make your scripts more developer friendly or just have a nice tidy up.

这使您可以在属性上执行合理的设置,创建“智能”属性,这些属性从数据源收集数据以显示一组有效选项,使脚本对开发人员更友好,或者使其整洁。

Let’s take a look at a script before and after applying some PropertyAttributes to spice things up.

让我们看一下在应用一些PropertyAttributes来增添趣味之前和之后的脚本。

unity 抽屉_Unity 4中的属性抽屉

Changing the appearance and behavior of the field controls is as easy as adding attributes to your fields as in the script below.

更改字段控件的外观和行为就像在下面的脚本中向字段添加属性一样容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Example : MonoBehaviour {
    public string playerName = "Unnamed";
    [Multiline]
    public string playerBiography = "Please enter your biography";
    [Popup ("Warrior", "Mage", "Archer", "Ninja")]
    public string @class = "Warrior";
    [Popup ("Human/Local", "Human/Network", "AI/Easy", "AI/Normal", "AI/Hard")]
    public string controller;
    [Range (0, 100)]
    public float health = 100;
    [Regex (@"^(?:\d{1,3}\.){3}\d{1,3}$", "Invalid IP address!\nExample: '127.0.0.1'")]
    public string serverAddress = "192.168.0.1";
    [Compact]
    public Vector3 forward = Vector3.forward;
    [Compact]
    public Vector3 target = new Vector3 (100, 200, 300);
    public ScaledCurve range;
    public ScaledCurve falloff;
    [Angle]
    public float turnRate = (Mathf.PI / 3) * 2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Example : MonoBehaviour {
     public string playerName = "Unnamed" ;
     [ Multiline ]
     public string playerBiography = "Please enter your biography" ;
     [ Popup ( "Warrior" , "Mage" , "Archer" , "Ninja" ) ]
     public string @ class = "Warrior" ;
     [ Popup ( "Human/Local" , "Human/Network" , "AI/Easy" , "AI/Normal" , "AI/Hard" ) ]
     public string controller ;
     [ Range ( 0 , 100 ) ]
     public float health = 100 ;
     [ Regex ( @ "^(?:\d{1,3}\.){3}\d{1,3}$" , "Invalid IP address!\nExample: '127.0.0.1'" ) ]
     public string serverAddress = "192.168.0.1" ;
     [ Compact ]
     public Vector3 forward = Vector3 . forward ;
     [ Compact ]
     public Vector3 target = new Vector3 ( 100 , 200 , 300 ) ;
     public ScaledCurve range ;
     public ScaledCurve falloff ;
     [ Angle ]
     public float turnRate = ( Mathf . PI / 3 ) * 2 ;
}

Here’s how it works. When the Inspector renders the component for your script, it will check for each property if there is a ProperyDrawer defined for the type of the property or if the property has an attribute that inherits from PropertyAttribute. If there is, then an associated PropertyDrawer will handle rendering of that single property. You are able to define both attributes and drawers to customize rendering and data exchange for a given property.

运作方式如下。 当Inspector为脚本呈现组件时,它将检查每个属性,是否为属性的类型定义了ProperyDrawer,或者该属性具有从PropertyAttribute继承的属性。 如果存在,则关联的PropertyDrawer将处理该单个属性的呈现。 您可以定义属性和抽屉,以自定义给定属性的渲染和数据交换。

编写自己的PropertyAttribute和PropertyDrawer (Writing your own PropertyAttribute and PropertyDrawer)

Let’s take a closer look at RegexAttribute and its PropertyDrawer to show how it fits together. First off, we should define an attribute that derives from PropertyAttribute. This attribute can store any kind of data you wish and it is intended to later be used by the PropertyDrawer.

让我们仔细看看RegexAttribute及其PropertyDrawer,以显示它们如何组合在一起。 首先,我们应该定义一个从PropertyAttribute派生的属性。 此属性可以存储您想要的任何类型的数据,并且打算稍后由PropertyDrawer使用。

1
2
3
4
5
6
7
8
public class RegexAttribute : PropertyAttribute {
    public readonly string pattern;
    public readonly string helpMessage;
    public RegexAttribute (string pattern, string helpMessage) {
        this.pattern = pattern;
        this.helpMessage = helpMessage;
    }
}
1
2
3
4
5
6
7
8
public class RegexAttribute : PropertyAttribute {
     public readonly string pattern ;
     public readonly string helpMessage ;
     public RegexAttribute ( string pattern , string helpMessage ) {
         this . pattern = pattern ;
         this . helpMessage = helpMessage ;
     }
}

The new RegexAttribute can be applied to a field in a script as shown below.

可以将新的RegexAttribute应用于脚本中的字段,如下所示。

1
2
3
4
5
6
using UnityEngine;
public class RegexExample : MonoBehaviour {
    [Regex (@"^(?:\d{1,3}\.){3}\d{1,3}$", "Invalid IP address!\nExample: '127.0.0.1'")]
    public string serverAddress = "192.168.0.1";
}
1
2
3
4
5
6
using UnityEngine ;
public class RegexExample : MonoBehaviour {
     [ Regex ( @ "^(?:\d{1,3}\.){3}\d{1,3}$" , "Invalid IP address!\nExample: '127.0.0.1'" ) ]
     public string serverAddress = "192.168.0.1" ;
}

So far we have just created an attribute which holds data. However this alone will not cause anything to render. We need to define a drawer for the attribute, so let’s implement a RegexDrawer. It has to derive from PropertyDrawer and it must have a CustomPropertyDrawer attribute which tells Unity which kinds of attributed fields it can draw controls for. In our case we want it to draw the regex controls for any strings with the RegexAttribute attached to it, so we specify that here.

到目前为止,我们刚刚创建了一个保存数据的属性。 但是,仅此一项就不会导致任何渲染。 我们需要为属性定义一个抽屉,所以让我们实现一个RegexDrawer。 它必须从PropertyDrawer派生,并且必须具有CustomPropertyDrawer属性,该属性告诉Unity可以为其绘制控件的哪些类型的属性字段。 在我们的例子中,我们希望它为附加了RegexAttribute的任何字符串绘制regex控件,因此我们在此处指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using UnityEditor;
using UnityEngine;
using System.Text.RegularExpressions;
[CustomPropertyDrawer (typeof (RegexAttribute))]
public class RegexDrawer : PropertyDrawer {
    // These constants describe the height of the help box and the text field.
    const int helpHeight = 30;
    const int textHeight = 16;
    // Provide easy access to the RegexAttribute for reading information from it.
    RegexAttribute regexAttribute { get { return ((RegexAttribute)attribute); } }
    // Here you must define the height of your property drawer. Called by Unity.
    public override float GetPropertyHeight (SerializedProperty prop,
                                             GUIContent label) {
        if (IsValid (prop))
            return base.GetPropertyHeight (prop, label);
        else
            return base.GetPropertyHeight (prop, label) + helpHeight;
    }
    // Here you can define the GUI for your property drawer. Called by Unity.
    public override void OnGUI (Rect position,
                                SerializedProperty prop,
                                GUIContent label) {
        // Adjust height of the text field
        Rect textFieldPosition = position;
        textFieldPosition.height = textHeight;
        DrawTextField (textFieldPosition, prop, label);
        // Adjust the help box position to appear indented underneath the text field.
        Rect helpPosition = EditorGUI.IndentedRect (position);
        helpPosition.y += textHeight;
        helpPosition.height = helpHeight;
        DrawHelpBox (helpPosition, prop);
    }
    void DrawTextField (Rect position, SerializedProperty prop, GUIContent label) {
        // Draw the text field control GUI.
        EditorGUI.BeginChangeCheck ();
        string value = EditorGUI.TextField (position, label, prop.stringValue);
        if (EditorGUI.EndChangeCheck ())
            prop.stringValue = value;
    }
    void DrawHelpBox (Rect position, SerializedProperty prop) {
        // No need for a help box if the pattern is valid.
        if (IsValid (prop))
            return;
        EditorGUI.HelpBox (position, regexAttribute.helpMessage, MessageType.Error);
    }
    // Test if the propertys string value matches the regex pattern.
    bool IsValid (SerializedProperty prop) {
        return Regex.IsMatch (prop.stringValue, regexAttribute.pattern);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using UnityEditor ;
using UnityEngine ;
using System . Text . RegularExpressions ;
[ CustomPropertyDrawer ( typeof ( RegexAttribute ) ) ]
public class RegexDrawer : PropertyDrawer {
     // These constants describe the height of the help box and the text field.
     const int helpHeight = 30 ;
     const int textHeight = 16 ;
     // Provide easy access to the RegexAttribute for reading information from it.
     RegexAttribute regexAttribute { get { return ( ( RegexAttribute ) attribute ) ; } }
     // Here you must define the height of your property drawer. Called by Unity.
     public override float GetPropertyHeight ( SerializedProperty prop ,
                                             GUIContent label ) {
        if ( IsValid ( prop ) )
             return base . GetPropertyHeight ( prop , label ) ;
         else
             return base . GetPropertyHeight ( prop , label ) + helpHeight ;
     }
    // Here you can define the GUI for your property drawer. Called by Unity.
    public override void OnGUI ( Rect position ,
                                SerializedProperty prop ,
                                GUIContent label ) {
        // Adjust height of the text field
        Rect textFieldPosition = position ;
        textFieldPosition . height = textHeight ;
        DrawTextField ( textFieldPosition , prop , label ) ;
        // Adjust the help box position to appear indented underneath the text field.
        Rect helpPosition = EditorGUI . IndentedRect ( position ) ;
        helpPosition . y += textHeight ;
         helpPosition . height = helpHeight ;
        DrawHelpBox ( helpPosition , prop ) ;
    }
    void DrawTextField ( Rect position , SerializedProperty prop , GUIContent label ) {
        // Draw the text field control GUI.
        EditorGUI . BeginChangeCheck ( ) ;
        string value = EditorGUI . TextField ( position , label , prop . stringValue ) ;
        if ( EditorGUI . EndChangeCheck ( ) )
            prop . stringValue = value ;
    }
    void DrawHelpBox ( Rect position , SerializedProperty prop ) {
        // No need for a help box if the pattern is valid.
        if ( IsValid ( prop ) )
            return ;
        EditorGUI . HelpBox ( position , regexAttribute . helpMessage , MessageType . Error ) ;
    }
     // Test if the propertys string value matches the regex pattern.
     bool IsValid ( SerializedProperty prop ) {
         return Regex . IsMatch ( prop . stringValue , regexAttribute . pattern ) ;
     }
}

为自定义的可序列化类编写PropertyDrawer (Writing a PropertyDrawer for a custom Serializable Class)

Sometimes you have a custom serializable class that is used in multiple different scripts, and you want to use some specific GUI for that class everywhere it’s used. Consider this ScaledCurve class:

有时您有一个自定义可序列化的类,该类可用于多个不同的脚本中,并且您想在使用该类的任何地方为该类使用一些特定的GUI。 考虑这个ScaledCurve类:

1
2
3
4
5
6
// Custom serializable class
[System.Serializable]
public class ScaledCurve {
    public float scale = 1;
    public AnimationCurve curve = AnimationCurve.Linear (0, 0, 1, 1);
}
1
2
3
4
5
6
// Custom serializable class
[ System . Serializable ]
public class ScaledCurve {
    public float scale = 1 ;
    public AnimationCurve curve = AnimationCurve . Linear ( 0 , 0 , 1 , 1 ) ;
}

Normally every instance of this class would be shown in the Inspector with a foldout, but you can create a PropertyDrawer to show the class with custom GUI. This GUI is then used everywhere the class is used. The Property Drawer used here places all the controls in one line. Here’s the image again so you can compare the look of the Range and Falloff properties without and with the custom PropertyDrawer:

通常,该类的每个实例都将在折叠器中显示在检查器中,但是您可以创建一个PropertyDrawer来使用自定义GUI来显示该类。 然后,在使用该类的任何地方都将使用此GUI。 这里使用的属性抽屉将所有控件放在一行中。 再次显示图像,因此您可以比较不带自定义PropertyDrawer的Range和Falloff属性的外观:

unity 抽屉_Unity 4中的属性抽屉

To make a PropertyDrawer for a class, pass the type of that class to the CustomPropertyDrawer attribute of the PropertyDrawer:

要为一个类创建PropertyDrawer,请将该类的类型传递给PropertyDrawer的CustomPropertyDrawer属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer (typeof (ScaledCurve))]
public class ScaledCurveDrawer : PropertyDrawer {
    const int curveWidth = 50;
    const float min = 0;
    const float max = 1;
    public override void OnGUI (Rect pos, SerializedProperty prop, GUIContent label) {
        SerializedProperty scale = prop.FindPropertyRelative ("scale");
        SerializedProperty curve = prop.FindPropertyRelative ("curve");
        // Draw scale
        EditorGUI.Slider (
            new Rect (pos.x, pos.y, pos.width - curveWidth, pos.height),
            scale, min, max, label);
        // Draw curve
        int indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;
        EditorGUI.PropertyField (
            new Rect (pos.width - curveWidth, pos.y, curveWidth, pos.height),
            curve, GUIContent.none);
        EditorGUI.indentLevel = indent;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine ;
using UnityEditor ;
[ CustomPropertyDrawer ( typeof ( ScaledCurve ) ) ]
public class ScaledCurveDrawer : PropertyDrawer {
    const int curveWidth = 50 ;
    const float min = 0 ;
    const float max = 1 ;
    public override void OnGUI ( Rect pos , SerializedProperty prop , GUIContent label ) {
        SerializedProperty scale = prop . FindPropertyRelative ( "scale" ) ;
        SerializedProperty curve = prop . FindPropertyRelative ( "curve" ) ;
        // Draw scale
        EditorGUI . Slider (
            new Rect ( pos . x , pos . y , pos . width - curveWidth , pos . height ) ,
            scale , min , max , label ) ;
        // Draw curve
        int indent = EditorGUI . indentLevel ;
        EditorGUI . indentLevel = 0 ;
        EditorGUI . PropertyField (
            new Rect ( pos . width - curveWidth , pos . y , curveWidth , pos . height ) ,
            curve , GUIContent . none ) ;
        EditorGUI . indentLevel = indent ;
    }
}

结语 (Wrapping Up)

Finally a few words about how to structure these classes. Your scripts remain in the asset folder as before. Your new attributes should also be placed in the asset folder just like any scripts, however a recommendation is to keep your asset folder organized, so consider creating an Attributes folder. Your new property drawers should be placed in an editor folder, inside your assets folder. Check out this image, hopefully it should make it pretty clear.

最后,关于如何构造这些类的几句话。 您的脚本将像以前一样保留在资产文件夹中。 您的新属性也应像任何脚本一样放置在资产文件夹中,但是建议保持资产文件夹井井有条,因此请考虑创建“属性”文件夹。 新的属性抽屉应放置在资产文件夹内的编辑器文件夹中。 签出这张图片,希望它可以使它很清晰。

If you have open beta access to Unity 4, you can learn more about PropertyDrawers in your local copy of the Unity 4 Script Reference – just search for PropertyDrawer. Once Unity 4 is released, the information will be in the online version of the Script Reference as well.

如果您具有对Unity 4的开放测试版访问权限,则可以在Unity 4脚本参考的本地副本中了解有关PropertyDrawers的更多信息-只需搜索PropertyDrawer即可 。 Unity 4发布后,该信息也将在脚本参考的在线版本中。

That’s all! We’re excited to see what kinds of cool extensions you will create with Unity 4!

就这样! 我们很高兴看到您将使用Unity 4创建什么样的出色扩展!

翻译自: https://blogs.unity3d.com/2012/09/07/property-drawers-in-unity-4/

unity 抽屉