在Unity中创建基于节点的编辑器

1.创建窗口(EditorWindow)

首先绘制元素,然后处理输入,如果GUI由于输入事件而改变,则强制窗口重新绘制。

using UnityEngine;
using UnityEditor;
using System. Collections. Generic;
public class NodeBasedEditor : EditorWindow
{
[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}
private void OnGUI()
{
DrawNodes();
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}
private void DrawNodes()
{
}
private void ProcessEvents( Event e)
{
}
}


2.创建节点

因为这是一个节点编辑器,它应该包含一个节点列表,这需要我们定义一个List <Node>。但首先我们应该定义Node类。一个节点将负责绘制本身和处理它自己的事件。不像ProcessEvents(事件e)NodeBasedEditorProcessEvents(事件E)节点将返回一个布尔值,这样我们就可以检查我们是否应该重新绘制GUI与否。

using System;
using UnityEditor;
using UnityEngine;
public class Node
{
public Rect rect;
public string title;
public GUIStyle style;
public Node( Vector2 position, float width, float height, GUIStyle nodeStyle)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
}
public void Drag( Vector2 delta)
{
rect. position += delta;
}
public void Draw()
{
GUI. Box( rect, title, style);
}
public bool ProcessEvents( Event e)
{
return false;
}
}

using UnityEngine;
using UnityEditor;
using System. Collections. Generic;
public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}
private void OnGUI()
{
DrawNodes();
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}
private void DrawNodes()
{
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Draw();
}
}
}
private void ProcessEvents( Event e)
{
}
}

创建节点

节点现在在编辑器中绘制,但如果我们不创建它们,我们就看不到它们。当用户在编辑器中右键单击时,我们应该显示一个带有“添加节点”项的上下文菜单。当用户单击“添加节点”时,我们将创建一个节点并将其添加到节点列表中,以便绘制它。节点需要的位置,宽度,高度和造型; 位置将是鼠标的当前位置,宽度将为200,高度将为50。

using UnityEngine;
using UnityEditor;
using System. Collections. Generic;
public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private GUIStyle nodeStyle;
[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}
private void OnEnable()
{
nodeStyle = new GUIStyle();
nodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1.png") as Texture2D;
nodeStyle. border = new RectOffset( 12, 12, 12, 12);
}
private void OnGUI()
{
DrawNodes();
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}
private void DrawNodes()
{
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Draw();
}
}
}
private void ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 1)
{
ProcessContextMenu( e. mousePosition);
}
break;
}
}
private void ProcessContextMenu( Vector2 mousePosition)
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Add node"), false, () => OnClickAddNode( mousePosition));
genericMenu. ShowAsContext();
}
private void OnClickAddNode( Vector2 mousePosition)
{
if ( nodes == null)
{
nodes = new List< Node>();
}
nodes. Add( new Node( mousePosition, 200, 50, nodeStyle));
}
}


使节点可拖动

好的,现在我们可以添加节点,但我们无法拖动它们。正如我前面提到的,节点将处理自己的事件,因此我们将在Node类中处理drag事件这里要注意的一件重要事情是我们应该使用Use()方法“使用”拖动事件稍后,我们将添加画布拖动,我们不希望同时拖动节点和整个画布(“使用”事件阻止其被其他进程使用,即它停止事件冒泡)。另请注意,ProcessNodeEvents(事件e)中的for循环向后遍历节点列表,因为最后一个节点是在顶部绘制的,因此它应该首先处理事件。

using System;
using UnityEditor;
using UnityEngine;
public class Node
{
public Rect rect;
public string title;
public bool isDragged;
public GUIStyle style;
public Node( Vector2 position, float width, float height, GUIStyle nodeStyle)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
}
public void Drag( Vector2 delta)
{
rect. position += delta;
}
public void Draw()
{
GUI. Box( rect, title, style);
}
public bool ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
if ( rect. Contains( e. mousePosition))
{
isDragged = true;
GUI. changed = true;
}
else
{
GUI. changed = true;
}
}
break;
case EventType. MouseUp:
isDragged = false;
break;
case EventType. MouseDrag:
if ( e. button == 0 && isDragged)
{
Drag( e. delta);
e. Use();
return true;
}
break;
}
return false;
}
}


public class NodeBasedEditor : EditorWindow{

。。。。。。。。。。

}

