自定义 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();
        }
    }
}

这只是示例代码。目标是让某些东西工作并绘制到屏幕上。