编者按
本文已于作者 @Lawliet 授权转载,原载于知乎,如需转载请务必联系原作者。
引言
俗语说:工欲善其事,必先利其器。
一个好的工具能让你的工作进度加快不少。
在制作关卡时,很多时候会遇到同一个物体可能需要复制多份,然后分布在不同地方。如果 一个个复制太浪费时间了,而美术和策划又不会使用代码批量复制。这时候,就需要做一个批量工具来加快制作的效率了。
正文
首先来看一下我们今天要做的批量工具面板,并且可以根据输入的变量,批量的变更每个的位移,旋转和缩放:
知识要点:
- EditorWindow
- GUI/GUILayout/EditorGUI/EditorGUILayout
- Selection
- Undo
使用版本:
- Unity3D 5.3.3
目标:
- 学习创建 EditorWindow 面板,然后使用GUI等工具绘制面板,最后批量复制对象。
整个插件的结构:
和上一篇教程一样,在Editor目录下创建我们的批处理面板脚本————BatchingLiteWindow.cs,然后继承EditorWindow。
EditorWindow是所有编辑器面板的基类,绘制面板必须要继承它。
然后我们使用MenuItem和静态函数添加启动面板的菜单。
MenuItem 的使用方法请看上一篇教程的最后部分。
public class BatchingLiteWindow : EditorWindow { [MenuItem("Tools/BatchingLite")] public static void ShowWindow() { //GetWindow函数的意思是创建一个面板 //类型为BatchingLiteWindow //第一个参数是面板的标题 EditorWindow.GetWindow("Batcking"); } }
然后我们定义几个变量,用于后面使用。
////// 位移增量 /// private Vector3 _position; ////// 旋转增量 /// private Vector3 _rotation; ////// 缩放增量 /// private Vector3 _scale; ////// 复制的数量 /// private int _number;
好了,接下来,就是本文的重点之一,绘制面板了。 首先,我们定义一个函数叫OnGUI,返回值为 void。
说到 OnGUI,用过老版本 Unity 引擎的朋友应该很清楚了。这是一套 Unity 最早的 UI 引擎。这套 UI 系统有别于现在流行的UGUI和NGUI,是一套imGUI(Immediate Mode GUI)。如果需要深入展开 imGUI 的原理来讲,那么可能需要好几个篇章,所以在此只讲一下怎么使用。 如果有兴趣的朋友可以上网搜索资料,或者到看这个回答 如何用 C++ 从零编写 GUI? - 回答作者: 文刀秋二
首先,绘制面板,一定要使用这套GUI,并且需要在特定函数内使用,例如OnGUI、OnSceneGUI、OnInspectorGUI、OnHeaderGUI、OnPreviewGUI等。 其次,imGUI 其中一个特性是不保存状态的,例如UGUI和NGUI的按钮类都会保存按钮当前是按下状态还是松开状态,可imGUI的按钮是不保存这个的。
imGUI有四个绘制类。分别是GUI、GUILayout、EditorGUI、EditorGUILayout,他们有相同的地方和不同的地方。
- GUI:多用与应用/游戏内绘制 UI。(编辑器绘制也可使用)
- GUILayout:GUI的功能上增加了布局的功能。
- EditorGUI:用于编辑器内绘制 UI。(仅限于编辑器内使用)
- EditorGUILayout:EditorGUI 的功能上增加了布局的功能。
好了,接下就开始写绘制面板的逻辑了。
void OnGUI() { //使用Vector3Field方法绘制 Vector3的输入框,第一个为输入框的标签(显示的名字),第二个参数需要传入需要显示的Vector3值。 //返回值为一个Vector3,当没有修改的时候,这个值为原来的值,当有修改的时候,这个返回值就是修改后的值。 //例如,把返回值赋予给_position,这样,输入框有修改的时候,_position能够拿到最新的值。 _position = EditorGUILayout.Vector3Field("Position", _position); _rotation = EditorGUILayout.Vector3Field("Rotation", _rotation); _scale = EditorGUILayout.Vector3Field("Scale", _scale); //Space的作用是空一行 EditorGUILayout.Space(); //然后使用IntField方法绘制一个int类型的输入框,使用与Vector3相似 //由于复制的数量不能为负数,所以我们要限制一下修改后的数值 _number = Mathf.Max(EditorGUILayout.IntField("Number", _number), 0); EditorGUILayout.Space(); //BeginHorizontal方法和EndHorizontal是成对存在的,然后他们的作用是水平布局,在两个函数内绘制的UI会限制在一个水平位置。 //相似的方法还有BeginVertical和EndVertical,是垂直布局。 EditorGUILayout.BeginHorizontal(); //绘制一个Generate Button,这里使用GUILayout而不使用EditorGUILayout是因为EditorGUILayout没有Button(不知道原因)。 //Button方法第一个参数是button显示的label。 //返回值为Button是否为点击, if (GUILayout.Button("Generate")) { Generate(); } //这里缓存Cancel按钮的状态,在EndHorizontal之后再调用Cancel方法。 bool isCancel = GUILayout.Button("Cancel"); EditorGUILayout.EndHorizontal(); if (isCancel) { Cancel(); } }
绘制完面板,然后就开始写复制部分的逻辑:
////// 生成复制对象 /// private void Generate() { //上一篇有介绍过Selection.activeGameObject是选中的对象,然后Selection.gameObjects是多选时,所有选中的对象。 //因为有可能是多个对象同时复制,所以使用选中对象组 GameObject[] selectGameObjects = Selection.gameObjects; int len = selectGameObjects.Length; for (int i = 0; i < len; i++) { GameObject selectGameObject = selectGameObjects[i]; for (int j = 0; j < _number; j++) { //根据选中对象,实例化对象,然后根据索引和增量,设置移动、旋转、缩放 GameObject gameObject = GameObject.Instantiate(selectGameObject); gameObject.transform.SetParent(selectGameObject.transform.parent); gameObject.transform.localPosition = selectGameObject.transform.localPosition + _position * j; gameObject.transform.localRotation = selectGameObject.transform.localRotation * Quaternion.Euler(_rotation * j); gameObject.transform.localScale = selectGameObject.transform.localScale + _scale * j; gameObject.name = selectGameObject.name; //Undo是Unity3d用于设置步骤,执行/撤销等 //RegisterCreatedObjectUndo是注册一个新创建的对象的步骤,然后名字为“Batching Create GameObject”,用于 Undo.RegisterCreatedObjectUndo(gameObject, "Batching Create GameObject"); } } }
最后是Cancel方法
////// 取消操作 /// 同ctrl + z /// private void Cancel() { //PerformUndo作用跟ctrl + z一样 Undo.PerformUndo(); }
解释一下,为啥 OnGUI 时,Cancel为什么要EditorGUILayout.EndHorizontal之后才调用,这是因为Cancel会导致之前EditorGUILayout.StartHorizontal的标记没了,然后执行EditorGUILayout.EndHorizontal会报错。
最后可以试一下面板的效果:
增加一个彩蛋,做了一个100*100*100的正方体矩阵然后直接占满 8g 内存.......
后记
如果大家有什么意见和建议,或者是有什么疑问,或者是有想看的知识点内容,都欢迎到评论区发上你们的评论。
最后我希望有更多人参与到插件开发的队伍里。也欢迎大家投稿。
- 源码:L-Lawliet/UnityEditorTutorial
- 题图来自于Gratisography | Free High Resolution Pictures
- 欢迎分享本文
- QQ群:234204968
封面配图爆炸!
@Oncle:四里