Attributes属性
属于U3D的RunTimeClass,所以要加上命名空间:
using UnityEngine;
using System.Collections;
在Unity引擎中,Attributes(属性)用于修饰类、字段、方法等,以提供额外的元数据或行为。例如,[SerializeField]属性用于在Unity编辑器中显示私有字段。我们可以模仿这种机制,在C#中实现自定义属性,并在运行时或编译时应用这些属性。
下面是一个商业化级别的具体代码实现,展示如何创建和使用自定义属性。
一、面板内建特性
首先是喜闻乐见的特性(Attribute),Unity内建了许多方便的特性来改变和扩展编辑器。
此外关于更多Unity内建的属性,可以在官方API文档中查看。 但文档并没有单独整理用于 Inspector面板 的特性,所以我在这做了一些简单的展开。
总所周知,Unity 提供了 “PropertyAttribute” 类,使我们可以方便的定义一个控制变量在Inspector中样式的特性。 于是,我们可以通过一个简单的反射,来获取Unity内建的这些属性:
using System.Reflection;
[UnityEditor.MenuItem("Test/打印PropertyAttribute派生类")]
static public void GetPropertyAttributeDerivedClass()
{
foreach (var item in Assembly.Load("UnityEngine.CoreModule").GetTypes())
if (item.IsSubclassOf(typeof(PropertyAttribute)))
Debug.Log(item);
}
使用上面方法,我们得到了12个特性,没错只有12个。是不是总觉得少了很多。
所以我又打印了一下从 “Attribute” 派生的所有类,这回一共有90个。
于是简单的整理了下面表格(仅与Inspector面板有关):
特性 | 说明 |
“PropertyAttribute” | 派生的所有特性 | 改变一个字段的样式 |
[HideInInspector] | 隐藏一个字段 |
[HelpURL]、[ContextMenu] | 作用在组件头部的两个特性 |
[RequireComponent] [AddComponentMenu] | 功能性的两个特性 |
[SerializeField] [SerializeReference] [FormerlySerializedAsAttribute] | 序列化相关 |
[Serializable]、[NonSerialized] | 序列化相关,但属于 "System" 命名空间下 |
从上可以看出,除了 “PropertyAttribute” 派生的所有特性 和 [HideInInspector] 外,其他特性主要是功能性的。
至于 [HideInInspector] 为什么不用继承 “PropertyAttribute” 类来实现。
目前我所能理解的,由于 “PropertyDrawer” 的实现方式,面板在绘制数组或List时,其根部并不在 “PropertyDrawer” 的处理范围内,所以无法全部隐藏,于是单独定义了一个 [HideInInspector] 特性。
二、自定义特性
扯完内建特性,接下来当然是自定义特性。 本着“要致富,先撸树”的原则。我们自然需要先定义一个继承自 “PropertyAttribute” 的类。
using UnityEngine;
[System.AttributeUsage(AttributeTargets.Field)] //限制属性只能使用在Field上
public class CustomAttribute : PropertyAttribute {}
//定义特性时建议在命名后加上“Attribute”以方便区分,使用时则可以省略
属性定义完,接着就是画UI了。 绘制UI的方式有则有两种, 一是继承 “DecoratorDrawer” 类,这可以在原有的基础上添加一些内容,但不提供修改变量的方法,如 [Space] [Header]。 二是继承 “PropertyDrawer” 类,这就需要自己全部重头绘制了,但可以通过方法中的 “property” 参数来修改变量。
using UnityEngine;
using UnityEditor;
/*************** DecoratorDrawer ***************/
[CustomPropertyDrawer(typeof(CustomAttribute))] //关联特性
public class CustomAttributeDrawer : DecoratorDrawer
{
public override void OnGUI(Rect position)
{
EditorGUI.DrawRect(position, Color.green);
GUI.Label(position, "DecoratorDrawer");
}
}
/*************** PropertyDrawer ***************/
[CustomPropertyDrawer(typeof(CustomAttribute))] //关联特性
public class CustomAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.DrawRect(position, Color.green);
GUI.Label(position, "PropertyDrawer");
}
//PropertyDrawer 除了用重载 OnGUI 的方式来绘制基于 IMGUI 的 GUI 外,
//还可以选择用重载 CreatePropertyGUI 的方式来绘制基于 UIElements 的 GUI。
}
之后定义个变量测试一下。
三、可序列化类型的自定义绘制
上面使用了 “DecoratorDrawer” 和 “PropertyDrawer” 两个类,来关联特性。 实际上我们也可以直接用来关联可序列化的类型。 只需要把 [CustomPropertyDrawer(typeof(...))] 中的特性类换掉就可以了。 [CustomPropertyDrawer(typeof(可序列化类型))]
像上面示例中,变量 test 是 int 类型的。 所以,将 “typeof(CustomAttribute)” 换成 “typeof(int)” 也能做到同样的效果。 不同的是,之后所有 int 类型的变量,默认样式都会改变。
当然,用在自定义的可序列化类型上也是可以的。
//==================== Test.cs
public class Test : MonoBehaviour
{
//定义个类,记得加上可序列化特性
[System.Serializable]
public class MyClass { ... }
//之后定义变量就不用加特性啦
public MyClass c;
}
//==================== MyClassDrawer.cs
[CustomPropertyDrawer(typeof(Test.MyClass))] //关联可序列化类型
public class MyClassDrawer : DecoratorDrawer //或者继承PropertyDrawer
{
...
}
四、组件的自定义绘制
编写组件的时候,免不了整了一些有关联的变量,或是数组套数组,看着特闹心, 又或是想加个功能按钮之类的,上面的方法就不好用了, 所以这时候,一不做二不休,直接继承 Editor,重画整个组件吧。
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(Test))] //关联类型
public class TestEditor : Editor
{
public override void OnInspectorGUI()
{
Rect position = GUILayoutUtility.GetRect(0, 20, GUILayout.ExpandWidth(true));
EditorGUI.DrawRect(position, Color.green);
GUI.Label(position, "TestEditor");
}
//也可以选择用重载 CreateInspectorGUI 的方式来绘制基于 UIElements 的 GUI。
}
当然,改变Unity自带组件的样式也是可以的。 甚至,GameObject 本身也算个组件。所以,需要的话改变 GameObject 的显示也不是问题。
五、控制对象销毁的hideFlags
hideFlags 主要用于控制对象销毁,保存以及在检查器中的可见性,一般不会动他。 但我们的游戏物体和组件一般默认是 None 的状态, 而 HideInHierarchy、HideInInspector、NotEditable 也就改变了对象在 Hierarchy 和 Inspector 中的显示, 所以有需要的大可放心用。需要注意的是剩下带 “Dont” 的状态,都需要手动销毁。
using UnityEngine;
public class Test : MonoBehaviour
{
private void Start()
{
gameObject.hideFlags = HideFlags.NotEditable; //使gameObject不可编辑
}
}
六、AddComponentMenu添加组件菜单
AddComponentMenu 属性允许将一个脚本添加到 Component 菜单中,然后你便可以通过 Component ->(你设置的名字)为一个选中的游戏对象创建该脚本,如下所示:
[AddComponentMenu("Learning/People")]
public class People : MonoBehaviour
{
}
七、RequireComponent() 添加组件
RequireComponent()属性会自动帮你添加你需要的组件,如果已经存在则不再重复添加,且不能移除,如下所示:
[AddComponentMenu("Learning/People")]
[RequireComponent(typeof(Rigidbody))]
public class People : MonoBehaviour
{
}
提示:经过测试,我发现一个问题,如果脚本已经挂在物体身上,然后再修改脚本,为添加 RequireComponent 属性的话,完全不起作用,因此建议大家在用此属性的时候要注意。
八、ContextMenu()和ContextMenuItem() 菜单定义
ContextMenu()属性允许添加一个命令到该组件上,你可以通过右键或者点击设置图标来调用到它(一般用于函数),且是在非运行状态下执行该函数;
ContextMenuItem()属性允许添加一个命令到该变量上,可通过右击变量来调用对应方法
如下所示:
[AddComponentMenu("Learning/People")]
[RequireComponent(typeof(Rigidbody))]
public class People : MonoBehaviour
{
public string name;
public int age;
[ContextMenu("OutputInfo")]
void OutputInfo()
{
print(name+"|"+age);
}
}
public class People : MonoBehaviour
{
[ContextMenuItem("右击时显示名", "OutputInfo")]
public int age;
public string name="Hia";
void OutputInfo()
{
print(name+"|"+age);
}
}
九、HelpURL() 指定文档链接
HelpURL()提供一个自定义的文档链接,点击组件上的文档图标既能打开到你指定的链接,如下所示:
[HelpURL("http://www.baidu.com")]
public class People : MonoBehaviour
{
public string name;
public int age;
}
提示:填写链接时,一定要写上 http:// 或者 https://,否则将无任何反应。
十、InitializeOnLoad 启动事件监听
在Unity开发过程中,我们经常需要在编辑器启动时或脚本重新编译后执行一些操作,例如初始化数据、注册事件等。这时,我们可以使用InitializeOnLoad特性来实现这一需求。本文将详细介绍InitializeOnLoad特性的用法,并通过三个实际案例来展示其应用场景。
InitializeOnLoad是Unity引擎提供的一种特性,用于在编辑器启动时或脚本重新编译后自动执行指定的操作。这种特性非常适合用于在编辑器启动时执行一些初始化操作,以确保项目在启动后能够正常运行。
要使用InitializeOnLoad特性,只需在编辑器脚本中为一个静态类添加该特性即可。例如:
using UnityEditor;
using UnityEngine;
[InitializeOnLoad]
public static class MyInitializer
{
static MyInitializer()
{
Debug.Log("InitializeOnLoad called.");
}
}
在这个例子中,我们创建了一个名为MyInitializer的静态类,并为其添加了InitializeOnLoad特性。当编辑器启动时,MyInitializer类的静态构造函数将被自动调用,从而实现自动初始化的功能。
十一、Range()、Multiline()、header()
Range()属性用于将一个值指定在一定的范围内,并在Inspector面板中为其添加滑块;
Multiline() 属性用于给 string 类型添加多行输入;
header() 属性用于添加属性的标题,具体操作如下所示:
public class People : MonoBehaviour
{
[Header("BaseInfo")]
[Multiline(5)]
public string name;
[Range(-2,2)]
public int age;
}
简单的分解一下:
1、第9行,我们使用了 [Header(“BaseInfo”)] 为其设置了标题(为“BaseInfo”),如上图所示。
2、第10行,我们使用了 [Multiline(5)] 为其 name 属性添加了5行输入,如上图所示,明显输入框变大了。
3、第12行,我们使用了 [Range(-2,2)] 为其 age 属性指定了一个(-2,2)的范围,并且为其添加了一个滑块,如上图所示。
十二、Tooltip()提示、Space() 距离设定
Tooptip()属性用于在 Inspector 面板中,当鼠标停留在设置了Tooptip()的属性添加指定的提示;
Space()用于为在 Inspector 面板两属性之间添加指定的距离,如下所示:
public class People : MonoBehaviour
{
[Header("BaseInfo")]
[Multiline(5)]
public string name;
[Range(-2, 2)]
public int age;
[Space(100)]
[Tooltip("用于设置性别!")]
public string sex;
}
十三、Serializable、SerializeField 序列化
1、Serializable这个属性可以让子类(继承类)的变量属性显示在检视面板中,也能序列化它。(JS的话完全不需要这个属性。)
[System.Serializable]
public class Boy
{
public int stength;
}
public class People : MonoBehaviour
{
public Boy bb;
}
2、SerializeField 序列化域(强制序列化):可以将私有变量序列化,将U3D的内建变量序列化等。
[SerializeField, Range(-2, 2)]
private int age;
当然公有变量也可以在面板隐藏:
[HideInInspector] 面板隐藏
[NonSerialized] 不被序列化(不被序列化且不在面板显示)