自定义Inspector面板

Attribute自定义Inspector

使用Attribute

实现下面的组件,体验几个Attribute的作用:

public class EditorTest : MonoBehaviour
{
    [Header("属性标题")]
    [Tooltip("This is a property.")]
    public int property1;
    [Space(1f)]
    [Tooltip("This is another property.")]
    public int property2;
    
    [Foldout("Group")]
    public int a;
    [Foldout("Group")]
    public int b;
    [Foldout("Group")]
    public int c;
}

Header(string header)属性可以在Inspector中增加一个粗体的标题。

Space(float hight)属性可以在Inspector中增加一段间隔。

Tooltip(string tooltip)属性使你在Inspector面板中将鼠标浮动在属性上时显示一个文字提示。

Foldout(string foldout)属性可以在Inspector面板中渲染折叠栏,参数名称相同的属性放置在同一个折叠栏中。

组件的Editor脚本

给组件绑定Editor脚本

以封装一个逻辑判断系统为例展示使用Editor脚本修改组件在Inspector面板的显示的方法。

在Project面板的根目录下创建一个名为"Editor"的文件夹,在其下新建脚本,命名为LogicalSystemEditor。同样在根目录下创建一个名为"Scripts"的文件夹,在其下新建脚本,命名为LogicalSystem。打开LogicalSystemEditor,修改类型定义如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;c
using UnityEngine.UI;

[CustomEditor(typeof(LogicalSystem))]
public class LogicalSystemEditor : Editor
{
    public override void OnInspectorGUI()
    {
	}
}
设计一个类

使用几个类来封装逻辑系统,下面的脚本使用了ScriptableObject,将来会着重学习,感兴趣的可以提前了解:

[CreateAssetMenu]
[System.Serializable]
public class LogicalSystem : ScriptableObject
{
	public List<OrStatement> orStatement = new List<OrStatement>();
	public uint orStatementLength;
	public bool matchStatement
	{
		get
		{
			foreach(OrStatement statement in orStatement)
			{
				if(statement.matchStatement)	return true;
			}
			return false;
		} 
	}
}

[System.Serializable]
public class OrStatement
{
    public List<AndStatement> andStatement = new List<AndStatement>();
    public uint andStatementLength;
    public bool matchStatement
    {
        get
        {
        	foreach(AndStatement statement in andStatement)
        	{
         	   if(!statement.matchStatement) return false;
			}
        	return true;
   		}
    }
}

[System.Serializable]
public class AndStatement
{
    public LogicalCondition condition;
    public bool matchStatement()
    {
        switch(condition)
        {
        	//未完成,请自行补充
        }
	}
}

[System.Serializable]
public enum  LogicalCondition
{
	//未完成,请自行补充
}
自定义Inspector

修改LogicalSystemEditor如下:

public class LogicalSystemEditor : Editor
{
    public override void OnInspectorGUI()
    {
    	LogicalSystem s = (LogicalSystem)target;
    	s.orStatementLength = (uint)EditorGUILayout.IntField("Conditions:", (int)s.orStatementLenth);
    	if (s.orStatementLength <= 0) s.orStatementLength = 1;
    	for (; s.orStatementLength > s.orStatement.Count;)
        {
            s.orStatement.Add(new OrStatement());
        }
        for (int i = 0; i < s.orStatementLength; i++)
        {
            OrStatement orBlock = s.orStatement[i];
            orBlock.andStatementLength = (uint)EditorGUILayout.IntField("    Or " + i, (int)orBlock.andStatementLength);
            if (orBlock.andStatementLength <= 0) orBlock.andStatementLength = 1;
            for (; orBlock.andStatementLength > orBlock.andStatement.Count;)
            {
                orBlock.andStatement.Add(new AndStatement());
            }
            for(int j = 0; j < orBlock.andStatementLength; j++)
            {
                AndStatement statement = orBlock.andStatement[j];
                statement.condition = (EffectConditionType)EditorGUILayout.EnumPopup("        And " + j, statement.condition);   
                switch(statement.condition)
                {
                    
                }
            }
        }
	}
}

在Project面板右键创建一个新的LogicalSystem,即可在Inspector面板观察到自定义的结果。

着重解析Editor脚本中的内容:
LogicalSystem s = (LogicalSystem)target;

在Editor脚本中,调用target属性可以获得我们试图修改的那个对象,在这里我们还需要将target拆箱,于是使用强制类型转换,并将其保存到一个局部遍历s中,这是为了后面书写的方便。

