1. 程式人生 > >Unity Editor 基礎篇(十一):結點編輯器基礎

Unity Editor 基礎篇(十一):結點編輯器基礎

轉自:http://mp.weixin.qq.com/s/CV_UTPMsWmz5w0gSOIPyFQ,請點選連線檢視原文,尊重樓主版權。

前言:

本文主要講解Unity編輯器中節點編輯器的建立使用。

知識點:

1.在自定義視窗內點選顯示選單項


使用GenericMenu(通用選單):

注意:這是一個編輯器類,如果想使用它你需要把它放到工程目錄下的Assets/Editor資料夾下。編輯器類在UnityEditor名稱空間下。所以當使用C#指令碼時,你需要在指令碼前面加上 "using UnityEditor"引用。

函式:

AddIten:新增一個專案到選單;

引數:function AddItem ( GUIContent 格式, on : boolean, 點選選單項回撥, 回撥的引數) : void

其中第二個引數控制如下顯示:


AddDisabledItem:新增一個禁用專案到選單;

AddSeparator:新增一個分隔條專案到選單;

GetItemCount:獲取選單中的專案數;

ShowAsContext: 顯示滑鼠下方的選單;

DropDown:在給定螢幕矩形位置顯示選單。


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的·文章:http://blog.csdn.net/qq_33337811/article/details/64571782
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

專案程式碼:

結點基類:

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.輸入節點:(如下圖效果)


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.輸出節點:

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.計算結點

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.比較結點

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

效果:

結束語:

本文轉自:http://mp.weixin.qq.com/s/CV_UTPMsWmz5w0gSOIPyFQ,本人學習了一下稍微改動了一下,感謝樓主的無私奉獻,請點選連線檢視原文,尊重樓主版權。