前言:
本文主要讲解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的·文章:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
项目代码:
结点基类:
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);
}
}
效果: