Unity Editor扩展 GraphView

GraphView介绍

GraphView是Unity推出的一个基于UIElement的节点编辑器UI模块,功能很完全,有多选,拖动,缩放,Group等功能。

GraphView的基本使用(一)

图形视图由四个主要元素组成。

  • GraphView
  • Node
  • Port
  • Edge

创建编辑器窗口

首先,创建编辑器扩展熟悉的编辑器窗口。

using UnityEditor;

public class SampleGraphEditorWindow : EditorWindow
{
    [MenuItem("Window/Open SampleGraphView")]
    public static void Open()
    {
        GetWindow<SampleGraphEditorWindow>("SampleGraphView");
    }
}

创建图形视图

接下来,我们将立即创建图形视图,这是节点和边的父级。

using UnityEditor.Experimental.GraphView;

public class SampleGraphView : GraphView
{
}

此外,在此处向编辑器窗口SampleGraphEditorWindow.cs添加图形视图。

    void OnEnable()
    {
        rootVisualElement.Add(new SampleGraphView());
    }

创建节点

尽管 GraphView 通常称为节点编辑器,但Node 顾名思义,它是节点编辑器中最重要的部分。

创建基础节点,在实际使用时继承此节点。

using UnityEditor.Experimental.GraphView;

public class SampleNode : Node
{
}

在构造函数中完成将此节点添加到图形视图。

    public SampleGraphView() : base()
    {
        AddElement(new SampleNode());
    }

设置高度,使其正确显示。

    private void OnEnable()
    {
        rootVisualElement.Add(new SampleGraphView()
        {
          style  = { flexGrow = 1}
        });
    }

可以看到显示窗口中已经显示出节点了。

为节点添加端口

节点可以与其他节点连接,可以将边从输出端口连接到输入端口。

    public SampleNode()
    {
        title = "Sample";

        var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
        inputContainer.Add(inputPort);

        var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
        outputContainer.Add(outputPort);
    }

使节点能够移动

使用SelectionDraggerAddManipulator。

    public SampleGraphView() : base()
    {
        AddElement(new SampleNode());
        this.AddManipulator(new SelectionDragger());
    }

创建多个节点

可以从右键单击菜单中添加新节点。

    public SampleGraphView() : base()
    {
        this.AddManipulator(new SelectionDragger());

        nodeCreationRequest += context =>
        {
            AddElement(new SampleNode());
        };
    }

连接节点

在SampleGraphView中重写GetCompatiblePorts并返回正确的端口。

    public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
    {
        return ports.ToList();
    }

 可以看到已经能连接节点了。

放大和缩小

只需在图形视图中调用SetupZoom即可放大和缩小。

    public SampleGraphView() : base()
    {
        SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);

        this.AddManipulator(new SelectionDragger());

        nodeCreationRequest += context =>
        {
            AddElement(new SampleNode());
        };
    }

更改背景颜色

因为网格背景出现在图形视图的元素之前,所以需要在添加视图显示元素之前调用Insert方法。

    public SampleGraphView() : base()
    {
        SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);

        Insert(0, new GridBackground());

        this.AddManipulator(new SelectionDragger());

        nodeCreationRequest += context =>
        {
            AddElement(new SampleNode());
        };
    }

实现节点

创建派生自基类节点的具体业务节点。创建如下节点为:

  • 根节点
  • 日志记录节点
  • 字符串输出节点

现在,我们将示例节点作为抽象类,并且继承它。

using UnityEditor.Experimental.GraphView;

public abstract class SampleNode : Node
{
}

首先,创建日志处理节点。

using UnityEditor.Experimental.GraphView;

public class ProcessNode : SampleNode
{
    public ProcessNode()
    {
        var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
        inputPort.portName = "In";
        inputContainer.Add(inputPort);

        var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
        outputPort.portName = "Out";
        outputContainer.Add(outputPort);
    }
}

继承此节点创建日志节点。

using UnityEditor.Experimental.GraphView;

public class LogNode : ProcessNode
{
    public LogNode() : base()
    {
        title = "Log";

        var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(string));
        inputContainer.Add(inputPort);
    }
}

创建字符串输出节点。

using UnityEngine.UIElements;
using UnityEditor.Experimental.GraphView;

public class StringNode : SampleNode
{
    private TextField textField;
    public string Text { get { return textField.value; } }

    public StringNode() : base()
    {
        title = "String";

        var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(string));
        outputContainer.Add(outputPort);

        textField = new TextField();
        mainContainer.Add(textField);
    }
}

最后,创建根节点。

由于根节点不消失,请消除相应的委托。

using UnityEditor.Experimental.GraphView;

public class RootNode : SampleNode
{
    public RootNode() : base()
    {
        title = "Root";

        capabilities -= Capabilities.Deletable;

        var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
        outputPort.portName = "Out";
        outputContainer.Add(outputPort);
    }
}

在生成图形视图时,就立刻放置一个根节点。

root = new RootNode();
AddElement(root);

选择并创建任何节点

使用搜索窗口,可以轻松地创建允许选择节点的 UI。
 

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Experimental.GraphView;

public class SampleSearchWindowProvider : ScriptableObject, ISearchWindowProvider
{
    private SampleGraphView graphView;

    public void Initialize(SampleGraphView graphView)
    {
        this.graphView = graphView;
    }

    List<SearchTreeEntry> ISearchWindowProvider.CreateSearchTree(SearchWindowContext context)
    {
        var entries = new List<SearchTreeEntry>();
        entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node")));

        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (var type in assembly.GetTypes())
            {
                if (type.IsClass && !type.IsAbstract && (type.IsSubclassOf(typeof(SampleNode)))
                    && type != typeof(RootNode))
                {
                    entries.Add(new SearchTreeEntry(new GUIContent(type.Name)) { level = 1, userData = type });
                }
            }
        }

        return entries;
    }

    bool ISearchWindowProvider.OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
    {
        var type = searchTreeEntry.userData as System.Type;
        var node = Activator.CreateInstance(type) as SampleNode;
        graphView.AddElement(node);
        return true;
    }
}

创建搜索树将继承示例节点类的类返回到搜索树条目,并处理OnSelectEntry 中选择的项。
右键单击更改您创建节点的位置,以调用搜索窗口。

using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

public class SampleGraphView : GraphView
{
    public RootNode root;
    
    public SampleGraphView()
    {
        SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);

        Insert(0, new GridBackground());
        
        //AddElement(new SampleNode());
        this.AddManipulator(new SelectionDragger());
        
        var searchWindowProvider = ScriptableObject.CreateInstance<SampleSearchWindowProvider>();
        searchWindowProvider.Initialize(this);
        
        root = new RootNode();
        AddElement(root);

        nodeCreationRequest += context =>
        {
            SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchWindowProvider);
        };
    }

    public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
    {
        return ports.ToList();
    }
}

现在,就可以选择和创建任何节点。

确保只有正确的端口才能彼此连接

由于 UI 实现可以连接所有节点,因此我们将修复它。

    public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
    {
        var compatiblePorts = new List<Port>();
        foreach (var port in ports.ToList())
        {
            if (startAnchor.node == port.node ||
                startAnchor.direction == port.direction ||
                startAnchor.portType != port.portType)
            {
                continue;
            }

            compatiblePorts.Add(port);
        }
        return compatiblePorts;
    }

现在

  • 无法连接到同一节点
  • 从输入到输入,从输出到输出,不连接
  • 端口上配置的类型不匹配,无法连接

实际使用节点进行处理

实际处理时,必须按顺序获取连接到根节点的节点并执行操作。
由于没有获取连接到节点的另一个节点的功能,因此在生成端口时必须缓存它。
此外,由于我们希望在节点端描述处理,因此我们将让进程节点具有用于处理的方法。

using UnityEditor.Experimental.GraphView;

public abstract class ProcessNode : SampleNode
{
    public Port InputPort;
    public Port OutputPort;

    public ProcessNode()
    {
        InputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
        InputPort.portName = "In";
        inputContainer.Add(InputPort);

        OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
        OutputPort.portName = "Out";
        outputContainer.Add(OutputPort);
    }

    public abstract void Execute();
}
using System.Linq;
using UnityEngine;
using UnityEditor.Experimental.GraphView;

public class LogNode : ProcessNode
{
    private Port inputString;

    public LogNode() : base()
    {
        title = "Log";

        inputString = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(string));
        inputContainer.Add(inputString);
    }

    public override void Execute()
    {
        var edge = inputString.connections.FirstOrDefault();
        var node = edge.output.node as StringNode;

        if (node == null) return;

        Debug.Log(node.Text);
    }
}
using UnityEditor.Experimental.GraphView;

public class RootNode : SampleNode
{
    public Port OutputPort;

    public RootNode() : base()
    {
        title = "Root";

        capabilities -= Capabilities.Deletable;

        OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
        OutputPort.portName = "Out";
        outputContainer.Add(OutputPort);
    }
}

现在,我们从根开始获取节点并执行操作。

using System.Linq

-------------------------------------

    public void Execute()
    {
        var rootEdge = root.OutputPort.connections.FirstOrDefault();
        if (rootEdge == null) return;

        var currentNode = rootEdge.input.node as ProcessNode;

        while (true)
        {
            currentNode.Execute();

            var edge = currentNode.OutputPort.connections.FirstOrDefault();
            if (edge == null) break;

            currentNode = edge.input.node as ProcessNode;
        }
    }

绘制一个按钮,执行此方法。

    void OnEnable()
    {
        var graphView = new SampleGraphView()
        {
            style = { flexGrow = 1 }
        };
        rootVisualElement.Add(graphView);

        rootVisualElement.Add(new Button(graphView.Execute) { text = "Execute" });
    }

执行结果如下:

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Editor扩展是指通过编写代码来扩展Unity编辑器的功能。通过创建自定义的编辑器窗口、工具栏按钮、菜单项和面板等,开发者可以为自己的项目添加一些定制化功能,以提高开发效率和用户体验。 Unity提供了一套API来实现编辑器扩展,开发者可以利用这些API去创建自定义的编辑器界面。首先,我们需要创建一个继承自EditorWindow或Editor类的脚本,然后在这个脚本中实现我们想要的功能。比如,我们可以在自定义的编辑器窗口中创建一些GUI元素,如按钮、文本框、下拉菜单等,用于控制场景中的对象、调整参数或执行特定的操作。 另外,Unity还提供了一些常用的工具类,如SerializedObject、SerializedProperty等,以便开发者可以访问和修改Unity对象的属性。使用这些工具类,我们可以在编辑器扩展中实现对象的序列化、反序列化和检查等功能。 在开发过程中,Unity的编辑器扩展还可以与自定义的脚本进行交互。通过注册自定义的菜单项、工具栏按钮或快捷键,我们可以在编辑器中快速调用脚本的功能,并在自定义界面中显示脚本的运行结果。 总结来说,Unity Editor扩展是一种强大的工具,它可以帮助开发者提高开发效率和用户体验。通过编写代码,我们可以创建出各种各样的自定义编辑器界面,以满足不同项目的需求。无论是增加交互性、优化工作流程还是增加特定的功能,Unity的编辑器扩展都能提供灵活和强大的解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值