最近共工作要求,需要对Timeline面板编辑器编写一些辅助性工具。但是我们没有办法对Timeline编辑器进行重绘或修改功能(我是不知道),所以我打算通过新建一个EditorWindow,在新的窗口中实现辅助功能。
实际上我想要的工具效果为:
但实际上打开的效果是这样的:
让我感觉有点不爽,不够干净整洁,看着就感觉麻烦。就开始考虑Unity中是否存在这样的接口,可以让我们主动去实现自己想要的排版界面。
然后开始百度/Google 搜索,结果都是Layout的讲解(难道就没有人有这样需求吗?)
然后又开始API查找,代码查找。结果还真到找了一个API
这个API可以自己的窗口打开时,窗口与目标窗口标签贴合。类似于这样:
但是明显不是我想要的效果,咋整?继续找…直到看到这个:
通过这段代码,感觉到这应该是一个Unity创建窗口的一套流程
ContainerWindow 是窗体最外层框架
SplitView是一套切割框架
DockArea是窗体载体
EditorWindow就是实际的窗体
对不对呢?实践才是真理,模仿写一套试试。
这几个脚本,在Unity中都是 internal ,说白了就是,我们无法直接使用,那万能的反射就派上用场了。
首先,先将我们需要的几个类中的方法反射调用出来:
ContainerWindow 反射:
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Reflection;
public static class EditorContainerWindow
{
private static Type _containerWindowType;
/// <summary>
/// 类型
/// </summary>
public static Type ContainerWindowType
{
get
{
if (_containerWindowType==null)
_containerWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ContainerWindow");
return _containerWindowType;
}
}
/// <summary>
/// 创建实例
/// </summary>
public static object CreateInstance()
{
return ScriptableObject.CreateInstance(ContainerWindowType);
}
/// <summary>
/// 设置RootView
/// </summary>
/// <param name="instance"></param>
/// <param name="value"></param>
public static void SetRootView(object instance, object value)
{
FieldInfo finfo = ContainerWindowType.GetField("m_RootView", BindingFlags.Instance|BindingFlags.NonPublic);
if (finfo!=null)
finfo.SetValue(instance,value);
}
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="instance"></param>
/// <param name="position"></param>
public static void SetPosition(object instance, Rect position)
{
PropertyInfo pInfo = ContainerWindowType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return;
pInfo.SetValue(instance,position);
}
/// <summary>
/// 获取坐标
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public static Rect GetPosition(object instance)
{
PropertyInfo pInfo = ContainerWindowType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return default(Rect);
return (Rect)pInfo.GetValue(instance);
}
/// <summary>
/// 显示
/// </summary>
/// <param name="instance"></param>
/// <param name="showMode"></param>
/// <param name="loadPosition"></param>
/// <param name="displayImmediately"></param>
/// <param name="setFocus"></param>
public static void Show(object instance, int showMode, bool loadPosition, bool displayImmediately, bool setFocus)
{
MethodInfo mInfo = ContainerWindowType.GetMethod("Show", BindingFlags.Public | BindingFlags.Instance, null,
new Type[]
{
typeof(EditorWindow).Assembly.GetType("UnityEditor.ShowMode"), typeof(bool), typeof(bool), typeof(bool)
}, null);
if (mInfo==null) return;
mInfo.Invoke(instance, new object[] {showMode, loadPosition, displayImmediately, setFocus});
}
/// <summary>
/// 设置尺寸
/// </summary>
/// <param name="instance"></param>
public static void OnResize(object instance)
{
MethodInfo mInfo = ContainerWindowType.GetMethod("OnResize", BindingFlags.Instance | BindingFlags.NonPublic);
if (mInfo==null) return;
mInfo.Invoke(instance, null);
}
}
DockArea 反射:
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
using Object = System.Object;
public static class EditorDockArea
{
private static Type _dockAreaType;
/// <summary>
/// 类型
/// </summary>
public static Type DockAreaType
{
get
{
if (_dockAreaType==null)
_dockAreaType = typeof(EditorWindow).Assembly.GetType("UnityEditor.DockArea");
return _dockAreaType;
}
}
/// <summary>
/// 创建实例
/// </summary>
/// <returns></returns>
public static object CreateInstance()
{
return ScriptableObject.CreateInstance(DockAreaType);
}
/// <summary>
/// 添加Tab
/// </summary>
/// <param name="instance"></param>
/// <param name="window"></param>
/// <param name="sendPaneEvents"></param>
public static void AddTab(object instance, EditorWindow window, bool sendPaneEvents = true)
{
MethodInfo mInfo = DockAreaType.GetMethod("AddTab", BindingFlags.Instance | BindingFlags.Public, null,
new Type[] {typeof(EditorWindow), typeof(bool)}, null);
if (mInfo==null) return;
mInfo.Invoke(instance, new object[] {window, sendPaneEvents});
}
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="instance"></param>
/// <param name="position"></param>
public static void SetPosition(object instance, Rect position)
{
PropertyInfo pInfo = DockAreaType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return;
pInfo.SetValue(instance,position);
}
/// <summary>
/// 获取坐标
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public static Rect GetPosition(object instance)
{
PropertyInfo pInfo = DockAreaType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return default(Rect);
return (Rect)pInfo.GetValue(instance);
}
}
EditorWindow反射:
using System;
using UnityEngine;
using UnityEditor;
using System.Reflection;
public static class EditorEditorWindow
{
private static Type _editorWindowType;
public static Type EdtiorWindowType
{
get
{
if (_editorWindowType == null) _editorWindowType = typeof(EditorWindow);
return _editorWindowType;
}
}
/// <summary>
/// 匹配父物体尺寸
/// </summary>
/// <param name="instance"></param>
public static void MakeParentsSettingsMatchMe(object instance)
{
MethodInfo mInfo = EdtiorWindowType.GetMethod("MakeParentsSettingsMatchMe", BindingFlags.Instance | BindingFlags.NonPublic);
if (mInfo==null) return;
mInfo.Invoke(instance, null);
}
}
SplitView反射:
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
public static class EditorSplitView
{
private static Type _splitViewType;
public static Type SplitViewType
{
get
{
if (_splitViewType==null)
_splitViewType = typeof(EditorWindow).Assembly.GetType("UnityEditor.SplitView");
return _splitViewType;
}
}
/// <summary>
/// 创建实例
/// </summary>
/// <returns></returns>
public static object CreateInstance()
{
return ScriptableObject.CreateInstance(SplitViewType);
}
/// <summary>
/// 添加子视图
/// </summary>
/// <param name="instance"></param>
/// <param name="view"></param>
public static void AddChild(object instance, object view)
{
MethodInfo mInfo = SplitViewType.GetMethod("AddChild", BindingFlags.Public | BindingFlags.Instance, null,
new Type[] {typeof(EditorWindow).Assembly.GetType("UnityEditor.View")}, null);
if (mInfo==null) return;
mInfo.Invoke(instance, new object[] {view});
}
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="instance"></param>
/// <param name="position"></param>
public static void SetPosition(object instance, Rect position)
{
PropertyInfo pInfo = SplitViewType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return;
pInfo.SetValue(instance,position);
}
/// <summary>
/// 获取坐标
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public static Rect GetPosition(object instance)
{
PropertyInfo pInfo = SplitViewType.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
if (pInfo==null) return default(Rect);
return (Rect)pInfo.GetValue(instance);
}
/// <summary>
/// 设置是否为垂直布局
/// </summary>
/// <param name="instance"></param>
/// <param name="isVertical"></param>
public static void SetVertical(object instance, bool isVertical)
{
FieldInfo fInfo = SplitViewType.GetField("vertical", BindingFlags.Public | BindingFlags.Instance);
if (fInfo==null) return;
fInfo.SetValue(instance,isVertical);
}
}
3个窗口,其中Timeline编辑窗口,我们得使用反射获取Timeline的窗口类。
//工具窗口
using UnityEditor;
public class NTimeLineWindow:EditorWindow
{
static void OpenWindow()
{
var window = GetWindow<NTimeLineWindow>();
window.Show();
}
}
//Menu菜单类窗口
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
using System.Collections.Generic;
public class NTimeLineMenuWindow:EditorWindow
{
private static Type _TimelineWindowType;
/// <summary>
/// 获取Timeline窗口类型
/// </summary>
private static Type TimelineWindowType
{
get
{
if (_TimelineWindowType == null)
{
Assembly a = null;
foreach (System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.FullName.ToLower().Contains("unityeditor.timeline"))
a = assembly;
if (a != null)
_TimelineWindowType = a.GetType("UnityEditor.Timeline.TimelineWindow");
}
}
return _TimelineWindowType;
}
}
//显示Timeline
public static void ShowTimeLine()
{
Type menuType = typeof(NTimeLineMenuWindow);
Type toolType = typeof(NTimeLineWindow);
//创建最外层容器
object containerInstance = EditorContainerWindow.CreateInstance();
//创建分屏容器
object splitViewInstance = EditorSplitView.CreateInstance();
//设置根容器
EditorContainerWindow.SetRootView(containerInstance,splitViewInstance);
//添加menu容器和工具容器
object menuDockAreaInstance = EditorDockArea.CreateInstance();
EditorDockArea.SetPosition(menuDockAreaInstance,new Rect(0,0,200,800));
EditorWindow menuWindow = (EditorWindow) ScriptableObject.CreateInstance(menuType);
//menuWindow.position = new Rect(0,0,150,800);
EditorDockArea.AddTab(menuDockAreaInstance,menuWindow);
EditorSplitView.AddChild(splitViewInstance,menuDockAreaInstance);
//tool面板与timeline面板分割面板
object tool_timelineSplitViewInstance = EditorSplitView.CreateInstance();
EditorSplitView.SetPosition(tool_timelineSplitViewInstance,new Rect(0,0,600,800));
//设置垂直状态
EditorSplitView.SetVertical(tool_timelineSplitViewInstance,true);
object toolDockArea = EditorDockArea.CreateInstance();
EditorDockArea.SetPosition(toolDockArea,new Rect(0,0,600,200));
EditorWindow toolWindow = (EditorWindow) ScriptableObject.CreateInstance(toolType);
EditorDockArea.AddTab(toolDockArea,toolWindow);
EditorSplitView.AddChild(tool_timelineSplitViewInstance,toolDockArea);
//添加timeline窗体
object timelineDockAreaInstance = EditorDockArea.CreateInstance();
EditorDockArea.SetPosition(timelineDockAreaInstance,new Rect(0,0,600,600));
EditorWindow timelineWindow = (EditorWindow) ScriptableObject.CreateInstance(TimelineWindowType);
EditorDockArea.AddTab(timelineDockAreaInstance,timelineWindow);
EditorSplitView.AddChild(tool_timelineSplitViewInstance,timelineDockAreaInstance);
//添加tool_timeline切割窗体
EditorSplitView.AddChild(splitViewInstance,tool_timelineSplitViewInstance);
EditorEditorWindow.MakeParentsSettingsMatchMe(menuWindow);
EditorEditorWindow.MakeParentsSettingsMatchMe(toolWindow);
EditorEditorWindow.MakeParentsSettingsMatchMe(timelineWindow);
EditorContainerWindow.SetPosition(containerInstance,new Rect(100,100,800,800));
EditorSplitView.SetPosition(splitViewInstance,new Rect(0,0,800,800));
EditorContainerWindow.Show(containerInstance,0,true,false,true);
EditorContainerWindow.OnResize(containerInstance);
}
[MenuItem("Tools/Time Line Menu")]
static void OpenWindow()
{
ShowTimeLine();
}
}
根据我们想要的排版 所以实际上的嵌套关系应该是这样的
NTimeLineMenuWindow.ShowTimeLine()方法内依照上图方法进行嵌套,得到最终结果:
完美!!!
我不知道Unity有没有提供这样的接口,如果有的话还得麻烦告诉我一下(我也就不用这么麻烦了)…