前言:

本文主要讲解Unity编辑器中节点编辑器的创建使用。


知识点:

1.在自定义窗口内点击显示菜单项

Unity使用EditorWindow类制作GM测试工具 unity_editor_结点

使用GenericMenu(通用菜单):

注意:这是一个编辑器类,如果想使用它你需要把它放到工程目录下的Assets/Editor文件夹下。编辑器类在UnityEditor命名空间下。所以当使用C#脚本时,你需要在脚本前面加上 "using UnityEditor"引用。

函数:

AddIten:添加一个项目到菜单;

参数:function AddItem ( GUIContent 格式, on : boolean, 点击菜单项回调, 回调的参数) : void

其中第二个参数控制如下显示:

Unity使用EditorWindow类制作GM测试工具 unity_editor_结点_02

AddDisabledItem:添加一个禁用项目到菜单;

AddSeparator:添加一个分隔条项目到菜单;

GetItemCount:获取菜单中的项目数;

ShowAsContext: 显示鼠标下方的菜单;

DropDown:在给定屏幕矩形位置显示菜单。




Unity使用EditorWindow类制作GM测试工具 unity_editor_结点_03



GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Add Input"), false, MenuCallback, MenuType.Input);
menu.AddItem(new GUIContent("Add Output"), false, MenuCallback, MenuType.Output);
menu.AddItem(new GUIContent("Add Cale"), false, MenuCallback, MenuType.Cale);
menu.AddItem(new GUIContent("Add Comp"), false, MenuCallback, MenuType.Comp);

menu.ShowAsContext();

e.Use();



2.窗口里的弹出窗口(本案例核心)

EditorWindow.BeginWindows 开始窗口

WindowRect = GUI.Window(id,窗口Rect, 绘制完回调, 弹出窗口名);

EditorWindow.EndWindows (); 结束窗口


3.设置弹出窗口可拖动

GUI.DragWindow(); 


4.关于绘制贝塞尔曲线,可以查看另一片关于Handle的·文章:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

项目代码:

结点基类:

public class BaseNode
{
    public string windowTitle; //结点窗口标题
    public Rect WindowRect; //窗口框

    public virtual void DrawWindow()
    {
        windowTitle = EditorGUILayout.TextField("Title", windowTitle); 
    }

    public virtual void SetInput(InputNode inputNode, Vector2 mousePos)
    {
        
    }

    public virtual void DrawBezier()
    {
    }

    public virtual void DeleteNode(BaseNode node)
    {
    }
}

然后所有类型的结点窗口都继承自这个类:


1.输入节点:(如下图效果)

Unity使用EditorWindow类制作GM测试工具 unity_editor_System_04

using System;
using UnityEngine;
using System.Collections;
using UnityEditor;

public enum InputType //输入类型
{
    Number, //数字
    RandomNumber //随机数
}
public class InputNode : BaseNode 
{
    // 获取用户选择的输入类型>
    private InputType type = InputType.Number;

    //获取输入的值
    private string inputValue = "";

    // 获取随机值
    private string rFrom;
    private string rTo;

    private string resultNumber; //结果数字

    public InputNode ()
    {
        windowTitle = "InputNode";
    }

    public override void DrawWindow () //绘制窗口
    {
        base.DrawWindow();
        type = (InputType) EditorGUILayout.EnumPopup("Input Type", type);

        if (type == InputType.Number)
        {
            inputValue = EditorGUILayout.TextField("Value", inputValue);
            resultNumber = inputValue;
        }
        else
        {
            rFrom = EditorGUILayout.TextField("From", rFrom);
            rTo = EditorGUILayout.TextField("To", rTo);

            if (GUILayout.Button("Create Random Number"))
            {
                CreateRandomNumber();
            }
        }
    }

    //创建随机数
    private void CreateRandomNumber()
    {
        int from = 0;
        int to = 0;
        Int32.TryParse(rFrom, out from);
        Int32.TryParse(rTo, out to);
        Debug.Log(from + "||" + to);

        resultNumber = UnityEngine.Random.Range(from, to) + "";
        Debug.Log(resultNumber);
    }

    //获取得到的数字
    public string GetResult()
    {
        return resultNumber;
    }
}



2.输出节点:

Unity使用EditorWindow类制作GM测试工具 unity_editor_结点_05

using UnityEngine;
using System.Collections;
using UnityEditor;