private void OnGUI()
{
DrawNodes();
ProcessNodeEvents( Event. current);
ProcessEvents( Event. current);

if ( GUI. changed) Repaint();
}
private void DrawNodes()
{
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Draw();
}
}
}
private void ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 1)
{
ProcessContextMenu( e. mousePosition);
}
break;
}
}
private void ProcessNodeEvents( Event e)
{
if ( nodes != null)
{
for ( int i = nodes. Count - 1; i >= 0; i--)
{
bool guiChanged = nodes[ i]. ProcessEvents( e);
if ( guiChanged)
{
GUI. changed = true;
}
}
}
}
    
private void ProcessContextMenu( Vector2 mousePosition)
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Add node"), false, () => OnClickAddNode( mousePosition));
genericMenu. ShowAsContext();
}

在节点之间创建连接

我们的节点编辑器现在有节点,但我们也应该能够连接它们。为此,我们需要节点上的两个连接点(输入和输出)以及它们之间的连接。连接点有一个矩形(这样我们可以绘制它),有一个类型(in或out),有一个样式,它引用它的父节点。因此,我们的ConnectionPoint类将是一个非常简单的类; 在特定位置绘制按钮,并在单击此按钮时执行操作。

另一方面,连接有两个连接点和一个删除它的操作。Connection类比ConnectionPoint简单得多,但它引入了一个新概念:Handles此类实际上用于在“ 场景”视图中绘制3D GUI控件,但它是唯一具有贝塞尔绘图方法的类:Handles.DrawBezier(Vector3,Vector3,Vector3,Vector3,Color,Texture2D,float)。它需要7个参数,前4个参数是位置控制(起始位置,结束位置,开始切线和结束切线),而其余参数确定贝塞尔曲线的外观。

using System;
using UnityEngine;
public enum ConnectionPointType { In, Out }
public class ConnectionPoint
{
public Rect rect;
public ConnectionPointType type;
public Node node;
public GUIStyle style;
public Action< ConnectionPoint> OnClickConnectionPoint;
public ConnectionPoint( Node node, ConnectionPointType type, GUIStyle style, Action< ConnectionPoint> OnClickConnectionPoint)
{
this. node = node;
this. type = type;
this. style = style;
this. OnClickConnectionPoint = OnClickConnectionPoint;
rect = new Rect( 0, 0, 10f, 20f);
}
public void Draw()
{
rect. y = node. rect. y + ( node. rect. height * 0.5f) - rect. height * 0.5f;
switch ( type)
{
case ConnectionPointType. In:
rect. x = node. rect. x - rect. width + 8f;
break;
case ConnectionPointType. Out:
rect. x = node. rect. x + node. rect. width - 8f;
break;
}
if ( GUI. Button( rect, "", style))
{
if ( OnClickConnectionPoint != null)
{
OnClickConnectionPoint( this);
}
}
}
}


using System;
using UnityEditor;
using UnityEngine;
public class Connection
{
public ConnectionPoint inPoint;
public ConnectionPoint outPoint;
public Action< Connection> OnClickRemoveConnection;
public Connection( ConnectionPoint inPoint, ConnectionPoint outPoint, Action< Connection> OnClickRemoveConnection)
{
this. inPoint = inPoint;
this. outPoint = outPoint;
this. OnClickRemoveConnection = OnClickRemoveConnection;
}
public void Draw()
{
Handles. DrawBezier(
inPoint. rect. center,
outPoint. rect. center,
inPoint. rect. center + Vector2. left * 50f,
outPoint. rect. center - Vector2. left * 50f,
Color. white,
null,
2f
);
if ( Handles. Button(( inPoint. rect. center + outPoint. rect. center) * 0.5f, Quaternion. identity, 4, 8, Handles. RectangleCap))
{
if ( OnClickRemoveConnection != null)
{
OnClickRemoveConnection( this);
}
}
}
}

绘图连接

由于  ConnectionConnectionPoint类已准备就绪,我们所要做的就是在Node类中绘制连接点并在NodeBasedEditor中绘制连接Node类的变化将是最小的; 我们将定义两个连接点,修改构造函数,以便我们可以为它们传递样式和操作,并在Draw()方法中绘制它们

但是,NodeBasedEditor需要进行重大修改。首先,我们需要为连接点定义样式。你可以为它们使用单​​一的样式,但我希望它们看起来不同,所以我将为每个样式使用单独的样式。我们将在OnEnable()中初始化这些样式,就像我们初始化节点样式一样。

