半小时学会Unity扩展开发一之编辑器UI篇

发表于2015-11-17
评论1 8k浏览
Unity提供了很好用的UGUI方便开发者搭建游戏UI。可是众所周知游戏开发过程中不仅需要做游戏开发,还需要扩展Unity Editor,快速构建好用的游戏工具,提升游戏开发效率。可是不得不吐槽Unity的忽视,Unity提供的编辑器UI都比较老旧,导致开发游戏工具困难,界面不仅难看,还不方便使用。
最近在做Unity扩展工具相关工作。顺便撸一下Unity扩展开发的一些UI知识。没有编辑器开发经验的人,通过本文可以迅速入门,老手们则可以借此温故下基本知识。

菜单实现

开始

最常见的实现菜单方式是在Toolbar上添加一个菜单。比如,一个常见的应用场景是创建一个名为“Tools”的Menu来提供一些团队常用的功能。根据Unity提供的API,可以快速写出如下代码:

[MenuItem("Tools/MenuItem1")]private static void CreateMenuItem1(){Debug.Log("Click MenuItem1");}[MenuItem("Tools/MenuItem2")]private static void CreateMenuItem2(){Debug.Log("Click MenuItem2");}

实现效果如下图:

从上面的代码知道Unity添加菜单是通过MenuItem属性完成的。且“MenuItem”传入的参数是菜单项的路径。

那么,除了Toolbar上的菜单,是否可以通过在MenuItem上传入其他路径实现诸如上下文菜单?Asset下的菜单?Inspector面板上Component下的菜单呢?
答案是可以的!通过传入不同参数,即可以实现不同位置的菜单项。具体如下例所示。
(1)Hierarchy的上下文菜单项
代码:

[MenuItem("GameObject/Hierarchy MenuItem",false,0)]static void CreateHierarchyMenuItem(){Debug.Log("Click Hierarchy MenuItem");}

效果(这里为什么使用false,0参数,会在后续解释):

(2)Project的上下文菜单项
代码:

[MenuItem("Assets/Project MenuItem")]static void CreateProjectMenuItem(){Debug.Log("Click Project MenuItem");}

效果:

3.Component下添加菜单项
代码:

[MenuItem("CONTEXT/Transform/Transform MenuItem")]private static void TransformMenu(){Debug.Log("Click Transform MenuItem");}

效果:

往前迈出一小步

通过以上代码,我们基本可以在Unity上创建出大部分应用场景下的菜单了。但是这里的菜单始终是一种显示、可用状态,如何实现菜单项在指定条件下变灰、不可用呢?
上文中,我们已经瞥见了MenuItem可以接受一个false参数,正是这个参数,实现菜单项的验证功能,完成“显示、变灰”状态的转换。
以下代码将实现上文创建的Tools/MenuItem1菜单项变灰。

[MenuItem("Tools/MenuItem1",false,0)]private static void CreateMenuItem1(){Debug.Log("Click MenuItem1");}//"Tools/MenuItem1"需要和上面的一样;true表示该方法是验证方法[MenuItem("Tools/MenuItem1", true, 0)]private static bool CreateMenuItem1Validate(){//返回false,表示当前菜单项不可见return false;}

效果如下:

还有点瑕疵

总是感觉还差了点什么,对,当一个菜单下有许多菜单项时,你一定希望能够对自己添加的菜单项进行分组排序。那么,如何实现菜单项的分组排序功能呢?
MenuItem属性的第3个参数就是用来实现该功能的。通过这个参数可以给菜单项设置优先级。设置优先级的规则如下:

  • Unity根据指定的优先级值大小进行排序,优先级小的在前面,优先级大的在后面;
  • 如果两个菜单项之间的优先级相差超过10(不包括10),会自动在两个菜单项之间插入一个分隔符(分组功能);
  • Unity现有菜单项的优先级Unity没有公布出来,可以尝试通过其他工具(如ILSpy)查看Unity源码得到;

实现方式如下代码所示:

[MenuItem("Tools/MenuItem1",false,12)]//参数为12private static void CreateMenuItem1(){Debug.Log("Click MenuItem1");}[MenuItem("Tools/MenuItem2",false,1)]//参数为1,所以会显示在前面private static void CreateMenuItem2(){Debug.Log("Click MenuItem2");}

效果如下图所示:

是否完整了?

通过以上实现,基本上能够搞定Unity中定义菜单的显示问题了。那么,问题又来了,如果我们想给菜单项方法传递参数,该怎么办呢?
不用担心!Unity给我们提供了第四个参数MenuCommand,通过该参数,能够给菜单项的方法传递参数,一种可能的用法如下代码所示。

