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:在給定螢幕矩形位置顯示選單。
2.窗口裡的彈出視窗(本案例核心)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();
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,本人學習了一下稍微改動了一下,感謝樓主的無私奉獻,請點選連線檢視原文,尊重樓主版權。