其次,我们需要跟踪点击连接点,这样,当用户选择出来,我们应该创建它们之间的连接。此步骤包括大多数添加内容:

  • OnClickInPoint(ConnectionPoint)处理单击点。
  • OnClickOutPoint(ConnectionPoint)处理单击点。
  • OnClickRemoveConnection(连接)处理单击连接上的删除按钮。
  • 创建连接()创建一个当连接和一个点被选择。
  • ClearConnectionSelection()清除选定的点。

最后,我们需要在OnGUI()中绘制连接,就像我们绘制节点一样。

using System;
using UnityEditor;
using UnityEngine;
public class Node
{
public Rect rect;
public string title;
public bool isDragged;
public ConnectionPoint inPoint;
public ConnectionPoint outPoint;
public GUIStyle style;
public Node( Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action< ConnectionPoint> OnClickInPoint, Action< ConnectionPoint> OnClickOutPoint)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
inPoint = new ConnectionPoint( this, ConnectionPointType. In, inPointStyle, OnClickInPoint);
outPoint = new ConnectionPoint( this, ConnectionPointType. Out, outPointStyle, OnClickOutPoint);
}
public void Drag( Vector2 delta)
{
rect. position += delta;
}
public void Draw()
{
inPoint. Draw();
outPoint. Draw();
GUI. Box( rect, title, style);
}
public bool ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
if ( rect. Contains( e. mousePosition))
{
isDragged = true;
GUI. changed = true;
}
else
{
GUI. changed = true;
}
}
break;
case EventType. MouseUp:
isDragged = false;
break;
case EventType. MouseDrag:
if ( e. button == 0 && isDragged)
{
Drag( e. delta);
e. Use();
return true;
}
break;
}
return false;
}
}


using UnityEngine;
using UnityEditor;
using System. Collections. Generic;
public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private List< Connection> connections;
private GUIStyle nodeStyle;
private GUIStyle inPointStyle;
private GUIStyle outPointStyle;
private ConnectionPoint selectedInPoint;
private ConnectionPoint selectedOutPoint;
[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}
private void OnEnable()
{
nodeStyle = new GUIStyle();
nodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1.png") as Texture2D;
nodeStyle. border = new RectOffset( 12, 12, 12, 12);
        
inPointStyle = new GUIStyle();
inPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left.png") as Texture2D;
inPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left on.png") as Texture2D;
inPointStyle. border = new RectOffset( 4, 4, 12, 12);
outPointStyle = new GUIStyle();
outPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right.png") as Texture2D;
outPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right on.png") as Texture2D;
outPointStyle. border = new RectOffset( 4, 4, 12, 12);
}
private void OnGUI()
{
DrawNodes();
DrawConnections();
ProcessNodeEvents( Event. current);
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}
private void DrawNodes()
{
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Draw();
}
}
}
private void DrawConnections()
{
if ( connections != null)
{
for ( int i = 0; i < connections. Count; i++)
{
connections[ i]. Draw();
}
}
}
private void ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
ClearConnectionSelection();
}
if ( e. button == 1)
{
ProcessContextMenu( e. mousePosition);
}
break;
}
}
private void ProcessNodeEvents( Event e)
{
if ( nodes != null)
{
for ( int i = nodes. Count - 1; i >= 0; i--)
{
bool guiChanged = nodes[ i]. ProcessEvents( e);
if ( guiChanged)
{
GUI. changed = true;
}
}
}
}
    
private void ProcessContextMenu( Vector2 mousePosition)
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Add node"), false, () => OnClickAddNode( mousePosition));
genericMenu. ShowAsContext();
}
private void OnClickAddNode( Vector2 mousePosition)
{
if ( nodes == null)
{
nodes = new List< Node>();
}
nodes. Add( new Node( mousePosition, 200, 50, nodeStyle, inPointStyle, outPointStyle, OnClickInPoint, OnClickOutPoint));
}
private void OnClickInPoint( ConnectionPoint inPoint)
{
selectedInPoint = inPoint;
if ( selectedOutPoint != null)
{
if ( selectedOutPoint. node != selectedInPoint. node)
{
CreateConnection();
ClearConnectionSelection();
}
else
{
ClearConnectionSelection();
}
}
}
private void OnClickOutPoint( ConnectionPoint outPoint)
{
selectedOutPoint = outPoint;
if ( selectedInPoint != null)
{
if ( selectedOutPoint. node != selectedInPoint. node)
{
CreateConnection();
ClearConnectionSelection();
}
else
{
ClearConnectionSelection();
}
}
}
private void OnClickRemoveConnection( Connection connection)
{
connections. Remove( connection);
}
private void CreateConnection()
{
if ( connections == null)
{
connections = new List< Connection>();
}
connections. Add( new Connection( selectedInPoint, selectedOutPoint, OnClickRemoveConnection));
}
private void ClearConnectionSelection()
{
selectedInPoint = null;
selectedOutPoint = null;
}
}


