编辑器拓展介绍
常见的Unity编辑器拓展使用主要是创建自定义的窗体进行使用,包括剧情编辑器,动画编辑器,技能编辑器等等。最常使用的就是 EditorGUI,EditorGUILayout,GUI。
这个三个的区别就是 前两者只能编辑器下使用,而GUI在运行时候和编辑器下都可以使用。同时EditorGUI和GUI是手动布局,需要我们自己计算布局,而EditorGUILayout是自动布局。
创建一个新的窗口
当然技能编辑器之前也说了的是一个新的窗口,那么需要新建一个Editor文件夹,因为所有编辑器代码全部需要放在Editor文件夹下。然后新建一个类叫
SkillEditorWindow,继承自EditorWindow。包含一个公共静态方法用于创建窗口,伪代码如下(因为是手撸的很多地方格式有问题)。
using UnityEngine;
using UnityEditor;
public SkillEditorWindow : EditorWindow
{
public static SkillEditorWindow window;
[MenuItem("Tools/Skill")]
public static void OpenWindow()
{
window = GetWindow<SkillEditorWindow>();
window.titleContent = new GUIContent("技能编辑器");
}
}
那么这样你就可以得到一个技能编辑器的窗体框了。如何打开,经常使用的有两种打开方式,一种是如我代码上标注的特性,他会在Unity上方的File,Edit那一排工具里面创建一个Tools/Skill的路径,记住这里只能传二级路径以上不能直接是Tools根路径。
我采用的是第二个创建方式,绑定在角色预制件上,然后通过拓展角色的Inspector面板进行打开。代码如下:
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(RoleManager))]
public class SkillMangerInspector : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("打开技能编辑器"))
{
RoleManager role = target as RoleManager;
SkillEditorWindow.OpenWindow(role);
}
}
}
这段代码中的RoleManager是我的角色控制类,也是这套技能编辑器与其对应的框架中的角色管理类是挂载到角色身上的,大家可以创建一个空类,对编辑器开发没有任何影响,CustomEditor特性的意思就是 把这个编辑器界面类与一个对应的Mono类的进行绑定,在这里面进行重载Inspector面板,这样我们可以看到可以通过上面的按钮去打开我们的技能编辑器了。
布局问题
我相信希望想学习技能编辑器的小伙伴都是写过编辑器拓展的,并且可能最常见使用的就是EditorGUILayout,因为这玩意儿使用一个BeginVertical()或者BeginHoriztal()就可以轻轻松松的构造出布局,但是技能编辑器却不能使用这种自动布局,因为其中包含的时间轴,是无法通过自动布局来获取运算的,所以需要我们手动去布局整个编辑器界面。
Unity的布局是基于Rect的,Rect是个2维矩形,在编辑器下它的原点在左上角,向屏幕右方向是X正方向,向屏幕下方向是Y正方向,在运行时候它的原点也是在左上角,向屏幕右方向是X正方向,向屏幕上方是Y正方向。
那么我的布局大概就是如上,其实就是整个屏幕本身就是一个大矩形,只不过用很多的小矩形给它划分成了一块一块的。
Rect基本参数就是x,y,width,height。首先来谈坐标,在编辑器窗口里面有个隐含的规则,相对布局。所有你可见的类似矩形的区域其实都可以理解成使用 GUILayout.BeginArea(Rect) 和 GUILayout.EndArea() 去构造了一块儿矩形区域,那么我们的窗体一开始创建出来就是构造了个大的Rect,这个大的可以通过窗体对象window.position去获取出来。这个相对布局是什么意思,就是你在这个Rect作用域内的坐标是以这个Rect为标准的,左上角为原点,向右为X正方向,向下为Y正方向,那么读者可以自行尝试去设置几个不同的位置看看是不是相对布局,这样理解会更为深刻。
有了上面这个很重要的知识,那么我们就可以去构造出一个布局类,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 技能编辑器布局设计
///
/// | HeadWin |TrackWin |
/// ---- |------------------------------| ----- -----
/// |设置/标题| 播放按钮| 数字 | | <-- timeBtnsHeght |
/// |---------|---------|(TimeLine)| ----- |
/// Action |添加/删除|播放速度 | 画线 | | |
/// Win | Action |播放进度 | | | <-- playSpeedHeght | <-- WindowHeight
/// |---------|---------|----------| ----- |
/// | Actions | | | |
/// | | 头部区域| 轨道区域 | |
/// ------|---------| | | |
/// | | | | |
///Inspector|Inspector| | | |
/// Win | | | | |
/// ------|------------------------------| -----
/// | <---- WindowWidth ----> |
/// </summary>
public class SkillEditorGuIStyles
{
private float inspectorWidth = 1 / 6f;
private float headerWidth = 1 / 6f;
private float trackWidth = 2/3f;
private float actionsHeight = 1 / 3f;
private float inspectorHeight = 2 / 3f;
public Rect actionsRect;
public Rect inspectorRect;
public Rect headerRect;
public Rect trackRect;
//播放按钮这些高度
public float timeBtnsHeight = 20f;
//播放速度这些高度
public float playSpeedHeight = 40f;
public SkillEditorGuIStyles(Rect windows)
{
actionsRect = new Rect();
inspectorRect = new Rect();
headerRect = new Rect();
trackRect = new Rect();
RefreshStyles(windows);
}
public void RefreshStyles(Rect windows)
{
actionsRect.x = 0;
actionsRect.y = 0;
actionsRect.width = windows.width * inspectorWidth;
actionsRect.height = windows.height * actionsHeight;
inspectorRect.x = 0;
inspectorRect.y = windows.height * actionsHeight;
inspectorRect.width = windows.width * inspectorWidth;
inspectorRect.height = windows.height * inspectorHeight;
headerRect.x = inspectorRect.width;
headerRect.y = 0;
headerRect.width = windows.width * headerWidth;
headerRect.height = windows.height;
trackRect.x = inspectorRect.width + headerRect.width;
trackRect.y = 0;
trackRect.width = windows.width * trackWidth;
trackRect.height = windows.height;
}
}
这样就可以把整个窗体分成许许多多的小块,这里提两个问题,在下一节位文章会进行解答,同时在下一章会开始正式讲解如何构思整个技能编辑器结构。
1 为什么要单独提出布局?
2 为什么上述代码布局是在不停的刷新Rect而不是一次构造出来 ?