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
==========================分割线==========================
如果大家有什么意见和建议,或者是有什么疑问,或者是有想看的知识点内容,都欢迎到评论区发上你们的评论。
最后我希望有更多人参与到插件开发的队伍里。也欢迎大家投稿。