选择节点

我们应该在用户点击某个节点时提供反馈,以便他们知道他们选择了哪个节点(或者根本就选择了一个节点)。当用户想要删除节点时,这将非常有用。

using System;
using UnityEditor;
using UnityEngine;
public class Node
{
public Rect rect;
public string title;
public bool isDragged;
public bool isSelected;
public ConnectionPoint inPoint;
public ConnectionPoint outPoint;
public GUIStyle style;
public GUIStyle defaultNodeStyle;
public GUIStyle selectedNodeStyle;
public Node( Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action< ConnectionPoint> OnClickInPoint, Action< ConnectionPoint> OnClickOutPoint)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
inPoint = new ConnectionPoint( this, ConnectionPointType. In, inPointStyle, OnClickInPoint);
outPoint = new ConnectionPoint( this, ConnectionPointType. Out, outPointStyle, OnClickOutPoint);
defaultNodeStyle = nodeStyle;
selectedNodeStyle = selectedStyle;
}
public void Drag( Vector2 delta)
{
rect. position += delta;
}
public void Draw()
{
inPoint. Draw();
outPoint. Draw();
GUI. Box( rect, title, style);
}
public bool ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
if ( rect. Contains( e. mousePosition))
{
isDragged = true;
GUI. changed = true;
isSelected = true;
style = selectedNodeStyle;
}
else
{
GUI. changed = true;
isSelected = false;
style = defaultNodeStyle;
}
}
break;
case EventType. MouseUp:
isDragged = false;
break;
case EventType. MouseDrag:
if ( e. button == 0 && isDragged)
{
Drag( e. delta);
e. Use();
return true;
}
break;
}
return false;
}
}


using UnityEngine;
using UnityEditor;
using System. Collections. Generic;
public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private List< Connection> connections;
private GUIStyle nodeStyle;
private GUIStyle selectedNodeStyle;
private GUIStyle inPointStyle;
private GUIStyle outPointStyle;
private ConnectionPoint selectedInPoint;
private ConnectionPoint selectedOutPoint;
[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}
private void OnEnable()
{
nodeStyle = new GUIStyle();
nodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1.png") as Texture2D;
nodeStyle. border = new RectOffset( 12, 12, 12, 12);
selectedNodeStyle = new GUIStyle();
selectedNodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1 on.png") as Texture2D;
selectedNodeStyle. border = new RectOffset( 12, 12, 12, 12);
inPointStyle = new GUIStyle();
inPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left.png") as Texture2D;
inPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left on.png") as Texture2D;
inPointStyle. border = new RectOffset( 4, 4, 12, 12);
outPointStyle = new GUIStyle();
outPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right.png") as Texture2D;
outPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right on.png") as Texture2D;
outPointStyle. border = new RectOffset( 4, 4, 12, 12);
}
...


NodeBasedEditor{。。。。。}
private void OnClickAddNode( Vector2 mousePosition)
{
if ( nodes == null)
{
nodes = new List< Node>();
}
nodes. Add( new Node( mousePosition, 200, 50, nodeStyle, selectedNodeStyle, inPointStyle, outPointStyle, OnClickInPoint, OnClickOutPoint));
}


删除节点

某些节点编辑器更喜欢将删除节点按钮放在节点本身上,但在我们的情况下,它可能很危险:用户可能会意外删除节点。因此,我们将做下一个最好的事情:将该按钮放在上下文菜单上。用户应首先选择节点,然后右键单击该节点以访问  删除节点按钮。当用户单击删除节点时,我们将从节点列表中删除该节点。但是,节点可能与其他节点有连接,因此我们应首先删除这些连接。

