自定义 window 模仿 SceneView
Custom window mimicking SceneView
我正在构建自定义 window 并且我正在尝试重用 Unity 的场景视图以便能够直接从这个特定的 window.
中绘制
我设法通过扩展 UnityEditor.SceneView
重现了正确的 window,这就是我所拥有的:
代码如下:
[EditorWindowTitle(title = "Shape Editor", useTypeNameAsIconName = false)]
public class StrokeEditor : SceneView
{
[MenuItem("Recognizer/Shape Editor")]
public static void Init()
{
var w = GetWindow<StrokeEditor>();
w.in2DMode = true;
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
protected override void OnGUI()
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Button("Add Stroke");
GUILayout.Button("Edit Stroke");
GUILayout.Button("Delete Stroke");
}
base.OnGUI();
}
}
有了这个,我可能就快完成了。
这是正确的处理方式吗?
我觉得有些不对劲,因为每当我使用 EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
时,它也会为主场景视图创建一个新场景。 (我希望主场景视图保持不变)
我还应该能够从场景视图中看到这些工具,例如:
有没有更好的方法来实现我想要的?
编辑 1:
所有这一切的最终用途是能够在 window 中通过点击和拖动鼠标像手机手势一样绘制 2D 形状。由此,我将能够获得一些职位来提供我的一种算法...
我过去处理过类似的问题。当我想扩展 SceneView 时,我使用 Gizmos 和绘图回调将我自己的控件添加到场景视图中,但我怀疑您可能想要更多的自由。
我做的另一件事是创建一个“编辑器预览场景”,向其中添加一个相机并将相机渲染到我的自定义 EditorWindow
中。这是很多工作,但一旦完成,我就可以完全自由地自定义编辑器体验了。
从 Unity 的 SceneView
继承可能非常危险,因为我预计它会经常更改,以至于您可能很难让您的东西在多个版本上工作。当 Unity 的代码不希望任何人继承 SceneView
.
时,您可能还会发现自己破坏了一些东西
您可以使用新的 GraphView
。这为您提供了一些您正在“免费”寻找的东西,主要是缩放和平移视图。由于 ShaderGraph 使用此功能,因此构建节点、select 节点和移动它们应该更容易,如果这是您想要的。
这是一个自定义编辑器的玩具示例 window,它允许您编辑可编写脚本的对象中的点列表:
Shape.cs
- 带有点列表的简单脚本化对象。
[CreateAssetMenu(menuName = "Test/ShapeObject")]
public class Shape : ScriptableObject
{
public List<Vector2> PointList = new List<Vector2>();
}
ShapeEditorWindow.cs
- 编辑器 window 带有工具栏和图形视图,可打开 Shape 类型的可编写脚本的对象。
using UnityEngine;
using UnityEditor;
using UnityEditor.UIElements;
public class ShapeEditorWindow : EditorWindow
{
private ShapeEditorGraphView _shapeEditorGraphView;
private Shape _shape;
[UnityEditor.Callbacks.OnOpenAsset(1)]
private static bool Callback(int instanceID, int line)
{
var shape = EditorUtility.InstanceIDToObject(instanceID) as Shape;
if (shape != null)
{
OpenWindow(shape);
return true;
}
return false; // we did not handle the open
}
private static void OpenWindow(Shape shape)
{
var window = GetWindow<ShapeEditorWindow>();
window.titleContent = new GUIContent("Shape Editor");
window._shape = shape;
window.rootVisualElement.Clear();
window.CreateGraphView();
window.CreateToolbar();
}
private void CreateToolbar()
{
var toolbar = new Toolbar();
var clearBtn = new ToolbarButton(()=>_shape.PointList.Clear()); ;
clearBtn.text = "Clear";
var undoBtn = new ToolbarButton(() =>_shape.PointList.RemoveAt(_shape.PointList.Count-1));
undoBtn.text = "Undo";
toolbar.Add(clearBtn);
toolbar.Add(new ToolbarSpacer());
toolbar.Add(undoBtn);
rootVisualElement.Add(toolbar);
}
private void CreateGraphView()
{
_shapeEditorGraphView = new ShapeEditorGraphView(_shape);
_shapeEditorGraphView.name = "Shape Editor Graph";
rootVisualElement.Add(_shapeEditorGraphView);
}
}
ShapeEditorGraphView.cs
- 具有缩放、网格、平移(使用 ContentDragger)和形状编辑器的图形视图。
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
public class ShapeEditorGraphView : GraphView
{
const float _pixelsPerUnit = 100f;
const bool _invertYPosition = true;
public ShapeEditorGraphView(Shape shape){
styleSheets.Add(Resources.Load<StyleSheet>("ShapeEditorGraph"));
this.StretchToParentSize();
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
Add(new GridBackground());
//pan with Alt-LeftMouseButton drag/ MidleMouseButton drag
this.AddManipulator(new ContentDragger());
//other things that might interest you
//this.AddManipulator(new SelectionDragger());
//this.AddManipulator(new RectangleSelector());
//this.AddManipulator(new ClickSelector());
this.AddManipulator(new ShapeManipulator(shape));
contentViewContainer.BringToFront();
contentViewContainer.Add(new Label { name = "origin", text = "(0,0)" });
//set the origin to the center of the window
this.schedule.Execute(() =>
{
contentViewContainer.transform.position = parent.worldBound.size / 2f;
});
}
public Vector2 WorldtoScreenSpace(Vector2 pos)
{
var position = pos * _pixelsPerUnit - contentViewContainer.layout.position;
if (_invertYPosition) position.y = -position.y;
return contentViewContainer.transform.matrix.MultiplyPoint3x4(position);
}
public Vector2 ScreenToWorldSpace(Vector2 pos)
{
Vector2 position = contentViewContainer.transform.matrix.inverse.MultiplyPoint3x4(pos);
if (_invertYPosition) position.y = -position.y;
return (position + contentViewContainer.layout.position) / _pixelsPerUnit;
}
}
不幸的是,网格背景和网格线的颜色相同,所以为了看到网格线,我们必须编写样式 sheet 并设置 GridBackground 属性。此文件必须位于 Editor/Resources 中,并使用 styleSheets.Add(Resources.Load<StyleSheet>("ShapeEditorGraph"));
加载
Editor/Resources/ShapeEditorGraph.uss
GridBackground {
--grid-background-color: rgba(32,32,32,1);
--line-color: rgba(255,255,255,.1);
--thick-line-color: rgba(255,255,255,.3);
--spacing: 100;
}
ShapeManipulator.cs
- 绘制和编辑形状。这类似于 RectangleSelector.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
public class ShapeManipulator : MouseManipulator
{
private Shape _shape;
private ShapeDraw _shapeDraw;
public ShapeManipulator(Shape shape)
{
activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse });
_shape = shape;
_shapeDraw = new ShapeDraw { points = shape.PointList };
}
protected override void RegisterCallbacksOnTarget()
{
target.Add(_shapeDraw);
target.Add(new Label { name = "mousePosition", text = "(0,0)" });
target.RegisterCallback<MouseDownEvent>(MouseDown);
target.RegisterCallback<MouseMoveEvent>(MouseMove);
target.RegisterCallback<MouseCaptureOutEvent>(MouseOut);
target.RegisterCallback<MouseUpEvent>(MouseUp);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<MouseDownEvent>(MouseDown);
target.UnregisterCallback<MouseUpEvent>(MouseUp);
target.UnregisterCallback<MouseMoveEvent>(MouseMove);
target.UnregisterCallback<MouseCaptureOutEvent>(MouseOut);
}
private void MouseOut(MouseCaptureOutEvent evt) => _shapeDraw.drawSegment = false;
private void MouseMove(MouseMoveEvent evt)
{
var t = target as ShapeEditorGraphView;
var mouseLabel = target.Q("mousePosition") as Label;
mouseLabel.transform.position = evt.localMousePosition + Vector2.up * 20;
mouseLabel.text = t.ScreenToWorldSpace(evt.localMousePosition).ToString();
//if left mouse is pressed
if ((evt.pressedButtons & 1) != 1) return;
_shapeDraw.end = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.MarkDirtyRepaint();
}
private void MouseUp(MouseUpEvent evt)
{
if (!CanStopManipulation(evt)) return;
target.ReleaseMouse();
if (!_shapeDraw.drawSegment) return;
if (_shape.PointList.Count == 0) _shape.PointList.Add(_shapeDraw.start);
var t = target as ShapeEditorGraphView;
_shape.PointList.Add(t.ScreenToWorldSpace(evt.localMousePosition));
_shapeDraw.drawSegment = false;
_shapeDraw.MarkDirtyRepaint();
}
private void MouseDown(MouseDownEvent evt)
{
if (!CanStartManipulation(evt)) return;
target.CaptureMouse();
_shapeDraw.drawSegment = true;
var t = target as ShapeEditorGraphView;
if (_shape.PointList.Count != 0) _shapeDraw.start = _shape.PointList.Last();
else _shapeDraw.start = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.end = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.MarkDirtyRepaint();
}
private class ShapeDraw : ImmediateModeElement
{
public List<Vector2> points { get; set; } = new List<Vector2>();
public Vector2 start { get; set; }
public Vector2 end { get; set; }
public bool drawSegment { get; set; }
protected override void ImmediateRepaint()
{
var lineColor = new Color(1.0f, 0.6f, 0.0f, 1.0f);
var t = parent as ShapeEditorGraphView;
//Draw shape
for (int i = 0; i < points.Count - 1; i++)
{
var p1 = t.WorldtoScreenSpace(points[i]);
var p2 = t.WorldtoScreenSpace(points[i + 1]);
GL.Begin(GL.LINES);
GL.Color(lineColor);
GL.Vertex(p1);
GL.Vertex(p2);
GL.End();
}
if (!drawSegment) return;
//Draw current segment
GL.Begin(GL.LINES);
GL.Color(lineColor);
GL.Vertex(t.WorldtoScreenSpace(start));
GL.Vertex(t.WorldtoScreenSpace(end));
GL.End();
}
}
}
这只是示例代码。目标是让某些东西工作并绘制到屏幕上。
我正在构建自定义 window 并且我正在尝试重用 Unity 的场景视图以便能够直接从这个特定的 window.
中绘制我设法通过扩展 UnityEditor.SceneView
重现了正确的 window,这就是我所拥有的:
代码如下:
[EditorWindowTitle(title = "Shape Editor", useTypeNameAsIconName = false)]
public class StrokeEditor : SceneView
{
[MenuItem("Recognizer/Shape Editor")]
public static void Init()
{
var w = GetWindow<StrokeEditor>();
w.in2DMode = true;
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
protected override void OnGUI()
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Button("Add Stroke");
GUILayout.Button("Edit Stroke");
GUILayout.Button("Delete Stroke");
}
base.OnGUI();
}
}
有了这个,我可能就快完成了。
这是正确的处理方式吗?
我觉得有些不对劲,因为每当我使用 EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
时,它也会为主场景视图创建一个新场景。 (我希望主场景视图保持不变)
我还应该能够从场景视图中看到这些工具,例如:
有没有更好的方法来实现我想要的?
编辑 1:
所有这一切的最终用途是能够在 window 中通过点击和拖动鼠标像手机手势一样绘制 2D 形状。由此,我将能够获得一些职位来提供我的一种算法...
我过去处理过类似的问题。当我想扩展 SceneView 时,我使用 Gizmos 和绘图回调将我自己的控件添加到场景视图中,但我怀疑您可能想要更多的自由。
我做的另一件事是创建一个“编辑器预览场景”,向其中添加一个相机并将相机渲染到我的自定义 EditorWindow
中。这是很多工作,但一旦完成,我就可以完全自由地自定义编辑器体验了。
从 Unity 的 SceneView
继承可能非常危险,因为我预计它会经常更改,以至于您可能很难让您的东西在多个版本上工作。当 Unity 的代码不希望任何人继承 SceneView
.
您可以使用新的 GraphView
。这为您提供了一些您正在“免费”寻找的东西,主要是缩放和平移视图。由于 ShaderGraph 使用此功能,因此构建节点、select 节点和移动它们应该更容易,如果这是您想要的。
这是一个自定义编辑器的玩具示例 window,它允许您编辑可编写脚本的对象中的点列表:
Shape.cs
- 带有点列表的简单脚本化对象。
[CreateAssetMenu(menuName = "Test/ShapeObject")]
public class Shape : ScriptableObject
{
public List<Vector2> PointList = new List<Vector2>();
}
ShapeEditorWindow.cs
- 编辑器 window 带有工具栏和图形视图,可打开 Shape 类型的可编写脚本的对象。
using UnityEngine;
using UnityEditor;
using UnityEditor.UIElements;
public class ShapeEditorWindow : EditorWindow
{
private ShapeEditorGraphView _shapeEditorGraphView;
private Shape _shape;
[UnityEditor.Callbacks.OnOpenAsset(1)]
private static bool Callback(int instanceID, int line)
{
var shape = EditorUtility.InstanceIDToObject(instanceID) as Shape;
if (shape != null)
{
OpenWindow(shape);
return true;
}
return false; // we did not handle the open
}
private static void OpenWindow(Shape shape)
{
var window = GetWindow<ShapeEditorWindow>();
window.titleContent = new GUIContent("Shape Editor");
window._shape = shape;
window.rootVisualElement.Clear();
window.CreateGraphView();
window.CreateToolbar();
}
private void CreateToolbar()
{
var toolbar = new Toolbar();
var clearBtn = new ToolbarButton(()=>_shape.PointList.Clear()); ;
clearBtn.text = "Clear";
var undoBtn = new ToolbarButton(() =>_shape.PointList.RemoveAt(_shape.PointList.Count-1));
undoBtn.text = "Undo";
toolbar.Add(clearBtn);
toolbar.Add(new ToolbarSpacer());
toolbar.Add(undoBtn);
rootVisualElement.Add(toolbar);
}
private void CreateGraphView()
{
_shapeEditorGraphView = new ShapeEditorGraphView(_shape);
_shapeEditorGraphView.name = "Shape Editor Graph";
rootVisualElement.Add(_shapeEditorGraphView);
}
}
ShapeEditorGraphView.cs
- 具有缩放、网格、平移(使用 ContentDragger)和形状编辑器的图形视图。
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
public class ShapeEditorGraphView : GraphView
{
const float _pixelsPerUnit = 100f;
const bool _invertYPosition = true;
public ShapeEditorGraphView(Shape shape){
styleSheets.Add(Resources.Load<StyleSheet>("ShapeEditorGraph"));
this.StretchToParentSize();
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
Add(new GridBackground());
//pan with Alt-LeftMouseButton drag/ MidleMouseButton drag
this.AddManipulator(new ContentDragger());
//other things that might interest you
//this.AddManipulator(new SelectionDragger());
//this.AddManipulator(new RectangleSelector());
//this.AddManipulator(new ClickSelector());
this.AddManipulator(new ShapeManipulator(shape));
contentViewContainer.BringToFront();
contentViewContainer.Add(new Label { name = "origin", text = "(0,0)" });
//set the origin to the center of the window
this.schedule.Execute(() =>
{
contentViewContainer.transform.position = parent.worldBound.size / 2f;
});
}
public Vector2 WorldtoScreenSpace(Vector2 pos)
{
var position = pos * _pixelsPerUnit - contentViewContainer.layout.position;
if (_invertYPosition) position.y = -position.y;
return contentViewContainer.transform.matrix.MultiplyPoint3x4(position);
}
public Vector2 ScreenToWorldSpace(Vector2 pos)
{
Vector2 position = contentViewContainer.transform.matrix.inverse.MultiplyPoint3x4(pos);
if (_invertYPosition) position.y = -position.y;
return (position + contentViewContainer.layout.position) / _pixelsPerUnit;
}
}
不幸的是,网格背景和网格线的颜色相同,所以为了看到网格线,我们必须编写样式 sheet 并设置 GridBackground 属性。此文件必须位于 Editor/Resources 中,并使用 styleSheets.Add(Resources.Load<StyleSheet>("ShapeEditorGraph"));
Editor/Resources/ShapeEditorGraph.uss
GridBackground {
--grid-background-color: rgba(32,32,32,1);
--line-color: rgba(255,255,255,.1);
--thick-line-color: rgba(255,255,255,.3);
--spacing: 100;
}
ShapeManipulator.cs
- 绘制和编辑形状。这类似于 RectangleSelector.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
public class ShapeManipulator : MouseManipulator
{
private Shape _shape;
private ShapeDraw _shapeDraw;
public ShapeManipulator(Shape shape)
{
activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse });
_shape = shape;
_shapeDraw = new ShapeDraw { points = shape.PointList };
}
protected override void RegisterCallbacksOnTarget()
{
target.Add(_shapeDraw);
target.Add(new Label { name = "mousePosition", text = "(0,0)" });
target.RegisterCallback<MouseDownEvent>(MouseDown);
target.RegisterCallback<MouseMoveEvent>(MouseMove);
target.RegisterCallback<MouseCaptureOutEvent>(MouseOut);
target.RegisterCallback<MouseUpEvent>(MouseUp);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<MouseDownEvent>(MouseDown);
target.UnregisterCallback<MouseUpEvent>(MouseUp);
target.UnregisterCallback<MouseMoveEvent>(MouseMove);
target.UnregisterCallback<MouseCaptureOutEvent>(MouseOut);
}
private void MouseOut(MouseCaptureOutEvent evt) => _shapeDraw.drawSegment = false;
private void MouseMove(MouseMoveEvent evt)
{
var t = target as ShapeEditorGraphView;
var mouseLabel = target.Q("mousePosition") as Label;
mouseLabel.transform.position = evt.localMousePosition + Vector2.up * 20;
mouseLabel.text = t.ScreenToWorldSpace(evt.localMousePosition).ToString();
//if left mouse is pressed
if ((evt.pressedButtons & 1) != 1) return;
_shapeDraw.end = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.MarkDirtyRepaint();
}
private void MouseUp(MouseUpEvent evt)
{
if (!CanStopManipulation(evt)) return;
target.ReleaseMouse();
if (!_shapeDraw.drawSegment) return;
if (_shape.PointList.Count == 0) _shape.PointList.Add(_shapeDraw.start);
var t = target as ShapeEditorGraphView;
_shape.PointList.Add(t.ScreenToWorldSpace(evt.localMousePosition));
_shapeDraw.drawSegment = false;
_shapeDraw.MarkDirtyRepaint();
}
private void MouseDown(MouseDownEvent evt)
{
if (!CanStartManipulation(evt)) return;
target.CaptureMouse();
_shapeDraw.drawSegment = true;
var t = target as ShapeEditorGraphView;
if (_shape.PointList.Count != 0) _shapeDraw.start = _shape.PointList.Last();
else _shapeDraw.start = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.end = t.ScreenToWorldSpace(evt.localMousePosition);
_shapeDraw.MarkDirtyRepaint();
}
private class ShapeDraw : ImmediateModeElement
{
public List<Vector2> points { get; set; } = new List<Vector2>();
public Vector2 start { get; set; }
public Vector2 end { get; set; }
public bool drawSegment { get; set; }
protected override void ImmediateRepaint()
{
var lineColor = new Color(1.0f, 0.6f, 0.0f, 1.0f);
var t = parent as ShapeEditorGraphView;
//Draw shape
for (int i = 0; i < points.Count - 1; i++)
{
var p1 = t.WorldtoScreenSpace(points[i]);
var p2 = t.WorldtoScreenSpace(points[i + 1]);
GL.Begin(GL.LINES);
GL.Color(lineColor);
GL.Vertex(p1);
GL.Vertex(p2);
GL.End();
}
if (!drawSegment) return;
//Draw current segment
GL.Begin(GL.LINES);
GL.Color(lineColor);
GL.Vertex(t.WorldtoScreenSpace(start));
GL.Vertex(t.WorldtoScreenSpace(end));
GL.End();
}
}
}
这只是示例代码。目标是让某些东西工作并绘制到屏幕上。