[MenuItem("CONTEXT/Transform/Transform MenuItem")]private static void TransformMenu(MenuCommand menuCommand){//得到当前选中的对象类型TransformTransform transform = menuCommand.context as Transform;Debug.Log(transform.localPosition);}

通过以上几步,一个完整的Unity菜单项就创建出来了,应该可以满足大部分现有需求了吧~~~~~

常用UI实现

掌握了菜单的用法,怎能不了解Unity编辑器那些事儿呢。无论写什么样的Unity扩展编辑器,都不可避免的需要用到基本的UI元素,如Button、Label、列表等等。
下文将介绍在扩展编辑器时,会用到的一些常用控件及其使用方法。

最基本的控件

Unity扩展编辑器的所有控件几乎都在GUI/GUILayout和EditorGUI/EditorGUILayout这两组类中。
其中GUI/EditorGUI类中的控件不支持自动布局,而GUILayout/EditorGUILayout中的类支持自动布局。具体实现中,可以按需选择不同的类,甚至可以混合使用两个类实现需要的控件和布局。
从这四个类的API中可以看出,包含多种控件,如IntField(输入int的文本框)、LabelField(显示文本)、Popup(弹出选择框)、Toggle等,在构建编辑器UI时,可以按需选择使用。

以下将通过一个小例子介绍如何使用基本控件。
本例是通过选中一个复选框来显示一个Name列表。首先,在界面上添加一个复选框。

public class AddEditor : EditorWindow{private List<string> NameList;// 所有窗口显示都在OnGUI里面实现private bool isChoosed = false;// 所有窗口显示都在OnGUI里面实现private void OnGUI(){ EditorGUILayout.LabelField("点击显示NameList:", GUILayout.Width(150)); //显示复选框 isChoosed = EditorGUILayout.ToggleLeft("显示NameList", isChoosed);}}

以上代码显示效果如下:

如图所示,默认情况下,Label和复选框不在一行上,如果想界面更美观,让它们显示在一行上,该怎么办呢?----答案是加入布局元素。

private void OnGUI(){ //Begin和End之间的会被布局到一行上 EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("点击显示NameList:", GUILayout.Width(150)); //isChoosed存储复选框是否被选中 isChoosed = EditorGUILayout.ToggleLeft("显示NameList", isChoosed); EditorGUILayout.EndHorizontal();}

效果如下:

在具体实现时,有多种布局可以选择使用,大致如下:

  • GUILayout.BeginArea()/GUILayout.EndArea():定义一个区域块
  • GUILayout.BeginHorizontal()/GUILayout.EndHorizontal():中间定义的所有控件水平排列  
  • GUILayout.BeginVertical()/GUILayout.EndVertical():中间定义的所有空间垂直排列
  • GUILayoutOptions:一系列布局方式的封装,如最小宽度、最大高度等,如上面代码中的“GUILayout.Width(150)”
  • EditorGUILayout.BeginScrollView/EditorGUILayout.EndScrollView():滚动条区域

通过各种布局方法的配置使用,可以合理安排界面局部,让界面更加整洁。

控制好界面布局后,我们需要响应复选框选中事件,并实现展现NameList功能。
Unity在实现Editor UI时,并没有使用常见的MVC模式,Unity中所有的刷新操作都在OnGUI中完成,所以对于复选框、按钮等事件处理也是在OnGUI刷新过程中完成的。具体如下代码所示。

private void OnGUI(){//Begin和End之间的会被布局到一行上EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("点击显示NameList:", GUILayout.Width(150));//isChoosed存储复选框是否被选中isChoosed = EditorGUILayout.ToggleLeft("显示NameList", isChoosed);EditorGUILayout.EndHorizontal();if (isChoosed){//显示滚动条_scrollVector = EditorGUILayout.BeginScrollView(_scrollVector, GUIStyle.none, GUILayout.ExpandWidth(true),GUILayout.ExpandHeight(true));//显示列表foreach (string s in NameList){EditorGUILayout.LabelField(s);//增加空隙EditorGUILayout.Space();}EditorGUILayout.EndScrollView();}}

此时,我们在勾选上复选框后便能够正确显示出NameList了。

此时,似乎我们的任务已经完成了,但是追求完美的人可能觉得这个列表太难看了!没有缩进,字体太小,颜色不好看....

好吧,接着下一步操作,设置控件的样式。
设置控件样式是通过类GUIStyle来完成的。本例中的设置代码如下所示:

private GUIStyle style_check;//样式类private void InitStyle(){//按需设置样式style_check = new GUIStyle(GUI.skin.label);style_check.margin.top = 5;style_check.margin.left = 60;style_check.fontSize = 12;style_check.normal.textColor = new Color(255,0,0,1);style_check.fontStyle = FontStyle.Italic;}//设置控件样式GUILayout.Label(s,style_check,GUILayout.Width(150),GUILayout.Height(20));

通过这步样式设置,最终我们得到的列表如下所示:

哈,到这里,介绍的扩展编辑器UI知识包括:使用控件;调整布局;给控件加事件响应、逻辑操作;美化UI界面方法。基本上一个扩展编辑器的UI实现就包括这么多了~~~

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引