using System;
using UnityEditor;
using UnityEngine;
public class Node
{
public Rect rect;
public string title;
public bool isDragged;
public bool isSelected;
public ConnectionPoint inPoint;
public ConnectionPoint outPoint;
public GUIStyle style;
public GUIStyle defaultNodeStyle;
public GUIStyle selectedNodeStyle;
public Action< Node> OnRemoveNode;
public Node( Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action< ConnectionPoint> OnClickInPoint, Action< ConnectionPoint> OnClickOutPoint, Action< Node> OnClickRemoveNode)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
inPoint = new ConnectionPoint( this, ConnectionPointType. In, inPointStyle, OnClickInPoint);
outPoint = new ConnectionPoint( this, ConnectionPointType. Out, outPointStyle, OnClickOutPoint);
defaultNodeStyle = nodeStyle;
selectedNodeStyle = selectedStyle;
OnRemoveNode = OnClickRemoveNode;
}
public void Drag( Vector2 delta)
{
rect. position += delta;
}
public void Draw()
{
inPoint. Draw();
outPoint. Draw();
GUI. Box( rect, title, style);
}
public bool ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
if ( rect. Contains( e. mousePosition))
{
isDragged = true;
GUI. changed = true;
isSelected = true;
style = selectedNodeStyle;
}
else
{
GUI. changed = true;
isSelected = false;
style = defaultNodeStyle;
}
}
if ( e. button == 1 && isSelected && rect. Contains( e. mousePosition))
{
ProcessContextMenu();
e. Use();
}
break;
case EventType. MouseUp:
isDragged = false;
break;
case EventType. MouseDrag:
if ( e. button == 0 && isDragged)
{
Drag( e. delta);
e. Use();
return true;
}
break;
}
return false;
}
private void ProcessContextMenu()
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Remove node"), false, OnClickRemoveNode);
genericMenu. ShowAsContext();
}
private void OnClickRemoveNode()
{
if ( OnRemoveNode != null)
{
OnRemoveNode( this);
}
}
}



NodeBasedEditor{。。。。。}
private void OnClickAddNode( Vector2 mousePosition)
{
if ( nodes == null)
{
nodes = new List< Node>();
}
nodes. Add( new Node( mousePosition, 200, 50, nodeStyle, selectedNodeStyle, inPointStyle, outPointStyle, OnClickInPoint, OnClickOutPoint, OnClickRemoveNode));
}


NodeBasedEditor{。。。。。}
private void OnClickRemoveNode( Node node)
{
if ( connections != null)
{
List< Connection> connectionsToRemove = new List< Connection>();
for ( int i = 0; i < connections. Count; i++)
{
if ( connections[ i]. inPoint == node. inPoint || connections[ i]. outPoint == node. outPoint)
{
connectionsToRemove. Add( connections[ i]);
}
}
for ( int i = 0; i < connectionsToRemove. Count; i++)
{
connections. Remove( connectionsToRemove[ i]);
}
connectionsToRemove = null;
}
nodes. Remove( node);
}


最后的接触

此时节点编辑器已完成,但它缺少一些可以提升用户体验的重要功能:

  • 可拖动的帆布,
  • 从选定连接点到鼠标位置的贝塞尔曲线,
  • 背景中的网格。

让我们的画布可拖动是最简单的,所以让我们从这开始。我们所要做的就是将鼠标拖动应用于节点列表中的每个节点。


NodeBasedEditor {。。。。。}
public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private List< Connection> connections;
private GUIStyle nodeStyle;
private GUIStyle selectedNodeStyle;
private GUIStyle inPointStyle;
private GUIStyle outPointStyle;
private ConnectionPoint selectedInPoint;
private ConnectionPoint selectedOutPoint;
private Vector2 drag;
...

private void ProcessEvents( Event e)
{
drag = Vector2. zero;
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
ClearConnectionSelection();
}
if ( e. button == 1)
{
ProcessContextMenu( e. mousePosition);
}
break;
case EventType. MouseDrag:
if ( e. button == 0)
{
OnDrag( e. delta);
}
break;
}
}

private void OnDrag( Vector2 delta)
{
drag = delta;
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Drag( delta);
}
}
GUI. changed = true;
}

接下来,将贝塞尔曲线从选定的连接点绘制到鼠标位置。通过绘制这个bezier,我们将让用户知道选择了哪个连接点以及它们的连接方式。

private void OnGUI()
{
DrawNodes();
DrawConnections();
DrawConnectionLine( Event. current);
ProcessNodeEvents( Event. current);
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}


private void DrawConnectionLine( Event e)
{
if ( selectedInPoint != null && selectedOutPoint == null)
{
Handles. DrawBezier(
selectedInPoint. rect. center,
e. mousePosition,
selectedInPoint. rect. center + Vector2. left * 50f,
e. mousePosition - Vector2. left * 50f,
Color. white,
null,
2f
);
GUI. changed = true;
}
if ( selectedOutPoint != null && selectedInPoint == null)
{
Handles. DrawBezier(
selectedOutPoint. rect. center,
e. mousePosition,
selectedOutPoint. rect. center - Vector2. left * 50f,
e. mousePosition + Vector2. left * 50f,
Color. white,
null,
2f
);
GUI. changed = true;
}
}


