正所谓:人靠衣装,佛靠金装。学会插件开发,成就大神人生。
- 欢迎关注公众号:雷潮课堂
编辑器开发
一、课程内容
1-1本课程目标
1-2插件开发的起因
1-3插件的作用
二、菜单项制作
2-1菜单项(MenuItem)
2-2组件菜单(AddComponentMenu)
2-3上下文菜单(ContextMenu)
2-4必要组件菜单(RequireComponent)
三、自定义InsPector界面
3-1属性相关标识(HideInInspector,SerializeField,NonSerialized,Serializable)
3-2自定义界面属性 (CustomEditor)
3-3自定序列化属性字段 (PropertyField)
四、 自定义操作界面(Window)
4-1Windows窗口
4-2界面的生命周期(事件)
4-3编辑器下的UI控件
文本颜色字段,标签字段,数字输入字段,滑动条,位置大小输入字段,选择字段,标签、层、对象选择字段,单选字段,分组字段,其他字段
1-1本课程目标
学会自定义编辑器,开发一款属于自己的编辑器插件
1-2插件开发的起因
如果一个模型师想要在Unity中查看效果,但是她又是程序小白,作为公司一员应该给到她非常好帮助,比如制作一套工具集合(插件),让她根本离不开你。
1-3插件的作用
帮助企业建立技术护城河,提高开发效率,减少沟通成本,减少工作。
2-1菜单项
Asset目录下新建一个Editor文件夹,新建MyEditor脚本
using UnityEngine;
using UnityEditor; // 引用命名空间
public class MyEditor : Editor // 继承Editor类
{
// 添加工具栏并设置创建方块方法 (false)显示出来
[MenuItem("工具/创建方块, &_f",false)] // % Ctrl & Alt # Shift
public static void CreatCube()
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = "方块";
}
// 用来处理编辑器的问题(Update)
private void OnSceneGUI()
{
if (Input.GetKeyDown(KeyCode.F) && Input.GetKeyDown(KeyCode.LeftAlt))
{
CreatCube();
}
}
-
效果图:
-
注意
OnSceneGUI方法中主要使用Handles系列来绘制可视化调试控件,比如线、盒子、球体、文本等(可以自行操作)
private void OnSceneGUI()
{
Handles.color = Color.green;
}
- 2-1-1多级菜单:
using UnityEngine;
using UnityEditor; // 引用命名空间
public class MyEditor : Editor // 继承Editor类
{
public static GameObject cube;
// 添加工具栏并设置创建方块方法 (false)显示出来
[MenuItem("工具/创建方块, &_f",false)] // # Ctrl & Alt
public static void CreatCube()
{
cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.name = "方块";
}
// 添加工具栏并设置创建方块方法 (false)显示出来
[MenuItem("工具/销毁方块, &_G", false)] // # 表示Ctrl & 表示Alt
public static void DestroyCube()
{
if (cube == null)
{
cube = GameObject.Find("方块");
}
Editor.Destroy(cube);
}
// 用来处理编辑器的问题(Scene绘制图形方法类似Update)
private void OnSceneGUI()
{
Handles.color = Color.green;
if (Input.GetKeyDown(KeyCode.F) && Input.GetKeyDown(KeyCode.LeftAlt))
{
CreatCube();
}
if (Input.GetKeyDown(KeyCode.G) && Input.GetKeyDown(KeyCode.LeftAlt))
{
DestroyCube();
}
}
}
2-2组件菜单(AddComponentMenu)
using UnityEngine;
//给Component菜单下增加One子菜单,并将TestOne类添加到对象上
[AddComponentMenu("One",1)]
public class TestOne : MonoBehaviour
{
}
2-3上下文菜单(ContextMenu)
在面板中右键出现的菜单
public class TestOne : MonoBehaviour
{
[ContextMenu("测试")]
public void Test()
{
Debug.Log("123");
}
}
2-4必要组件菜单(RequireComponent)
using UnityEngine;
[RequireComponent(typeof(Rigidbody))] // 添加组件必须要加上刚体
//给Component菜单下增加One子菜单,并将TestOne类添加到对象上
[AddComponentMenu("One",1)]
public class TestOne : MonoBehaviour
{
}
3-1属性相关标识
- 隐藏属性显示 (HideInInspector)
- 不显示的属性(SerializeField)
- 关键字序列化(Serializable) (针对类,结构体,枚举,委托有效)
- 不会被序列(NonSerialized)
using System;
using UnityEngine;
[Serializable] // 可以使类显示在视图面板
public enum MyColor
{
red,
green,
blue
}
public class TestOne : MonoBehaviour
{
[HideInInspector]
public int age; // 将显示的属性隐藏
[SerializeField]
private float height; // 显示隐藏的属性
[NonSerialized]
public Vector3 v = new Vector3(10, 0, 0); // 防止变量序列化
public MyColor c;
}
3-2自定义界面属性
- 自定义显示面板(CustomEditor)
我们添加系统组件的时候,发现面板很好看,而脚本做不到这种效果。这里就需要自定义显示面板,如下图所示:
using UnityEngine;
using UnityEditor;// 引用命名空间
// CustomEditor是对应标识的组件脚本
[CustomEditor(typeof(BoxCollider))] // 设置自定义BoxCollider的内容
public class MyInsPectorTest : Editor // 继承Editor类
{
private bool b;
private string s = "";
private string m;
// 重写Editor 绘制面板图形界面方法
public override void OnInspectorGUI()
{
// 当我们使用自动布局技术的时候,就不要混合使用GUI.Lable,以免产生不必要的约束问题
GUILayout.Label("雷潮");
b = GUILayout.Toggle(b,"关闭音效");
GUILayout.Label("请输入账号");
s = GUILayout.TextField(s);
GUILayout.Label("请输入密码");
m = GUILayout.PasswordField(m,'*'); // 后面是占位符,将密码字符更替为*
if (GUILayout.Button("登录"))
{
if (s == "leichao" && m == "123456")
{
Debug.Log("登录成功");
}
else
{
Debug.Log("账号或者密码错误");
}
}
}
}
3-3自定序列化属性字段 (PropertyField)
命名空间在UnityEditor
SerializedObject 序列化对象 请点击查看序列化对象类
SerializedProperty 序列化属性 请点击查看序列化属性类
using System;
using UnityEngine;
[Serializable] // 可以使类显示在视图面板
public enum MyColor
{
red,
green,
blue
}
public class TestOne : MonoBehaviour
{
public int age;
public float height;
public Vector3 v = new Vector3(10, 0, 0);
public MyColor c;
}
using UnityEditor;
[CustomEditor(typeof(TestOne))]
public class Test1 : Editor
{
public SerializedObject mObj;
public SerializedProperty mAge;
public SerializedProperty mHeight;
public SerializedProperty mVector;
public SerializedProperty mColor;
private void OnEnable()
{
mObj = new SerializedObject(target); // 创建一个目标,即挂载脚本物体
mAge = this.mObj.FindProperty("age");
mHeight = this.mObj.FindProperty("height");
mVector = this.mObj.FindProperty("v");
mColor = this.mObj.FindProperty("c");
}
public override void OnInspectorGUI()
{
this.mObj.Update();
EditorGUILayout.PropertyField(this.mAge);
EditorGUILayout.PropertyField(this.mHeight);
EditorGUILayout.PropertyField(this.mVector);
EditorGUILayout.PropertyField(this.mColor, true);
}
}
4.自定义窗口菜单
4-1自定义窗口
using UnityEngine;
using UnityEditor;
public class MyWindow :EditorWindow
{
private bool b;
private string s;
private string m;
private Color c = Color.white;
private string t;
[MenuItem("Tools/Window")]
public static void ShowW()
{
EditorWindow.GetWindow<MyWindow>().Show();
}
private void OnGUI()
{
// 编辑MyWindow
EditorGUILayout.LabelField("雷潮邀请你输入");
EditorGUILayout.Space(); // 隔开一行
EditorGUILayout.LabelField("请输入账号");
s = EditorGUILayout.TextField(s);
EditorGUILayout.SelectableLabel("请输入密码");
s = EditorGUILayout.PasswordField(s);
EditorGUILayout.Space(); // 隔开一行
EditorGUILayout.LabelField("请选择颜色");
this.c = EditorGUILayout.ColorField(c);
EditorGUILayout.Space(); // 隔开一行
EditorGUILayout.LabelField("请输入你要提交的信息");
this.t = EditorGUILayout.TextArea(this.t); // 文本区域可以空格换行输入
b = GUILayout.Toggle(b, "记住账号");
b = GUILayout.Toggle(b, "记住密码");
}
}
- 另一种显示窗口的方法
[MenuItem("Tools/Window")]
public static void ShowW()
{
// 无法关闭
MyWindow.CreateInstance<MyWindow>().ShowPopup();
}
- 填充更多内容
4-2界面的生命周期(事件)
public class WindowEditorLife : EditorWindow
{
[MenuItem("Window/MyWindow")]
public static void CreatWindow()
{
EditorWindow.GetWindow<WIndowEditorLife>().Show();
}
private void OnEnable() // 当窗口可用时
{
Debug.Log("OnEnable ==== 1");
}
private void OnFocus() // 当鼠标在窗口中,获得焦点时,调用一次
{
Debug.Log("OnFocus ==== 2");
}
private void Update()
{
Debug.Log("所有可见窗口上每帧调用");
}
private void OnLostFocus() // 失去焦点时调用
{
Debug.Log("OnLostFocus");
}
private void OnGUI() // 绘制图形
{
Debug.Log("绘制图形");
if (GUILayout.Button("关闭窗口"))
{
this.Close();
}
}
private void OnDisable() // 窗口不可用的时候调用
{
Debug.Log("OnDisable");
}
private void OnDestroy() // 销毁的时候调用
{
Debug.Log("OnDestroy");
}
private void OnHierarchyChange() // 当场景层次面板发生改变的时候调用
{
Debug.Log("OnHierarchyChange");
}
private void OnValidate() // 当脚本被加载或者检视面板值备修改的时候调用
{
Debug.Log("OnValidate");
}
private void OnProjectChange() // 当工程目录更新时调用(有资源变动)
{
Debug.Log("OnProjectChange");
}
private void OnSelectionChange() // 当在窗口选中某个物体时
{
Debug.Log("OnSelectionChange");
}
private void OnInspectorUpdate() // 窗口的更新调用,每秒10次
{
Debug.Log("OnInspectorUpdate");
}
4-3编辑器下的UI控件
- 滚动条
EditorGUILayout.Slider(…) 浮点数滚动条
EditorGUILayout.IntSlider(…)整数滚动条
EditorGUILayout.MinMaxSlider(…) 最小最大滚动条
number = EditorGUILayout.Slider(number, 0, 100);
number1 = EditorGUILayout.IntSlider(number1, 0, 100);
EditorGUILayout.MinMaxSlider(ref number2, ref number3, 0, 100);
- 输入框
EditorGUILayout.TextField(string) 文本输入框
EditorGUILayout.TextArea(string); 可换行的文本输入框
EditorGUILayout.PasswordField(string); 文本输入框
EditorGUILayout.ColorField(Color); 颜色输入框
EditorGUILayout.IntField(int); 整数输入
EditorGUILayout.FloatField(float); 浮点数输入
EditorGUILayout.CurveField(string,objec) // 动画曲线输入
EditorGUILayout.ObjectField(objec,typeof()) // 获取选择的物体,后面可以限制选择的物体类型
EditorGUILayout.InspectorTitlebar(bool,object) // 选择的物体放在面板
s1 = EditorGUILayout.TextField(s1); // 文本输入
s1 = EditorGUILayout.TextArea(s1); // 换行输入
s1 = EditorGUILayout.PasswordField(s1); // 密码文本输入
c1 = EditorGUILayout.ColorField(c1);
a1 = EditorGUILayout.CurveField("动画",a1);
// 选择选中的物体,类型就是后面的参数typeof(Transform)
o1 = EditorGUILayout.ObjectField(o1, typeof(Transform));
if (o1 != null)
{
b = EditorGUILayout.InspectorTitlebar(b, o1); // 选择的物体放在面板
Debug.Log(b);
}
- 向量
EditorGUILayout.RectField(Rect); 矩阵
EditorGUILayout.BoundsField(Bounds);XYZ距离
EditorGUILayout.Vector2Field(string, Vector2) 二维坐标
EditorGUILayout.Vector3Field(string,Vector3); 三维坐标
EditorGuILayout.Vector4Field(string,Vector4); 三维坐标(表示旋转)(增加了W)
(
r = EditorGUILayout.RectField(r); // X,Y,width,height
b = EditorGUILayout.BoundsField(b); // 中心,大小
v2 = EditorGUILayout.Vector2Field("平面位置", v2);
v3 = EditorGUILayout.Vector3Field("3D位置", v3);
v4 = EditorGUILayout.Vector4Field("四元数", v4);
- 下拉菜单
EditorGUILayout.Popup(int, string[]); 字符串选择,返回字符串数组下标
EditorGUILayout.IntPopup(int,int[],string[]) 字符串选择,返回字符串对应下标的整形数组值
EditorGUILayout.EnumPopup(Enum) 枚举选择,返回枚举
Enum e = People.man;
enum People
{
man,
woman
}
// 下拉菜单
index = EditorGUILayout.Popup(index, strs);
index = EditorGUILayout.IntPopup(index, strs, numbers);
e = EditorGUILayout.EnumPopup(e);
Debug.Log(e);
- 标签、层、对象
EditorGUILayout.TagField(this.t);
EditorGUILayout.LayerField(this.layer);
EditorGUILayout.ObjectField(this.mObj, typeof(BoxCollider));
- 展开
EditorGUILayout.Foldout(bool,string)
s1 = "Player";
s1 = EditorGUILayout.TagField(s1);
// Layer
number1 = EditorGUILayout.LayerField(number1);
b1 = EditorGUILayout.Foldout(b1, "点击打开"); // 展开控件
if (b1)
{
number = EditorGUILayout.Slider(number, 0, 100);
o1 = EditorGUILayout.ObjectField(o1 as UnityEngine.Object, typeof(GameObject)); // 选中的游戏物体菜单
}
else
{
Debug.Log("没有任何物体");
}
- 布局分组
- 水平分组
EditorGUILayout.BeginHorizontal();
EditorGUILayout.EndHorizontal();- 垂直分组
EditorGUILayout.BeginVertical();
EditorGUILayout.EndVertical();- Toggle分组
EditorGUILayout.BeginToggleGroup(“选择分组”,this.mbool);
EditorGUILayout.EndToggleGroup();- 滚动分组
EditorGUILayout.BeginScrollView(this.mPos);
EditorGUILayout.EndScrollView();
b1 = EditorGUILayout.BeginToggleGroup("灵敏度设置", b1);
b2 = EditorGUILayout.Toggle("鼠标设置",b2);
b3 = EditorGUILayout.Toggle("鼠标设置", b3);
b4 = EditorGUILayout.Toggle("鼠标设置", b4);
EditorGUILayout.EndToggleGroup();
####5总结:
顶部编辑器菜单:引用using UnityEditor; 使用[MenuItem()];
Inspector面板的处理:继承Editor,重写虚方法:OnInspectorGUI(),并调用(父类的此方法)base.OnInspectorGUI()
窗口的面板:继承:EditorWindow, 使用EditorWindow.GetWindow(); 调用窗口的Show()方法。
- 欢迎购买本课程:
腾讯课堂
网易云课堂