public class OutputNode : BaseNode
{
    private string input1Value;

    //持有输入结点的引用
    private InputNode inputNode;

    //OutputNode 的 Input1 输入框的矩形
    private Rect input1Rect;

    public OutputNode ()
    {
        windowTitle = "OutputNode";
    }

    public override void DrawWindow ()
    {
        base.DrawWindow();

        if (inputNode != null)
        {
            input1Value = inputNode.GetResult();//获取输入的数字
        }
        input1Value = EditorGUILayout.TextField("Input1:", input1Value);

        if (Event.current.type == EventType.Repaint)
        {
            input1Rect = GUILayoutUtility.GetLastRect();
        }
    }

    public override void SetInput(InputNode inputNode, Vector2 mousePos)
    {
        mousePos.x -= WindowRect.x;
        mousePos.y -= WindowRect.y;

        //获取我们的输入结点的引用
        //如果我们的鼠标点击在了OutputNode 的 input1 的文本框的 Rect 中时 执行的操作
        if (input1Rect.Contains(mousePos))
        {
            Debug.Log("Enter");
            //将输入结点的引用给OutputNode
            this.inputNode = inputNode;
        }

        inputNode = null;
    }

    public override void DrawBezier()
    {
        if (inputNode != null)
        {
            Rect rect = input1Rect;
            rect.x = rect.x + WindowRect.x;
            rect.y = rect.y + WindowRect.y;
            rect.width = 1;
            rect.height = 1;

            //rect.x += WindowRect.x 简化的写法
            NodeEditorWindow.DrawBezier(inputNode.WindowRect, rect);
        }
    }
    public override void DeleteNode(BaseNode node)
    {
        if (inputNode == node)
        {
            inputNode = null;
        }
    }
}



3.计算结点

Unity使用EditorWindow类制作GM测试工具 unity_editor_ide_06

using UnityEngine;
using System.Collections;
using UnityEditor;

public enum CalcType //计算方式
{
    Greater, 
    Less,
    Equal
}
public class CaleNode : BaseNode 
{
    private CalcType caleType = CalcType.Greater;

    private string input1Value;
    private string input2Value;

    public CaleNode ()
    {
        windowTitle = "CaleNode";
    }

    public override void DrawWindow ()
    {
        base.DrawWindow();
        caleType = (CalcType)EditorGUILayout.EnumPopup("Calculation Type", caleType);
        input1Value = EditorGUILayout.TextField("input1", input1Value);
        input2Value = EditorGUILayout.TextField("input2", input2Value);
    }
}



4.比较结点

Unity使用EditorWindow类制作GM测试工具 unity_editor_System_07

using UnityEngine;
using System.Collections;
using UnityEditor;

public enum CompType //比较方式
{
    Greater,
    Less,
    Equal
}

public class CompNode : BaseNode
{
    private CompType compType = CompType.Greater;

    private string input1Value;
    private string input2Value;

    private InputNode inputNode1;

    private InputNode inputNode2;

    /// <summary>
    /// OutputNode 的 Input1 输入框的矩形
    /// </summary>
    private Rect input1Rect;

    /// <summary>
    /// OutputNode 的 Input2 输入框的矩形
    /// </summary>
    private Rect input2Rect;

    public CompNode ()
    {
        windowTitle = "CompNode";
    }

    public override void DrawWindow()
    {
        base.DrawWindow();
        compType = (CompType) EditorGUILayout.EnumPopup("Calculation Type", compType);

        if (inputNode1 != null)
        {
            input1Value = inputNode1.GetResult();
        }

        input1Value = EditorGUILayout.TextField("input1", input1Value);
        if (Event.current.type == EventType.Repaint)
        {
            input1Rect = GUILayoutUtility.GetLastRect();
        }
        if (inputNode2 != null)
        {
            input2Value = inputNode2.GetResult();
        }
        input2Value = EditorGUILayout.TextField("input2", input2Value);
        if (Event.current.type == EventType.Repaint)
        {
            input2Rect = GUILayoutUtility.GetLastRect();
        }
    }

    public override void SetInput (InputNode inputNode, Vector2 mousePos)
    {
        mousePos.x -= WindowRect.x;
        mousePos.y -= WindowRect.y;

        //获取我们的输入结点的引用
        //如果我们的鼠标点击在了OutputNode 的 input1 的文本框的 Rect 中时 执行的操作
        if (input1Rect.Contains(mousePos))
        {
            Debug.Log("Enter");
            //将输入结点的引用给OutputNode
            this.inputNode1 = inputNode;
        }

        if (input2Rect.Contains(mousePos))
        {
            Debug.Log("Enter");
            //将输入结点的引用给OutputNode
            this.inputNode2 = inputNode;
        }
    }