最后,绘制网格:

public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private List< Connection> connections;
private GUIStyle nodeStyle;
private GUIStyle selectedNodeStyle;
private GUIStyle inPointStyle;
private GUIStyle outPointStyle;
private ConnectionPoint selectedInPoint;
private ConnectionPoint selectedOutPoint;
private Vector2 offset;
private Vector2 drag;
...

private void OnGUI()
{
DrawGrid( 20, 0.2f, Color. gray);
DrawGrid( 100, 0.4f, Color. gray);
DrawNodes();
DrawConnections();
DrawConnectionLine( Event. current);
ProcessNodeEvents( Event. current);
ProcessEvents( Event. current);
if ( GUI. changed) Repaint();
}
private void DrawGrid( float gridSpacing, float gridOpacity, Color gridColor)
{
int widthDivs = Mathf. CeilToInt( position. width / gridSpacing);
int heightDivs = Mathf. CeilToInt( position. height / gridSpacing);
Handles. BeginGUI();
Handles. color = new Color( gridColor. r, gridColor. g, gridColor. b, gridOpacity);
offset += drag * 0.5f;
Vector3 newOffset = new Vector3( offset. x % gridSpacing, offset. y % gridSpacing, 0);
for ( int i = 0; i < widthDivs; i++)
{
Handles. DrawLine( new Vector3( gridSpacing * i, - gridSpacing, 0) + newOffset, new Vector3( gridSpacing * i, position. height, 0f) + newOffset);
}
for ( int j = 0; j < heightDivs; j++)
{
Handles. DrawLine( new Vector3(- gridSpacing, gridSpacing * j, 0) + newOffset, new Vector3( position. width, gridSpacing * j, 0f) + newOffset);
}
Handles. color = Color. white;
Handles. EndGUI();
}


完整脚本

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

public class NodeBasedEditor : EditorWindow
{
private List< Node> nodes;
private List< Connection> connections;

private GUIStyle nodeStyle;
private GUIStyle selectedNodeStyle;
private GUIStyle inPointStyle;
private GUIStyle outPointStyle;

private ConnectionPoint selectedInPoint;
private ConnectionPoint selectedOutPoint;

private Vector2 offset;
private Vector2 drag;

[ MenuItem( "Window/Node Based Editor")]
private static void OpenWindow()
{
NodeBasedEditor window = GetWindow< NodeBasedEditor>();
window. titleContent = new GUIContent( "Node Based Editor");
}

private void OnEnable()
{
nodeStyle = new GUIStyle();
nodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1.png") as Texture2D;
nodeStyle. border = new RectOffset( 12, 12, 12, 12);

selectedNodeStyle = new GUIStyle();
selectedNodeStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/node1 on.png") as Texture2D;
selectedNodeStyle. border = new RectOffset( 12, 12, 12, 12);

inPointStyle = new GUIStyle();
inPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left.png") as Texture2D;
inPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn left on.png") as Texture2D;
inPointStyle. border = new RectOffset( 4, 4, 12, 12);

outPointStyle = new GUIStyle();
outPointStyle. normal. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right.png") as Texture2D;
outPointStyle. active. background = EditorGUIUtility. Load( "builtin skins/darkskin/images/btn right on.png") as Texture2D;
outPointStyle. border = new RectOffset( 4, 4, 12, 12);
}

private void OnGUI()
{
DrawGrid( 20, 0.2f, Color. gray);
DrawGrid( 100, 0.4f, Color. gray);

DrawNodes();
DrawConnections();

DrawConnectionLine( Event. current);

ProcessNodeEvents( Event. current);
ProcessEvents( Event. current);

if ( GUI. changed) Repaint();
}

private void DrawGrid( float gridSpacing, float gridOpacity, Color gridColor)
{
int widthDivs = Mathf. CeilToInt( position. width / gridSpacing);
int heightDivs = Mathf. CeilToInt( position. height / gridSpacing);

Handles. BeginGUI();
Handles. color = new Color( gridColor. r, gridColor. g, gridColor. b, gridOpacity);

offset += drag * 0.5f;
Vector3 newOffset = new Vector3( offset. x % gridSpacing, offset. y % gridSpacing, 0);

for ( int i = 0; i < widthDivs; i++)
{
Handles. DrawLine( new Vector3( gridSpacing * i, - gridSpacing, 0) + newOffset, new Vector3( gridSpacing * i, position. height, 0f) + newOffset);
}

for ( int j = 0; j < heightDivs; j++)
{
Handles. DrawLine( new Vector3(- gridSpacing, gridSpacing * j, 0) + newOffset, new Vector3( position. width, gridSpacing * j, 0f) + newOffset);
}

Handles. color = Color. white;
Handles. EndGUI();
}

private void DrawNodes()
{
if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Draw();
}
}
}

