在 Unity 中,[Attribute]格式的特性是用于扩展编辑器功能、控制序列化行为和调整 Inspector 显示,进行编辑器拓展的核心工具。
一.基础编辑器拓展
1.基础序列化控制
1.[SerializeField] 强制显示私有变量到Inspector
2.[HideInInspector] 隐藏该字段在Inspector中的显示
3.[NonSerialized] 阻止该字段被序列化
4.[Serializable] 对类内类使用
2.Inspector布局优化
1.[Header(“Title”)] 为该字段添加标题
2.[Tooltip("Description")] 鼠标悬停显示提示文本
3.[Space(int)] 插入垂直距离,修改字段显示垂直间距
4.[TexArea] 动态拓展多行文本输入框
5.[Multiline(int)] 多行文本输入框
3.数值约束
1.[Range(min,max)] float类型变量使用,滑动条约束
2.[Min(minValue)] 设置最小值
4.警告提示
1.[Obsolete(“已弃用的提示”)]:警告该标志字段,已弃用
示例代码
[SerializeField, Range(0, 1)] float ID;
[HideInInspector] public int index;
public Student student;
[TextArea]
[Space(5)]
[SerializeField]
string description;
[Obsolete("弃用的错误的数值")]
int wrongNum;
int trueNum;
private void ObsoleteTest()
{
wrongNum++;
}
[Serializable]
public class Student
{
[Header("年龄"), Tooltip("以阴历为准"), Min(0)]
public int age;
}
二.高级编辑器拓展
1.脚本标记
1.[ExecuteInEditorMode]:该脚本在编辑器模式下运行
2.[RequireComponment(typeof(TypeName ))]:挂载本脚本时将自动添加目标组件
3.[DisallowMultipleComponent]:禁止同一物体重复挂载本脚本
4.[CustomEditor(typeof(TypeName))]:本脚本对目标类型脚本进行编辑器面板下的拓展
第四种较为特殊,在这里详细介绍
[CustomEditor(typeof(TypeName))]
使用该特性,需要编写一个实例脚本及一个对其进行拓展的继承Editor的脚本。
实例脚本Test
示例代码
using UnityEngine;
[ExecuteInEditMode]//编辑模式下任意操作引起帧运行
[DisallowMultipleComponent]//禁止重复挂载
[RequireComponent(typeof(Rigidbody))]
public class Test : MonoBehaviour
{
public int ID;
public string Name;
public bool isDead;
public GameObject weapon;
public Texture texture;
public E_testEnum testEnum;
public float processSlider;
public Player player;
void Update()
{
// Debug.Log("操作了一下");
}
public void TestDebug()
{
Debug.Log("Test!");
}
}
public enum E_testEnum
{
test1, test2, test3
}
编辑器脚本TestEditor
我们需要重写实现Editor中的OnInspectorGUI()方法,在脚本组件面板上绘制信息。
当OnInspectorGUI()函数中无功能代码时,发现Test脚本无法正常显示信息(见下图)
此时,可以使用DrawDefaultInspector()来进行默认字段的正常绘制。
public override void OnInspectorGUI()
{
DrawDefaultInspector();
}
接下来我们来自定义绘制一下所有字段。
示例代码
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Test))]
public class TestEditor : Editor
{
//获得需要编辑显示的组件
private Test testComponent;
//当关联组件所在对象被选中或组件被添加时调用
private void OnEnable()
{
testComponent = (Test)target;//在当前的外挂脚本中获得需要被拓展的test组件对象
}
//当关联组件所在对象被取消选中或组件爱你被移除时调用
private void OnDisable()
{
testComponent = null;
}
//OnInspectorGUI是用于绘制检视面板的生命周期函数
public override void OnInspectorGUI()
{
//标题显示
EditorGUILayout.LabelField("测试拓展脚本");
//简单数据类型绘制
testComponent.ID = EditorGUILayout.IntField("角色ID", testComponent.ID);
testComponent.Name = EditorGUILayout.TextField("角色姓名", testComponent.Name);
testComponent.isDead = EditorGUILayout.Toggle("是否死亡", testComponent.isDead);
//对象数据类型绘制/
testComponent.weapon = EditorGUILayout.ObjectField("武器", testComponent.weapon, typeof(GameObject), true) as GameObject;
testComponent.texture = EditorGUILayout.ObjectField("贴图", testComponent.texture, typeof(Texture), false) as Texture;
//标题,原始组件的值,成员变量的类型,是否可以将场景中对象拖给这个变量,注意纹理来源于project而非场景中物体,故用false
//枚举数据类型绘制
testComponent.testEnum = (E_testEnum)EditorGUILayout.EnumPopup("玩家职业", testComponent.testEnum);
//终极数据类型绘制(适用于list等,以及自己写的类型的数据)
OtherDataDraw("player", "玩家");
//滑动条绘制
testComponent.processSlider = EditorGUILayout.Slider(new GUIContent("滑动条"), testComponent.processSlider, 0, 100);
if (testComponent.processSlider >= 80)
{
EditorGUILayout.HelpBox("滑动条即将到顶", MessageType.Error);
}
if (testComponent.processSlider <= 20)
{
EditorGUILayout.HelpBox("滑动条即将到底", MessageType.Warning);
}
//按钮绘制(默认纵向绘制,一行占一个按钮)
if (GUILayout.Button("来个按钮"))
{
Debug.Log("点击了按钮");
};
//开启横向绘制(一行可多个按钮)
GUILayout.BeginHorizontal();
//关闭横向绘制
GUILayout.EndHorizontal();
}
void OtherDataDraw(string originalDataName, string processedHeadName)
{
//更新可序列化数据
serializedObject.Update();
//通过成员变量名找到组件上的成员变量
SerializedProperty sp = serializedObject.FindProperty(originalDataName);
//可序列化数据绘制(取到的数据,标题,是否将所有获得的序列化数据显示)
EditorGUILayout.PropertyField(sp, new GUIContent(processedHeadName), true);
//将修改的数据写入到可序列化的原始数据中
serializedObject.ApplyModifiedProperties();
}
}
2.[AddComponment( )]
标记实例脚本,编辑器面板Componment菜单拓展。
使用:点击拓展脚本即可为目标物体添加脚本组件。
常用重载
1.AddComponentMenu(string menuName)
编辑器面板Componment菜单拓展,标记实例脚本。
2.AddComponentMenu(string menuName, int order)
编辑器面板Componment菜单拓展,标记实例脚本。
参数order表示拓展脚本按钮在面板中的优先级,值越小,在面板中位置越靠上。
示例代码
[AddComponentMenu("MyAddC/addc1", 1)]
public class AddC1 : MonoBehaviour{
}
注意:先选中场景内游戏物体,再为其添加脚本~
3.[MenuItem( )]
编辑器面板菜单拓展,标记静态函数。点击拓展方法即可在编辑器面板下执行。
常用重载
1.[MenuItem("MenuName/FuncName")]
编辑器面板菜单拓展,标记静态方法。
//增加菜单栏选项
[MenuItem("MyTools/SendMes1 &1", false, 900)]
static void SendMes1()
{
print("Print1");
}
2.[MenuItem("MenuName/FuncName",int priority)]
编辑器面板菜单拓展,标记静态方法。
参数priority表示拓展方法在面板中的优先级,值越小,在面板中位置越靠上。
优先级每差出10级,面板下多一条分割线(见下图)
3.[MenuItem(“CONTEXT/组件名称/拓展功能名称”)]
Inspector拓展脚本缩略点内功能,对当前实例脚本内非静态方法使用。
示例代码
//给某目标类型组件添加右键菜单选项【“CONTEXT/Rigibody(组件名称)/Init(按钮名称)”】
[MenuItem("CONTEXT/Rigidbody/Init")]
static void RigInit()
{
Debug.Log("我是Rigibody");
}
4.[ContextMenu( )]
常用重载
1.[ContextMenu(“功能描述”)]
Inspector拓展脚本缩略点内功能,对当前实例脚本内非静态方法使用。
2.[ContextMenuItem("功能描述", "实例方法名称")]
Inspector拓展字段右键菜单功能,对当前实例脚本内非静态方法使用。
优化:nameof( )关键字替代"实例方法" ,便于后期维护。
示例代码
public class AttributesTest : MonoBehaviour
{
[ContextMenuItem("增加10点力量", "AddPower")]
public int power;
//对当前实例脚本使用,方法需非静态,打开脚本右侧缩略点可直接调用该方法
[ContextMenu("组件目录菜单")]
void Init()
{
Debug.Log("方法调用");
}
void AddPower()
{
power += 10;
}
}
以上是我学习过程中总结的印象中大致所有的Attribute特性及其应用,可能会有瑕疵或缺漏,欢迎大家不吝赐教。
本篇完!