zoukankan      html  css  js  c++  java
  • Unity开发笔记-Editor扩展用GraphView实现逻辑表达式(1)UI基础逻辑实现

    写在前面

    Unity的官方文档对graphview的api只有粗略描述,想要通过API来理解GraphView如何搭建,是非常低效和让人抓狂的。
    也许是因为是实验API的关系,但个人感觉Unity的其他API也需要大量借助其他非官方资料和开源项目才能理解。

    我直接参考了如下博客:

    https://qiita.com/ma_sh/items/7627a6151e849f5a0ede
    日语可以通过谷歌翻译大概可以明白,非常值得一读的教程。

    开源项目:
    https://github.com/rygo6/GTLogicGraph

    下面进入正题:

    0 实现GraphView子类

    构造函数中,将EditorWindow作为参数传入以便后面使用
    另外我们需要添加一些功能函数
    SetupZoom实现滚轮缩放
    AddManipulator函数可以添加GraphView的操作功能。
    1.ContentDragger 按住Alt键可以拖动窗口范围,参考Animator的window功能
    2.RectangleSelector 多框选功能,一次选中多个Node,玩过rts的都知道

    3.SelectionDragger 选中Node移动功能,否则不能通过鼠标拖动改变node的位置

      `
      public class YaoJZGraphView : GraphView
    {
            public YaoJZGraphView(EditorWindow editorWindow)
        {
            _editorWindow = editorWindow;
    
            //按照父级的宽高全屏填充
            this.StretchToParentSize();
            //滚轮缩放
            SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
            //graphview窗口内容的拖动
            this.AddManipulator(new ContentDragger());
            //选中Node移动功能
            this.AddManipulator(new SelectionDragger());
              //多个node框选功能
            this.AddManipulator(new RectangleSelector());
            }
      }
      `
    

    1 实现EditorWindow子类,将GraphView添加到rootVisualElement中

    我们需要一个EditorWindow子类来显示window,这一步和其他EditorWindow的的扩展没有任何差别。
    然后将上面的GraphView子类YaoJZGraphView通过EditorWindow的rootVisualElement.Add()方法添加到EidtorWindow中。
    编写一个静态方法,打上MenuItem标签,就可以在编辑器中显示出来了。

      `
      public class YaoJZGraphEditorWindow:EditorWindow
    {
      private YaoJZGraphView _graphView;
      public void Init()
        {
            _graphView = new YaoJZGraphView(this);
            rootVisualElement.Add(_graphView);
        }
    
      [MenuItem("YJZ/GraphWindow")]
        public static void Open()
        {
            YaoJZGraphEditorWindow window = GetWindow<YaoJZGraphEditorWindow>(ObjectNames.NicifyVariableName(nameof(YaoJZGraphEditorWindow)));
            window.Init();
        }
            }`
    

    2 实现第一个Node子类

    现在我们为GraphView实现第一个子类,既然是表达式编辑器,那我们就实现一个FloatNodeView,用来表示一个浮点型数值节点。

      `public class YaoJZFloatNodeView:Node
    {
     public YaoJZFloatNodeView()
        {
            title = "Float"; 
      }
      }`
    

    3 AddElement添加Node到GraphView中

    将我们实现的YaoJZFloatNodeView子类通过AddElement方法添加到GraphView中,为了简单起见,直接在构造函数里添加。

        `public class YaoJZGraphView : GraphView
    {
            public YaoJZGraphView(EditorWindow editorWindow)
        {
            _editorWindow = editorWindow;
                AddElement(new YaoJZFloatNodeView());            //将node添加到graphview
            }
      }`
    

    4 添加右键菜单,实现ISearchWindowProvider接口

    当然我们的Node不可能直接写死在GraphView的构造函数里,我们希望通过右键菜单的形式添加一个Node节点,幸好我们可以实现ISearchWindowProvider接口做到这点

    4.1 实现Node显示列表接口CreateSearchTree

    右键菜单中的每个选项都是一个SearchTreeEntry,在这个接口中添加我们需要显示的所有Node类型
    另外也可以添加SearchTreeGroupEntry,实现多级菜单功能

      `public class YaoJZSearchMenuWindowProvider:ScriptableObject, ISearchWindowProvider
    {
      public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
        {
            var entries = new List<SearchTreeEntry>();
      entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node")));                        //添加了一个一级菜单
        
            entries.Add(new SearchTreeGroupEntry(new GUIContent("Example")) { level = 1 });      //添加了一个二级菜单
       entries.Add(new SearchTreeEntry(new GUIContent("float")) { level = 2, userData = typeof(YaoJZFloatNodeView) });
      return entries;
      }
        }`
    

    4.2 实现选中回调OnSelectEntry

    当我们在右键菜单中点击了SearchTreeEntry就会触发这个回调,所以我们利用这个函数的回调,实现往GraphView中添加Node的功能。
    这样的话YaoJZSearchMenuWindowProvider需要引用YaoJZGraphView,这样就产生了耦合。
    为了解耦,我们可以实现一个delegate,添加Node的逻辑在YaoJZGraphView中处理了。

       `public class YaoJZSearchMenuWindowProvider:ScriptableObject, ISearchWindowProvider
    {
      public delegate bool SerchMenuWindowOnSelectEntryDelegate(SearchTreeEntry searchTreeEntry,            //声明一个delegate类
            SearchWindowContext context);
    
        public SerchMenuWindowOnSelectEntryDelegate OnSelectEntryHandler;                              //delegate回调方法
    
            public bool OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
        {
            if (OnSelectEntryHandler == null)
            {
                return false;
            }
            return OnSelectEntryHandler(searchTreeEntry, context);
        }
    
        }`
    

    4.3 在YaoJZGraphView 中实例化YaoJZSearchMenuWindowProvider

    实现nodeCreationRequest回调方法,打开SearchWindow
    是的没错,我们的右键菜单是一个SearchWindow实例,而我们实现的YaoJZSearchMenuWindowProvider是他的数据提供者
    我们需要实例化YaoJZSearchMenuWindowProvider然后作为SearchWindowContext的参数传给SearchWindow
    然后绑定之前实现的delegate方法OnSelectEntryHandler,方法的参数是searchTreeEntry,
    我们通过userData属性获得之前传入的Node的Type类型,然后使用反射创建Node实例,
    并用AddElement添加到GraphView中
    这样右键功能就实现了

    YaoJZGraphView.cs类

      `public class YaoJZGraphView : GraphView
    {
      public YaoJZGraphView(EditorWindow editorWindow)
        {
            _editorWindow = editorWindow;
    
      var menuWindowProvider = ScriptableObject.CreateInstance<YaoJZSearchMenuWindowProvider>();
            menuWindowProvider.OnSelectEntryHandler = OnMenuSelectEntry;
            
            nodeCreationRequest += context =>
            {
                SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), menuWindowProvider);
            };
    
      }
    
      private bool OnMenuSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
        {
            var type = searchTreeEntry.userData as Type;
            Node node = Activator.CreateInstance(type) as Node;
            this.AddElement(node);
            
            return true;
        }
      `
    

    5 为Node添加Port

    没有Port的Node是孤单的,Node通过Port和其他Node相连,Port有2个重要的属性Direction和Capacity
    Direction:定义了Port是输入还是输出端口
    portName:在UI上显示Port的名称,注意:还有title和name属性,设置值后都不会在UI上显示出来
    capacity:端口的连线是单个(Port.Capacity.Single)还是多个(Port.Capacity.Multi),连线对应的是Edge类。
    通过这个属性我们可以让Port实现一对一,一对多,多对多的连接组合
    这个例子里的Port都是Single类型的

    下面我们创建一个输入port和一个输出port:

      `
      //创建一个inputPort
      var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(Port));
      //设置port显示的名称
      inputPort.portName = "in";
      //添加到inputContainer容器中
      inputContainer.Add(inputPort);
    
      var outPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(Port));
      outPort.portName = "out";    
      outputContainer.Add(outPort);
    
      RefreshExpandedState();
      `
    

    Node有几个重要的Container,inputContainer,outputContainer是port的容器
    你当然也可以将outputport放入到inputContainer,对Node来说,port是input还是output都是UI的Element

    而Node的内容容器是mainContainer,后面我们将Node的扩展功能放入mainContainer容器中。

    6 Port的连接

    现在你会发现Port之间无法用线连在一起,我们需要覆写GraphView中的GetCompatiblePorts方法:

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

    现在可以连接2个Node了,但是现在Node自己的input可以和output也能相连,这不是我们想要的,我们需要改写一下GetCompatiblePorts的逻辑。
    通过GetCompatiblePorts接口我们定义具体的port连接规则,比如
    1.2个port如果是属于同一个node,则无法连接。
    2.Direction相同的Port无法相互连接,input和input,output和output不能连接
    3.portType不匹配的无法连接
    4.其他和业务相关的逻辑检测

    下面我们改写一下:

      `              
      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;
        }`
    

    好的,到此为止Node显示以及Node的Port之间的连接功能完成了,下一个教程我们扩展Node,实现表达式的各个节点功能

  • 相关阅读:
    linux weblogic的sh文件
    linux 安装weblogic(转载)
    linux 安装jdk
    linux 用户和用户组
    测试开发工程师必备软硬能力&高级测试开发工程师需要具备什么能力?
    postman强大的团队协作功能
    requests(一): 发送一个json格式的post请求
    python操作Excel模块openpyxl
    appium环境安装app自动化
    夜神模拟器怎么连接adb
  • 原文地址:https://www.cnblogs.com/terrynoya/p/14087817.html
Copyright © 2011-2022 走看看