private void DrawConnections()
{
if ( connections != null)
{
for ( int i = 0; i < connections. Count; i++)
{
connections[ i]. Draw();
}
}
}

private void ProcessEvents( Event e)
{
drag = Vector2. zero;

switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
ClearConnectionSelection();
}

if ( e. button == 1)
{
ProcessContextMenu( e. mousePosition);
}
break;

case EventType. MouseDrag:
if ( e. button == 0)
{
OnDrag( e. delta);
}
break;
}
}

private void ProcessNodeEvents( Event e)
{
if ( nodes != null)
{
for ( int i = nodes. Count - 1; i >= 0; i--)
{
bool guiChanged = nodes[ i]. ProcessEvents( e);

if ( guiChanged)
{
GUI. changed = true;
}
}
}
}

private void DrawConnectionLine( Event e)
{
if ( selectedInPoint != null && selectedOutPoint == null)
{
Handles. DrawBezier(
selectedInPoint. rect. center,
e. mousePosition,
selectedInPoint. rect. center + Vector2. left * 50f,
e. mousePosition - Vector2. left * 50f,
Color. white,
null,
2f
);

GUI. changed = true;
}

if ( selectedOutPoint != null && selectedInPoint == null)
{
Handles. DrawBezier(
selectedOutPoint. rect. center,
e. mousePosition,
selectedOutPoint. rect. center - Vector2. left * 50f,
e. mousePosition + Vector2. left * 50f,
Color. white,
null,
2f
);

GUI. changed = true;
}
}

private void ProcessContextMenu( Vector2 mousePosition)
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Add node"), false, () => OnClickAddNode( mousePosition));
genericMenu. ShowAsContext();
}

private void OnDrag( Vector2 delta)
{
drag = delta;

if ( nodes != null)
{
for ( int i = 0; i < nodes. Count; i++)
{
nodes[ i]. Drag( delta);
}
}

GUI. changed = true;
}

private void OnClickAddNode( Vector2 mousePosition)
{
if ( nodes == null)
{
nodes = new List< Node>();
}

nodes. Add( new Node( mousePosition, 200, 50, nodeStyle, selectedNodeStyle, inPointStyle, outPointStyle, OnClickInPoint, OnClickOutPoint, OnClickRemoveNode));
}

private void OnClickInPoint( ConnectionPoint inPoint)
{
selectedInPoint = inPoint;

if ( selectedOutPoint != null)
{
if ( selectedOutPoint. node != selectedInPoint. node)
{
CreateConnection();
ClearConnectionSelection();
}
else
{
ClearConnectionSelection();
}
}
}

private void OnClickOutPoint( ConnectionPoint outPoint)
{
selectedOutPoint = outPoint;

if ( selectedInPoint != null)
{
if ( selectedOutPoint. node != selectedInPoint. node)
{
CreateConnection();
ClearConnectionSelection();
}
else
{
ClearConnectionSelection();
}
}
}

private void OnClickRemoveNode( Node node)
{
if ( connections != null)
{
List< Connection> connectionsToRemove = new List< Connection>();

for ( int i = 0; i < connections. Count; i++)
{
if ( connections[ i]. inPoint == node. inPoint || connections[ i]. outPoint == node. outPoint)
{
connectionsToRemove. Add( connections[ i]);
}
}

for ( int i = 0; i < connectionsToRemove. Count; i++)
{
connections. Remove( connectionsToRemove[ i]);
}

connectionsToRemove = null;
}

nodes. Remove( node);
}

private void OnClickRemoveConnection( Connection connection)
{
connections. Remove( connection);
}

private void CreateConnection()
{
if ( connections == null)
{
connections = new List< Connection>();
}

connections. Add( new Connection( selectedInPoint, selectedOutPoint, OnClickRemoveConnection));
}

private void ClearConnectionSelection()
{
selectedInPoint = null;
selectedOutPoint = null;
}
}


using System;
using UnityEditor;
using UnityEngine;