    public override void DrawBezier ()
    {
        if (inputNode1 != null)
        {
            Rect rect = input1Rect;
            rect.x = rect.x + WindowRect.x;
            rect.y = rect.y + WindowRect.y;
            rect.width = 1;
            rect.height = 1;

            //rect.x += WindowRect.x 简化的写法
            NodeEditorWindow.DrawBezier(inputNode1.WindowRect, rect);
        }

        if (inputNode2 != null)
        {
            Rect rect = input2Rect;
            rect.x = rect.x + WindowRect.x;
            rect.y = rect.y + WindowRect.y;
            rect.width = 1;
            rect.height = 1;

            //rect.x += WindowRect.x 简化的写法
            NodeEditorWindow.DrawBezier(inputNode2.WindowRect, rect);
        }
    }
    public override void DeleteNode (BaseNode node)
    {
        if (inputNode1 == node)
        {
            inputNode1 = null;
        }

        if (inputNode2 == node)
        {
            inputNode2 = null;
        }
    }
}



结点类准备好了,现在来绘制窗口:

继承自EditorWindow的类编辑窗口:

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

public enum MenuType //菜单类型
{
    Input,
    Output,
    Cale,
    Comp,
    Delete,
    Line
}
public class NodeEditorWindow : EditorWindow
{
    /// <summary>
    /// 窗口容器,用于存放窗口
    /// </summary>
    private List<BaseNode> windows = new List<BaseNode>();

    /// <summary>
    /// 判断是否点击在窗口上
    /// </summary>
    private bool isClickedOnWindow = false;

    /// <summary>
    /// 当前选中的窗口的下标
    /// </summary>
    private int selectedIndex = -1;

    /// <summary>
    /// 当前鼠标的位置
    /// </summary>
    private Vector2 mousePos;

    /// <summary>
    /// 判断当前是否为画线模式
    /// </summary>
    private bool isDrawLineModel = false;

    /// <summary>
    /// 当前选中的Node
    /// </summary>
    private BaseNode selectNode;

    private BaseNode drawModeSelectedNode;

    [MenuItem("Tool/My Node Editor")]
    static void OpenWindow() //打开窗口
    {
        GetWindow<NodeEditorWindow>();
    }

    //开始绘制
    void OnGUI()
    {
        //1.获取当前事件
        Event e = Event.current;
        //获取鼠标的位置
        mousePos = e.mousePosition;

        //2.创建我们的菜单
        //当我们按下鼠标右键时,执行的操作
        if (e.button == 1 && e.isMouse && !isDrawLineModel )
        {
            CreateMenu(e); //创建菜单
        }
        //在画线状态下点击鼠标左键时执行的操作
        else if (e.button == 0 && e.isMouse && isDrawLineModel)
        {
            //找到画线模式下选中的结点
            FoundSelectedWindow();
            drawModeSelectedNode = windows[selectedIndex];

            
            if (isClickedOnWindow && drawModeSelectedNode != null)
            {
                //1.否则,将输入结点的引用给输出结点
                drawModeSelectedNode.SetInput((InputNode)selectNode,mousePos);
                //isDrawLineModel = false;
                //selectNode = null;
                //2.将线给连上
            }
            //else
            //{
            //    isDrawLineModel = false;
            //    selectNode = null;
            //}

            //1.当我们点击的位置不在窗口上时,我们停止画线
            //2.当我们点击在窗口上时,判断是否点击的时同一个窗口
            //如果点击的是同一个窗口的话,那么我们也停止画线
            //if (!isClickedOnWindow || drawModeSelectedNode == selectNode)
            //{
            //    isDrawLineModel = false;
            //    selectNode = null;
            //}
            isDrawLineModel = false;
            selectNode = null;
        }


        //画线功能
        if (isDrawLineModel && selectNode != null)
        {
            //2.找到结束的位置(矩形)
            Rect endRect = new Rect(mousePos, new Vector2(10, 10));
            DrawBezier(selectNode.WindowRect, endRect);

            Repaint();
        }

        //维护画线功能
        for (int i = 0; i < windows.Count; i++)
        {
            windows[i].DrawBezier();
        }

        BeginWindows(); //开始绘制弹出窗口

        for (int i = 0; i < windows.Count; i++)
        {
            windows[i].WindowRect = GUI.Window(i, windows[i].WindowRect, WindowCallback, windows[i].windowTitle);
        }

        EndWindows();//结束绘制弹出窗口
    }