s.orStatementLength = (uint)EditorGUILayout.IntField("Conditions:", (int)s.orStatementLenth);

在此我们使用了EditorGUILayout.IntField()函数,这个函数可以在Inspector面板中绘制一个输入整数的输入框。函数的第一个参数用于修改这个输入框前的文字信息,第二个参数是输入框中值的来源,而返回值是修改后的结果。由于我们想让这个输入框的输入和输出都指向同一个属性,所以使用了两次s.orStatementLength。由于这个属性是一个uint,而函数的参数和返回值类型都是int,所以使用了两次强制类型转换。

statement.condition = (EffectConditionType)EditorGUILayout.EnumPopup("        And " + j, statement.condition);

在此我们又用了一个EditorGUILayout.EnumPopup函数,这个函数的作用是绘制一个Enum的下拉选框。函数的第一个参数是下拉选框前的文字信息,第二个参数是值的来源,返回值是修改后的结果。同上,我们使用了两次一样的属性。由于函数返回类型为Object,我们使用了一次强制类型转换。

EditorGUILayout的其它方法
public static Rect EditorGUILayout.BeginHorizontal();
public static void EditorGUILayout.EndHorizontal();
public static Rect EditorGUILayout.BeginVertical();
public static void EditorGUILayout.EndVertical();

可以控制属性的排版,在Begin和End中间渲染的其它Inspector组件会呈竖直或水平排版。

public static Vector2 EditorGUILayout.BeginScrollView(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical);
public static void EditorGUILayout.EndScrollView();

渲染一个可以滚动的子面板,通过在其中镶嵌BeginHorizontal()等函数可以更好的自定义面板。

public static Color EditorGUILayout.ColorField(string label, Color value);

渲染一个颜色条。参数和返回值都需要填同一个对象,label指输入框前的文字信息。

public static double EditorGUILayout.DoubleField(string label, double value); 
public static float EditorGUILayout.FloatField(string label, float value); 
public static int EditorGUILayout.FloatField(string label, int value);

渲染一个可以输入数值的文本框。

public static float EditorGUILayout.Slider(string label, float value, float leftValue, float rightValue); 
public static int EditorGUILayout.IntSlider(string label, int value, int leftValue, int rightValue);

渲染一个滑动条,可以输入或拖动数值。

public static Enum EditorGUILayout.EnumPopup(string label, Enum selected);

渲染一个可以选择Enum的下拉选框。注意,Enum必须具有System.Serializable标记。

public static Enum EditorGUILayout.EnumFlagsField(string label, Enum enumValue);

渲染一个可以多选的下拉选框。适用这个方法的Enum需要特别构建:

[System.Serializable]
enum ExampleFlagsEnum
{
 None = 0, // Custom name for "Nothing" option
 A = 1 << 0,
 B = 1 << 1,
 AB = A | B, // Combination of two flags
 C = 1 << 2,
 All = ~0, // Custom name for "Everything" option
}

在ExampleFlagsEnum的渲染中,Enum的值被以位的方式解读,选中的每个值会连续做按位或运算,并将结果输出。注意,Enum必须具有System.Serializable标记。

public static void EditorGUILayout.Space();

渲染一段空行。

public static void EditorGUILayout.LabelField(string label);

渲染一段不能修改的标题字符。

public static string EditorGUILayout.TextField(string label, string text); 
public static string EditorGUILayout.TextArea(string text);

渲染一个可供输入的文本框。Area是多行的,Field是单行的。

public static Vector2 EditorGUILayout.Vector2Field(string label, Vector2 value); 
public static Vector3 EditorGUILayout.Vector3Field(string label, Vector3 value); 
public static Vector4 EditorGUILayout.Vector3Field(string label, Vector4 value); 
public static Vector2Int EditorGUILayout.Vector2IntField(string label, Vector2Int value); 
public static Vector3Int EditorGUILayout.Vector3IntField(string label, Vector3Int value); 
public static Vector4Int EditorGUILayout.Vector4IntField(string label, Vector4Int value);

渲染用于输入向量的组合输入框。

public static bool EditorGUILayout.Toggle(string label, bool value);

渲染一个返回布尔类型的勾选框。

public static Object EditorGUILayout.ObjectField(string label, Object obj, Type objType, bool allowSceneObjects);

渲染一个可以拖入任何类型的框,尤其适用于拖入ScriptableObject或MonoBehaviour。

这个方法还可以用于渲染拖入图片的框体,如下列代码:

target.icon = EditorGUILayout.ObjectField("Icon", target.icon, typeof(Sprite), true) as Sprite;