public class Node
{
public Rect rect;
public string title;
public bool isDragged;
public bool isSelected;

public ConnectionPoint inPoint;
public ConnectionPoint outPoint;

public GUIStyle style;
public GUIStyle defaultNodeStyle;
public GUIStyle selectedNodeStyle;

public Action< Node> OnRemoveNode;

public Node( Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action< ConnectionPoint> OnClickInPoint, Action< ConnectionPoint> OnClickOutPoint, Action< Node> OnClickRemoveNode)
{
rect = new Rect( position. x, position. y, width, height);
style = nodeStyle;
inPoint = new ConnectionPoint( this, ConnectionPointType. In, inPointStyle, OnClickInPoint);
outPoint = new ConnectionPoint( this, ConnectionPointType. Out, outPointStyle, OnClickOutPoint);
defaultNodeStyle = nodeStyle;
selectedNodeStyle = selectedStyle;
OnRemoveNode = OnClickRemoveNode;
}

public void Drag( Vector2 delta)
{
rect. position += delta;
}

public void Draw()
{
inPoint. Draw();
outPoint. Draw();
GUI. Box( rect, title, style);
}

public bool ProcessEvents( Event e)
{
switch ( e. type)
{
case EventType. MouseDown:
if ( e. button == 0)
{
if ( rect. Contains( e. mousePosition))
{
isDragged = true;
GUI. changed = true;
isSelected = true;
style = selectedNodeStyle;
}
else
{
GUI. changed = true;
isSelected = false;
style = defaultNodeStyle;
}
}

if ( e. button == 1 && isSelected && rect. Contains( e. mousePosition))
{
ProcessContextMenu();
e. Use();
}
break;

case EventType. MouseUp:
isDragged = false;
break;

case EventType. MouseDrag:
if ( e. button == 0 && isDragged)
{
Drag( e. delta);
e. Use();
return true;
}
break;
}

return false;
}

private void ProcessContextMenu()
{
GenericMenu genericMenu = new GenericMenu();
genericMenu. AddItem( new GUIContent( "Remove node"), false, OnClickRemoveNode);
genericMenu. ShowAsContext();
}

private void OnClickRemoveNode()
{
if ( OnRemoveNode != null)
{
OnRemoveNode( this);
}
}
}


using System;
using UnityEditor;
using UnityEngine;

public class Connection
{
public ConnectionPoint inPoint;
public ConnectionPoint outPoint;
public Action< Connection> OnClickRemoveConnection;

public Connection( ConnectionPoint inPoint, ConnectionPoint outPoint, Action< Connection> OnClickRemoveConnection)
{
this. inPoint = inPoint;
this. outPoint = outPoint;
this. OnClickRemoveConnection = OnClickRemoveConnection;
}

public void Draw()
{
Handles. DrawBezier(
inPoint. rect. center,
outPoint. rect. center,
inPoint. rect. center + Vector2. left * 50f,
outPoint. rect. center - Vector2. left * 50f,
Color. white,
null,
2f
);

if ( Handles. Button(( inPoint. rect. center + outPoint. rect. center) * 0.5f, Quaternion. identity, 4, 8, Handles. RectangleCap))
{
if ( OnClickRemoveConnection != null)
{
OnClickRemoveConnection( this);
}
}
}
}


using System;
using UnityEngine;

public enum ConnectionPointType { In, Out }

public class ConnectionPoint
{
public Rect rect;

public ConnectionPointType type;

public Node node;

public GUIStyle style;

public Action< ConnectionPoint> OnClickConnectionPoint;

public ConnectionPoint( Node node, ConnectionPointType type, GUIStyle style, Action< ConnectionPoint> OnClickConnectionPoint)
{
this. node = node;
this. type = type;
this. style = style;
this. OnClickConnectionPoint = OnClickConnectionPoint;
rect = new Rect( 0, 0, 10f, 20f);
}

public void Draw()
{
rect. y = node. rect. y + ( node. rect. height * 0.5f) - rect. height * 0.5f;

switch ( type)
{
case ConnectionPointType. In:
rect. x = node. rect. x - rect. width + 8f;
break;

case ConnectionPointType. Out:
rect. x = node. rect. x + node. rect. width - 8f;
break;
}

if ( GUI. Button( rect, "", style))
{
if ( OnClickConnectionPoint != null)
{
OnClickConnectionPoint( this);
}
}
}
}

猜你喜欢

转载自blog.csdn.net/qq_31967569/article/details/81025419