Unity编辑器扩展摘要
扩展Unity编辑器的类应该放在 Editor 目录下,类中的方法应该声明为 static
方法。
MenuItem
特性说明
特性标签:
[MenuItem(string itemName, bool isValidateFunction=false, int priority=1000)]
参数说明:
itemName
:用于指定菜单栏名称和菜单项名称。isValidateFunction
:用于指定该方法是否是带有相同itemName的方法的校验方法。如果是校验方法,那么在展开菜单项所在的菜单栏时,将会调用该校验方法,如果校验方法返回true,则菜单项可点击,否则菜单项显示为灰色,不可点击。默认值为false
。priority
:显示优先级。数值越小的菜单项越靠上显示。如果同一菜单栏中的两个菜单项的优先级相差超过 11 ,那么它们之间会显示水平分隔线。如果希望GameObject菜单栏中的菜单项能够在Hierarchy窗口的右键菜单中显示,那么菜单项的优先级不能超过 49 。默认值为 1000 。
添加菜单栏和菜单项
[MenuItem]
特性标签将静态方法注册为编辑器菜单扩展方法,为编辑器添加菜单栏、子菜单栏和菜单项。下面的编辑器扩展方法将会在菜单栏中添加一个名为 MyMenuItems 的菜单栏,并在此菜单栏中添加一个名为 MyItemA1 的菜单项,当点击该菜单项时,会执行 MyItemA1()
方法 :
[MenuItem("MyMenuItems/MyItemA1", false, 1)]
private static void MyItemA1()
{
Debug.Log("点击了MyItemA1");
}
启用和禁用菜单项
[MenuItem]
特性标签的第二个参数可以为编辑器菜单项附加校验方法。下面的编辑器扩展方法为 MyMenuItems/MyItemA1 菜单项添加了校验方法,当校验方法返回 true
时,MyMenuItems/MyItemA1 可点击,当校验方法返回 false
时,MyMenuItems/MyItemA1 显示为灰色,不可点击:
[MenuItem("MyMenuItems/MyItemA1", true)]
private static bool MyItemA1Validate()
{
Debug.Log("校验MyItemA1是否可以点击");
return false;
}
备注:在Unity 2017.4.4f1和Unity 2018.2.7f1中测试时发现,可以通过校验方法禁用GameObject菜单栏中的菜单项,使其显示为灰色并且不可点击,但是在Hierarchy右键菜单中,该菜单项仍然会显示为黑色并且可以点击,只是点击后不会执行绑定的方法。
为菜单项添加分隔线
[MenuItem]
特性标签的第三个参数可以设置菜单项的显示优先级,当两个菜单项的优先级相差超过 11 时,在这两个菜单项之间会显示出水平分隔线。下面的编辑器扩展方法在 MyMenuItems 菜单栏中添加了两个菜单项,并将其与 MyItemA1 分隔开:
[MenuItem("MyMenuItems/MyItemB1", false, 1 + 11)]
private static void MyItemB1()
{
Debug.Log("点击了MyItemB1");
}
[MenuItem("MyMenuItems/MyItemB2", false, 1 + 12)]
private static void MyItemB2()
{
Debug.Log("点击了MyItemB2");
}
为Hierarchy窗口和Project窗口添加右键菜单项
Hierarchy窗口的右键菜单项放置在 GameObject 菜单栏中,并且菜单项的优先级不能大于 49 ,否则这个菜单项只能在编辑器的 GameObject 菜单栏中显示,不能在Hierarchy窗口的右键菜单中显示。
Project窗口的右键菜单项放置在 Assets 菜单栏中,这里的菜单项没有优先级限制。
示例代码:
[MenuItem("GameObject/MyGameObjectItem", false, 49)]
private static void MyGameObjectItem()
{
Debug.Log("点击了MyGameObjectItem");
}
[MenuItem("Assets/MyProjectItem")]
private static void MyProjectItem()
{
Debug.Log("点击了MyProjectItem");
}
为Inspector窗口中的组件添加右键菜单项
Inspector窗口中的组件的右键菜单项放置在 CONTEXT 菜单栏中,这个菜单栏不会显示在编辑器的水平菜单栏中。组件菜单项的名称参数格式是 CONTEXT/组件脚本名称/菜单项名称
,这里不能设置子菜单栏,也不能指定组件脚本的命名空间。下面的编辑器扩展方法为所有自定义MonoBehaviour组件的右键菜单添加了一个名为 MyMonoMenuItem 的菜单项:
[MenuItem("CONTEXT/MonoBehaviour/MyMonoMenuItem")]
private static void MyMonoMenuItem()
{
Debug.Log("点击了MyMonoMenuItem");
}
Unity内置的组件不能像上面那样通过 MonoBehaviour 添加右键菜单项,必须要使用它们自身的名称。
为编辑器扩展方法添加一个 MenuCommand
类型的参数就可以获取当前右键所点击的组件的信息。因为不能指定脚本的命名空间,所以应该在处理组件之前校验参数传入的组件的信息,以免不同命名空间下的同名脚本引发逻辑错误。
下面的编辑器扩展方法为 Transform 组件添加了一个名为 MyTransformMenuItem 的菜单项,并在方法中进行了组件类型校验:
[MenuItem("CONTEXT/Transform/MyTransformMenuItem")]
private static void MyTransformMenuItem(MenuCommand command)
{
Debug.Log("点击了MyTransformMenuItem");
if (command.context.GetType().FullName == "UnityEngine.Transfrom")
{
Transform transform = command.context as Transform;
}
}
为Inspector窗口中的自定义MonoBehaviour组件添加右键菜单项的另一种方法是使用 [ContextMenu]
特性标签,具体方法在 ContextMenu 章节中。
获取当前在Hierarchy窗口中选中的对象
Selection
类提供了许多属性和方法,用于获取当前在编辑器中被选中的对象。例如,Selection.activeGameObject属性可以返回当前在Hierarchy窗口中第一个被选中的GameObject,Selection.gameObjects可以返回当前在Hierarchy窗口中所有被选中的Game Object。
示例代码:
[MenuItem("MyMenuItems/MySelectionMenuItem")]
private static void MySelectionMenuItem()
{
GameObject gameObject = Selection.activeGameObject;
Debug.Log("第一个选中的对象:" + gameObject.name);
GameObject[] gameObjects = Selection.gameObjects;
Debug.Log("已选对象数量:" + gameObjects.Length);
}
实现撤销操作功能
在自定义编辑器扩展方法时,必须通过 Undo
类主动向编辑器注册操作内容才能通过编辑器快捷键(Ctrl Z)实现撤销操作功能。
例如,通过 Undo.DestroyObjectImmediate()
方法实现可撤销的删除操作和通过 Undo.RecordObject()
方法实现可撤销的修改操作:
[MenuItem("GameObject/MyDeleteItem", false, 49)]
private static void MyDeleteItem()
{
GameObject gameObject = Selection.activeTransform.gameObject;
Undo.DestroyObjectImmediate(gameObject);
}
[MenuItem("CONTEXT/Transform/MyResetItem")]
private static void MyResetItem(MenuCommand command)
{
if (command.context.GetType().FullName == "UnityEngine.Transfrom")
{
Transform transform = command.context as Transform;
// 记录对象当前的状态,在执行撤销操作时该对象会被恢复到记录时的状态
Undo.RecordObject(transform, "Reset transform");
// 如果执行撤销操作,下面的操作会被撤销
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
}
}
为自定义菜单项设置快捷键
在 MenuItem
的参数 itemName
中可以指定自定义菜单项的快捷键,快捷键符号放在参数字符串的最后,使用空格将其与菜单项名称分隔开。如果快捷键是单个字母键,需要在字母前添加下划线( _
);如果快捷键是字母键和修饰键的组合,则把修饰键写在前面,字母写在后面,无需在字母前添加下划线。如果为编辑器扩展方法的 itemName
特性参数添加了快捷键符号,那么相应的校验方法的 itemName
特性参数中也应该添加快捷键符号。
快捷键符号:
- 修饰键:
%
(Ctrl/Cmd),#
(Shift),&
(Alt),_
(单个字母键) - 方向键:LEFT,RIGHT,UP,DOWN
- 其他:F1 … F12,HOME,END,PGUP,PGDN等
在下面的编辑器扩展方法中,为 MyMenuItems/MyItemA1 菜单项添加了快捷键 A ,为 MyMenuItems/MyItemB1 菜单项添加了快捷键 Ctrl+Shift+Alt+B :
[MenuItem("MyMenuItems/MyItemA1 _A", false, 1)]
private static void MyItemA1()
{
Debug.Log("点击了MyItemA1");
}
[MenuItem("MyMenuItems/MyItemB1 %&#B", false, 1 + 11)]
private static void MyItemB1()
{
Debug.Log("点击了MyItemB1");
}
ContextMenu
为Inspector窗口中的组件添加右键菜单项
使用 ContextMenu
特性标签可以为自定义MonoBehaviour组件添加Inspector窗口中的右键菜单项,它作用于MonoBehaviour类中的成员方法,而不是Editor扩展类中的静态方法。 ContextMenu
特性标签的参数与 MenuItem
特性标签的参数完全相同,但是绑定的快捷键不会生效。
下面的示例代码中,为Test组件添加了一个名为 MyComponentMenuItem 的Inspector组件右键菜单项,当点击该菜单项时,会调用 MyComponentMenuItem()
方法:
public class Test : MonoBehaviour
{
[ContextMenu("MyComponentMenuItem")]
private void MyComponentMenuItem()
{
Debug.Log("点击了MyComponentMenuItem");
}
}
ContextMenuItem
ContextMenuItemAttribute API文档
为Inspector窗口中的组件字段添加右键菜单项
使用 ContextMenuItem
特性标签可以为自定义MonoBehaviour类中的字段添加Inspector窗口中的右键菜单项,它作用于MonoBehaviour类中的成员字段。 ContextMenuItem
特性标签需要两个 string
类型的参数,第一个参数用于设置菜单项名称,第二个参数用于设置当点击了菜单项时要调用的方法的名称。
下面的示例代码中,为Test组件的 myField
字段添加了一个名为 MyFieldMenuItem 的Inspector组件右键菜单项,当点击该菜单项时,会调用 MyFieldMenuItemMethod()
方法:
public class Test : MonoBehaviour
{
[ContextMenuItem("MyFieldMenuItem", "MyFieldMenuItemMethod")]
public string myField;
private void MyFieldMenuItemMethod()
{
Debug.Log(myField);
}
}
ScriptableWizard
创建不可停靠的自定义编辑器窗口
ScriptableWizard
类提供了用于创建不可停靠的自定义编辑器窗口的编程接口。编写继承自 ScriptableWizard
的自定义类,在其中利用 ScriptableWizard
提供的属性和方法可以设置窗口属性、获取鼠标和字段的操作等事件,配合 Selection
类 和 Undo
类可以获取当前选中的对象和实现撤销操作等。
使用 ScriptableWizard.DisplayWizard<T>()
方法来显示窗口。 DisplayWizard<T>()
方法需要一个泛型参数和三个字符串参数,分别用于指定要显示的窗口类、窗口标题、“Create”按钮的名称和“Other”按钮的名称。最后面的两个参数是可选参数,用于设置显示在自定义编辑器窗口底部的两个按钮(Create和Other)的名称。如果没有指定“Create”按钮的名称,那么会使用 Create 做为该按钮的默认名称;如果没有指定“Other”按钮的名称,那么不会显示该按钮。
下面的示例代码展示了 ScriptableWizard
类中的一些常用接口的使用方法:
public class MyWizard : ScriptableWizard
{
// 字段会按顺序显示在窗口的字段区域中
public string text;
public Texture image;
// 在打开窗口和修改窗口中字段的值时调用
private void OnWizardUpdate()
{
Debug.Log("OnWizardUpdate");
// isValid控制Create和Other按钮是否可以点击
if (string.IsNullOrEmpty(text) || image == null)
isValid = false;
else
isValid = true;
// 显示在字段区域顶部(窗口标题下方)的帮助信息
helpString = "MyHelpString";
// 显示在字段区域底部的错误信息
errorString = "MyErrorString";
}
// 在点击了 Create 按钮时调用
private void OnWizardCreate()
{
Debug.Log("OnWizardCreate");
}
// 在点击了 Other 按钮时调用
private void OnWizardOtherButton()
{
Debug.Log("OnWizardOtherButton");
}
[MenuItem("MyMenuItems/DisplayMyWizard")]
private static void DisplayMyWizard()
{
// 参数分别设置 窗口标题、Create按钮名称 和 Other按钮名称
ScriptableWizard.DisplayWizard<MyWizard>("MyWizardTitle", "MyCreateButtonName", "MyOtherButtonName");
}
}
限制打开的窗口数量
ScriptableWizard
类创建的窗口不会自定限制打开的窗口数量,可以为打开窗口的菜单项方法添加校验方法来判断是否可以打开新的窗口。
下面的示例代码展示了如何限制只能打开一个 MyWizard 窗口:
public class MyWizard : ScriptableWizard
{
private static bool isOpen = false;
// 打开窗口时调用一次
private void Awake()
{
isOpen = true;
}
// 关闭窗口时调用一次
private void OnDestroy()
{
isOpen = false;
}
[MenuItem("MyMenuItems/DisplayMyWizard")]
private static void DisplayMyWizard()
{
ScriptableWizard.DisplayWizard<MyWizard>("MyWizardTitle");
}
[MenuItem("MyMenuItems/DisplayMyWizard", true)]
private static bool DisplayMyWizardValidate()
{
return !isOpen;
}
}
显示悬浮通知信息
ScriptableWizard.ShowNotification() API文档
ScriptableWizard
类的 ShowNotification()
成员方法可以在编辑器窗口的中心弹出半透明的悬浮通知消息,并在几秒后自动隐藏这个悬浮消息。 ShowNotification()
方法需要一个 GUIContent
对象作为参数,在 GUIContent
对象中可以设置通知中要显示的图片和文字信息。
下面的示例代码对 MyWizard
类的 OnWizardOtherButton()
方法进行了修改,在自定义编辑器窗口中设置了 text
和 image
字段的值后,点击“Other”按钮,就会在编辑器中弹出一个悬浮通知:
private void OnWizardOtherButton()
{
GUIContent content = new GUIContent(text, image);
ShowNotification(content);
}
EditorPrefs
保存编辑器中的自定义参数
EditorPrefs
类可用于在编辑器中存储数据,它的用法与 PlayerPrefs
相同,但是只能在编辑器中使用。
在自定义编辑器窗口中,默认情况下,每次重新打开窗口都会将窗口中的字段还原成脚本中编写的默认值。使用 EditorPrefs
类可以在窗口关闭时对数据进行保存,在窗口打开时还原保存的数据。
下面的示例代码中,修改了 MyWizard
类的 Awake()
和 OnDestroy()
两个方法,实现了对自定义数据的保存和还原:
// 打开窗口时调用一次
private void Awake()
{
// 当取不到Key对象的数据时,返回默认值,默认值可省略
text = EditorPrefs.GetString("MyUniqueKey", "MyDefaultValue");
}
// 关闭窗口时调用一次
private void OnDestroy()
{
EditorPrefs.SetString("MyUniqueKey", text);
}
EditorUtility
显示进度条
EditorUtility.DisplayProgressBar() API文档
使用 EditorUtility.DisplayProgressBar()
方法可以显示进度条,该方法需要两个字符串参数和一个浮点数参数,分别用于设置进度条窗口的标题、描述信息和百分比进度。使用 EditorUtility.ClearProgressBar()
方法可以关闭进度条。
下面的示例代码在 MyMenuItems 菜单栏中添加了一个名为 ShowMyProgressBar 的菜单项,点击该菜单项会显示出一个进度条,并在涨满后自动关闭。
[MenuItem("MyMenuItems/ShowMyProgressBar")]
private static void ShowMyProgressBar()
{
int num = 0;
while (num < 300)
{
num++;
EditorUtility.DisplayProgressBar("MyTitle", "MyMessage", (float)num / 300);
System.Threading.Thread.Sleep(5);
}
EditorUtility.ClearProgressBar();
}
EditorWindow
创建可停靠的自定义编辑器窗口
EditorWindow
类提供了用于创建可停靠的自定义编辑器窗口的编程接口。显示自定义窗口时,首先要通过 EditorWindow.GetWindow<T>()
方法获取到窗口对象,然后再调用窗口对象的 Show()
方法才能打开窗口。
下面的实例代码展示了自定义 EditorWindow 的简单用法:
public class MyEditorWindow : EditorWindow
{
private bool unfolded = true;
private Transform selected;
private void OnGUI()
{
selected = Selection.activeTransform;
if (selected != null)
{
// 判断窗口中的组件面板是否展开
unfolded = EditorGUILayout.InspectorTitlebar(unfolded, selected);
if (unfolded)
{
Undo.RecordObject(selected, "MyUndoName");
selected.name = EditorGUILayout.TextField("Name", selected.name);
// 绘制空行
EditorGUILayout.Space();
selected.position = EditorGUILayout.Vector3Field("Position", selected.position);
}
}
}
private void OnInspectorUpdate()
{
// 重新绘制
this.Repaint();
}
[MenuItem("MyMenuItems/ShowMyWindow")]
private static void ShowWindow()
{
// 获取自定义窗口的实例并显示窗口
GetWindow<MyEditorWindow>().Show();
}
}