Unity - 在游戏视图上方停靠一个编辑器 window
Unity - Dock an editor window above the game view
我想要实现的是通过脚本将编辑器 window 停靠在 Unity 编辑器游戏视图的下方、顶部或左侧或右侧(为简单起见,我们只关注停靠在上方游戏视图)。查看 this 视频,了解我想要实现的目标示例。
编辑
由于问题被标记为题外话,这里是一个代码示例。我遇到的问题是 Dock
方法中的 dropInfo
为空,因此 SplitView
的 PerformDrop
方法导致空引用错误。
public static class Docker
{
private class _EditorWindow
{
private EditorWindow instance;
private Type type;
public _EditorWindow( EditorWindow instance )
{
this.instance = instance;
type = instance.GetType();
}
public object m_Parent
{
get
{
var field = type.GetField( "m_Parent", BindingFlags.Instance | BindingFlags.NonPublic );
return field.GetValue( instance );
}
}
}
private class _DockArea
{
private object instance;
private Type type;
public _DockArea( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object window
{
get
{
var property = type.GetProperty( "window", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
public object s_OriginalDragSource
{
set
{
var field = type.GetField( "s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic );
field.SetValue( null, value );
}
}
}
private class _ContainerWindow
{
private object instance;
private Type type;
public _ContainerWindow( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object rootSplitView
{
get
{
var property = type.GetProperty( "rootSplitView", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
}
private class _SplitView
{
private object instance;
private Type type;
public _SplitView( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object DragOver( EditorWindow child, Vector2 screenPoint )
{
var method = type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
return method.Invoke( instance, new object[] { child, screenPoint } );
}
public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint )
{
var method = type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
method.Invoke( instance, new object[] { child, dropInfo, screenPoint } );
}
}
public enum DockPosition
{
Left,
Top,
Right,
Bottom
}
/// <summary>
/// Docks the second window to the first window at the given position
/// </summary>
public static void Dock( this EditorWindow wnd, EditorWindow other, DockPosition position )
{
var mousePosition = GetFakeMousePosition( wnd, position );
var parent = new _EditorWindow( wnd );
var child = new _EditorWindow( other );
var dockArea = new _DockArea( parent.m_Parent );
var containerWindow = new _ContainerWindow( dockArea.window );
var splitView = new _SplitView( containerWindow.rootSplitView );
var dropInfo = splitView.DragOver( other, mousePosition );
dockArea.s_OriginalDragSource = child.m_Parent;
splitView.PerformDrop( other, dropInfo, mousePosition );
}
private static Vector2 GetFakeMousePosition( EditorWindow wnd, DockPosition position )
{
Vector2 mousePosition = Vector2.zero;
// The 20 is required to make the docking work.
// Smaller values might not work when faking the mouse position.
switch ( position )
{
case DockPosition.Left:
mousePosition = new Vector2( 20, wnd.position.size.y / 2 );
break;
case DockPosition.Top:
mousePosition = new Vector2( wnd.position.size.x / 2, 20 );
break;
case DockPosition.Right:
mousePosition = new Vector2( wnd.position.size.x - 20, wnd.position.size.y / 2 );
break;
case DockPosition.Bottom:
mousePosition = new Vector2( wnd.position.size.x / 2, wnd.position.size.y - 20 );
break;
}
return new Vector2(wnd.position.x + mousePosition.x, wnd.position.y + mousePosition.y);
}
}
public static class SomeStaticClass
{
[MenuItem("DOCK TESTING/Dock Above")]
public static void DockAbove()
{
SysType gameViewType = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.GameView");
EditorWindow baseWindow = EditorWindow.GetWindow(gameViewType);
EditorWindow newWindow = (EditorWindow)ScriptableObject.CreateInstance(gameViewType);
baseWindow.Dock(newWindow, Docker.DockPosition.Top);
}
}
所以为了将来的参考,对于尝试此代码的任何其他人(找到 here)这就是我为它工作所做的。
dropInfo
对象始终为 null 的原因有两点:
首先使用固定值 20 来计算假鼠标位置,生成的位置是游戏视图的下降标签区域,因此无法将另一个 window 锚定到游戏中视图(这仅在尝试在游戏顶部视图上切换时发生)。为了解决这个问题,我只是使用了一个不同的值,作为 offset
参数传递给 GetFakeMousePosition
。
其次,使用编辑器 window 位置来计算假鼠标位置,总是给出奇怪的结果,即当我注意到 window position
属性 returns window 相对于它的父级的本地位置,而不是 window 的屏幕位置,所以我从使用 window 位置改为使用它的顶部父位置(Window->DockArea->SplitView)位置来计算假鼠标位置。
所有这些更改后,GetFakeMousePosition
看起来像这样:
private static Vector2 GetFakeMousePosition(_SplitView view, DockPosition position, float offset)
{
Vector2 mousePosition = Vector2.zero;
switch ( position )
{
case DockPosition.Left:
mousePosition = new Vector2(offset, view.position.height / 2);
break;
case DockPosition.Top:
mousePosition = new Vector2(view.position.width / 2, offset);
break;
case DockPosition.Right:
mousePosition = new Vector2(view.position.width - offset, wnd.position.size.y / 2 );
break;
case DockPosition.Bottom:
mousePosition = new Vector2(view.position.width / 2, view.position.height - offset);
break;
}
return new Vector2(view.position.x + mousePosition.x, view.position.y + mousePosition.y);
}
_SplitView
class 看起来像这样:
private class _SplitView
{
private object instance;
private Type type;
public _SplitView( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object DragOver( EditorWindow child, Vector2 screenPoint )
{
var method = type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
return method.Invoke( instance, new object[] { child, screenPoint } );
}
public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint )
{
var method = type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
method.Invoke( instance, new object[] { child, dropInfo, screenPoint } );
}
public Rect position
{
get
{
var property = type.GetProperty("screenPosition", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(instance, null);
}
}
}
我想要实现的是通过脚本将编辑器 window 停靠在 Unity 编辑器游戏视图的下方、顶部或左侧或右侧(为简单起见,我们只关注停靠在上方游戏视图)。查看 this 视频,了解我想要实现的目标示例。
编辑
由于问题被标记为题外话,这里是一个代码示例。我遇到的问题是 Dock
方法中的 dropInfo
为空,因此 SplitView
的 PerformDrop
方法导致空引用错误。
public static class Docker
{
private class _EditorWindow
{
private EditorWindow instance;
private Type type;
public _EditorWindow( EditorWindow instance )
{
this.instance = instance;
type = instance.GetType();
}
public object m_Parent
{
get
{
var field = type.GetField( "m_Parent", BindingFlags.Instance | BindingFlags.NonPublic );
return field.GetValue( instance );
}
}
}
private class _DockArea
{
private object instance;
private Type type;
public _DockArea( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object window
{
get
{
var property = type.GetProperty( "window", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
public object s_OriginalDragSource
{
set
{
var field = type.GetField( "s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic );
field.SetValue( null, value );
}
}
}
private class _ContainerWindow
{
private object instance;
private Type type;
public _ContainerWindow( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object rootSplitView
{
get
{
var property = type.GetProperty( "rootSplitView", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
}
private class _SplitView
{
private object instance;
private Type type;
public _SplitView( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object DragOver( EditorWindow child, Vector2 screenPoint )
{
var method = type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
return method.Invoke( instance, new object[] { child, screenPoint } );
}
public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint )
{
var method = type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
method.Invoke( instance, new object[] { child, dropInfo, screenPoint } );
}
}
public enum DockPosition
{
Left,
Top,
Right,
Bottom
}
/// <summary>
/// Docks the second window to the first window at the given position
/// </summary>
public static void Dock( this EditorWindow wnd, EditorWindow other, DockPosition position )
{
var mousePosition = GetFakeMousePosition( wnd, position );
var parent = new _EditorWindow( wnd );
var child = new _EditorWindow( other );
var dockArea = new _DockArea( parent.m_Parent );
var containerWindow = new _ContainerWindow( dockArea.window );
var splitView = new _SplitView( containerWindow.rootSplitView );
var dropInfo = splitView.DragOver( other, mousePosition );
dockArea.s_OriginalDragSource = child.m_Parent;
splitView.PerformDrop( other, dropInfo, mousePosition );
}
private static Vector2 GetFakeMousePosition( EditorWindow wnd, DockPosition position )
{
Vector2 mousePosition = Vector2.zero;
// The 20 is required to make the docking work.
// Smaller values might not work when faking the mouse position.
switch ( position )
{
case DockPosition.Left:
mousePosition = new Vector2( 20, wnd.position.size.y / 2 );
break;
case DockPosition.Top:
mousePosition = new Vector2( wnd.position.size.x / 2, 20 );
break;
case DockPosition.Right:
mousePosition = new Vector2( wnd.position.size.x - 20, wnd.position.size.y / 2 );
break;
case DockPosition.Bottom:
mousePosition = new Vector2( wnd.position.size.x / 2, wnd.position.size.y - 20 );
break;
}
return new Vector2(wnd.position.x + mousePosition.x, wnd.position.y + mousePosition.y);
}
}
public static class SomeStaticClass
{
[MenuItem("DOCK TESTING/Dock Above")]
public static void DockAbove()
{
SysType gameViewType = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.GameView");
EditorWindow baseWindow = EditorWindow.GetWindow(gameViewType);
EditorWindow newWindow = (EditorWindow)ScriptableObject.CreateInstance(gameViewType);
baseWindow.Dock(newWindow, Docker.DockPosition.Top);
}
}
所以为了将来的参考,对于尝试此代码的任何其他人(找到 here)这就是我为它工作所做的。
dropInfo
对象始终为 null 的原因有两点:
首先使用固定值 20 来计算假鼠标位置,生成的位置是游戏视图的下降标签区域,因此无法将另一个 window 锚定到游戏中视图(这仅在尝试在游戏顶部视图上切换时发生)。为了解决这个问题,我只是使用了一个不同的值,作为 offset
参数传递给 GetFakeMousePosition
。
其次,使用编辑器 window 位置来计算假鼠标位置,总是给出奇怪的结果,即当我注意到 window position
属性 returns window 相对于它的父级的本地位置,而不是 window 的屏幕位置,所以我从使用 window 位置改为使用它的顶部父位置(Window->DockArea->SplitView)位置来计算假鼠标位置。
所有这些更改后,GetFakeMousePosition
看起来像这样:
private static Vector2 GetFakeMousePosition(_SplitView view, DockPosition position, float offset)
{
Vector2 mousePosition = Vector2.zero;
switch ( position )
{
case DockPosition.Left:
mousePosition = new Vector2(offset, view.position.height / 2);
break;
case DockPosition.Top:
mousePosition = new Vector2(view.position.width / 2, offset);
break;
case DockPosition.Right:
mousePosition = new Vector2(view.position.width - offset, wnd.position.size.y / 2 );
break;
case DockPosition.Bottom:
mousePosition = new Vector2(view.position.width / 2, view.position.height - offset);
break;
}
return new Vector2(view.position.x + mousePosition.x, view.position.y + mousePosition.y);
}
_SplitView
class 看起来像这样:
private class _SplitView
{
private object instance;
private Type type;
public _SplitView( object instance )
{
this.instance = instance;
type = instance.GetType();
}
public object DragOver( EditorWindow child, Vector2 screenPoint )
{
var method = type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
return method.Invoke( instance, new object[] { child, screenPoint } );
}
public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint )
{
var method = type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
method.Invoke( instance, new object[] { child, dropInfo, screenPoint } );
}
public Rect position
{
get
{
var property = type.GetProperty("screenPosition", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(instance, null);
}
}
}