Unity3D插件开发教程(二):制作批处理工具
文章来源:Unity3D插件开发教程(二):制作批处理工具 - 知乎 (zhihu.com)
声明:
- 题图来自于Gratisography | Free High Resolution Pictures
- 欢迎分享本文
- 本文未经允许不能以任何形式转载。
俗语说:工欲善其事,必先利其器。
一个好的工具能让你的工作进度加快不少。
在制作关卡时,很多时候会遇到同一个物体可能需要复制多份,然后分布在不同地方。如果 一个个复制太浪费时间了,而美术和策划又不会使用代码批量复制。这时候,就需要做一个批量工具来加快制作的效率了。
首先来看一下我们今天要做的批量工具面板,并且可以根据输入的变量,批量的变更每个的位移,旋转和缩放:
知识要点:
- 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<BatchingLiteWindow>("Batcking");}
}
然后我们定义几个变量,用于后面使用。
/// <summary>
/// 位移增量
/// </summary>
private Vector3 _position;/// <summary>
/// 旋转增量
/// </summary>
private Vector3 _rotation;/// <summary>
/// 缩放增量
/// </summary>
private Vector3 _scale;/// <summary>
/// 复制的数量
/// </summary>
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();}
}
绘制完面板,然后就开始写复制部分的逻辑
/// <summary>
/// 生成复制对象
/// </summary>
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<GameObject>(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方法
/// <summary>
/// 取消操作
/// 同ctrl + z
/// </summary>
private void Cancel()
{//PerformUndo作用跟ctrl + z一样Undo.PerformUndo();
}
解释一下,为啥OnGUI时, Cancel为什么要 EditorGUILayout.EndHorizontal之后才调用,这是因为 Cancel会导致之前 EditorGUILayout.StartHorizontal的标记没了,然后执行 EditorGUILayout.EndHorizontal会报错。
最后可以试一下面板的效果:
增加一个彩蛋,做了一个100*100*100的正方体矩阵然后直接占满8g内存.......
源码:L-Lawliet/UnityEditorTutorial
==========================分割线==========================
如果大家有什么意见和建议,或者是有什么疑问,或者是有想看的知识点内容,都欢迎到评论区发上你们的评论。
最后我希望有更多人参与到插件开发的队伍里。也欢迎大家投稿。