    private void CreateMenu (Event e)
    {
        FoundSelectedWindow(); //尝试寻找点击的窗体

        //当我们点击在窗口的时候,我们可以删除窗口和画线
        if (isClickedOnWindow)
        {
            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("Delete Node"), false, MenuCallback, MenuType.Delete);
            menu.AddItem(new GUIContent("Draw Line"), false, MenuCallback, MenuType.Line);

            menu.ShowAsContext();

            e.Use();

            isClickedOnWindow = false;
        }
        //当我们点击在窗口外时,可以创建结点
        else
        {
            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("Add Input"), false, MenuCallback, MenuType.Input);
            menu.AddItem(new GUIContent("Add Output"), false, MenuCallback, MenuType.Output);
            menu.AddItem(new GUIContent("Add Cale"), true, MenuCallback, MenuType.Cale);
            menu.AddItem(new GUIContent("Add Comp"), false, MenuCallback, MenuType.Comp);

            menu.ShowAsContext();

            e.Use();
        }

    }

    //设置选择的窗体
    private void FoundSelectedWindow ()
    {
        for (int i = 0; i < windows.Count; i++)
        {
            if (windows[i].WindowRect.Contains(mousePos))
            {
                Debug.Log(i);
                isClickedOnWindow = true;
                selectedIndex = i;
                break;
            }
            else
            {
                isClickedOnWindow = false;
            }
        }
    }

    //弹出窗口绘制完后绘制窗口里内容
    private void WindowCallback(int id)
    {
        windows[id].DrawWindow();
        GUI.DragWindow(); //设置窗口可拖动
    }

    private void MenuCallback (object type)
    {
        Debug.Log("Enter!!!" + ((MenuType)type).ToString());
        switch ((MenuType)type)
        {
            //在鼠标位置创建指定大小的小窗口
            case MenuType.Input:
                InputNode input = new InputNode();
                input.WindowRect = new Rect(mousePos.x,mousePos.y,200,150);
                windows.Add(input);
                break;
            case MenuType.Output:
                OutputNode output = new OutputNode();
                output.WindowRect = new Rect(mousePos.x, mousePos.y, 200, 150);
                windows.Add(output);
                break;
            case MenuType.Cale:
                CaleNode cale = new CaleNode();
                cale.WindowRect = new Rect(mousePos.x, mousePos.y, 200, 150);
                windows.Add(cale);
                break;
            case MenuType.Comp:
                CompNode comp = new CompNode();
                comp.WindowRect = new Rect(mousePos.x, mousePos.y, 200, 150);
                windows.Add(comp);
                break;
            case MenuType.Delete:
                //删除对应的子窗口
                for (int i = 0; i < windows.Count; i++)
                {
                    windows[i].DeleteNode(windows[selectedIndex]);
                }

                windows.RemoveAt(selectedIndex);
                break;
            case MenuType.Line:
                //写我们的画线逻辑
                FoundSelectedWindow();
                //1.找到开始的位置(矩形)
                selectNode = windows[selectedIndex];
                //2.切换当前模式为画线模式
                isDrawLineModel = true;
                break;
            default:
                throw new ArgumentOutOfRangeException("type", type, null);
        }
    }

    public static void DrawBezier(Rect start, Rect end)
    {
        Vector3 startPos = new Vector3(start.x + start.width/2, start.y + start.height/2, 0);
        Vector3 endPos = new Vector3(end.x + end.width / 2, end.y + end.height / 2, 0);
        Vector3 startTan = startPos + Vector3.right*50;
        Vector3 endTan = endPos + Vector3.left * 50;
        Color shadow = new Color(0,0,0,0.7f);

        for (int i = 0; i < 5; i++)
        {
            Handles.DrawBezier(startPos, endPos, startTan, endTan, shadow, null, 1+(i*2));
        }

        Handles.DrawBezier(startPos,endPos,startTan,endTan,Color.black, null,1);
    }
}



效果:

Unity使用EditorWindow类制作GM测试工具 unity_editor_System_08