在Unity中创建基于Node节点的编辑器 (二) 窗口序列化

孙广东  2018.5.13

csdn 的产品 , 真垃圾, 不想吐槽了, 文章保存就丢!     没办法  。    怎么不满意, 还是得继续用, 哎~~~

第二部分 在Unity中序列化基于节点的编辑器

重温基于节点的编辑器

重温XML序列化
有许多序列化选项,例如JSON或Unity自己的序列化系统。 我们以前已经介绍过XML序列化 我们将使用 XMLOp  类和本文中的一些XML属性。

添加一个菜单栏(添加保存, 加载等按钮)
我们将从添加菜单栏开始,然后从控制台窗口clone中复制它(一起弄的一个 仿Unity Console窗口的脚本):
     private float menuBarHeight = 20f ;
     private Rect menuBar ;

     private void OnGUI ()
     {
         DrawGrid ( 20 , 0.2f , Color . gray );
         DrawGrid ( 100 , 0.4f , Color . gray );
         DrawMenuBar ();
 
         DrawNodes ();
         DrawConnections ();
 
         DrawConnectionLine ( Event . current );
 
         ProcessNodeEvents ( Event . current );
         ProcessEvents ( Event . current );
 
         if ( GUI . changed ) Repaint ();
     }
 
     private void DrawMenuBar ()
     {
         menuBar = new Rect ( 0 , 0 , position . width , menuBarHeight );
 
         GUILayout . BeginArea ( menuBar , EditorStyles . toolbar );
         GUILayout . BeginHorizontal ();
 
         GUILayout . Button ( new GUIContent ( "Save" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 ));
         GUILayout . Space ( 5 );
         GUILayout . Button ( new GUIContent ( "Load" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 ));
 
         GUILayout . EndHorizontal ();
         GUILayout . EndArea ();
     }
在第56行,我们调用 DrawMenuBar()方法,并在第69-82行之间创建菜单栏。控制台窗口克隆有一个 button和6个 toggles,但由于我们只是序列化和反序列化,所以我们不需要两个以上的按钮。请记住,编辑器GUI系统具有绘制顺序,并按照您调用它们的顺序从后向前绘制元素。这就是我们绘制网格后绘制菜单栏的原因。否则,网格将被拖到菜单栏上。

DrawMenuBar() 在 DrawGrid() 之前被调用,因此在菜单栏上有一个丑陋的网格。

目前,保存和加载按钮什么都不做,但我们会做到。

序列化
接下来,我们需要准备我们的类(节点 Node和连接 Connection)进行序列化。让我们记住关于XML序列化的两个重要关键点:

  1. XML序列化程序只能序列化public 字段。
  2. 要序列化的类应该有一个无参数的构造函数。
规则1号不会导致很多问题(它仍然会导致一些问题,但我们会做到这一点),但规则编号2是有问题的。我们的两个类都有带参数的构造函数。首先解决这个问题:
     public Node () { }
    
     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 Connection () { }
    
     public Connection ( ConnectionPoint inPoint , ConnectionPoint outPoint , Action < Connection > OnClickRemoveConnection )
     {
         this . inPoint = inPoint ;
         this . outPoint = outPoint ;
         this . OnClickRemoveConnection = OnClickRemoveConnection ;
     }

接下来,我们将忽略无法序列化或不需要序列化的属性。 例如,在 Node  类中, GUIStyle 可以不用序列化,因为它们已经由编辑器自己提供。 我们不需要 isDraggedisSelected。 实际上,Node类只有一个需要序列化的属性: rect。 让我们来看看如何正确地忽略不必要和不可序列化的属性后Node类的样子:
public class Node
{
public Rect rect ;
 
[ XmlIgnore ] public string title ;
[ XmlIgnore ] public bool isDragged ;
[ XmlIgnore ] public bool isSelected ;
 
[ XmlIgnore ] public ConnectionPoint inPoint ;
[ XmlIgnore ] public ConnectionPoint outPoint ;
 
[ XmlIgnore ] public GUIStyle style ;
[ XmlIgnore ] public GUIStyle defaultNodeStyle ;
[ XmlIgnore ] public GUIStyle selectedNodeStyle ;
 
[ XmlIgnore ] public Action < Node > OnRemoveNode ;
 
public Node ()
{
}


信不信由你,Node类已准备好在这一点上被序列化。 所以,我们来序列化节点!

保存节点
记得保存按钮什么也没做? 那么,它至少应该保存节点,因为它们现在是可序列化的。 节点保存的方法非常简单:
     private void Save ()
     {
         XMLOp . Serialize ( nodes , "Assets/Resources/nodes.xml" );
     }

我们将在用户点击保存按钮时调用该方法:
     private void DrawMenuBar ()
     {
         menuBar = new Rect ( 0 , 0 , position . width , menuBarHeight );
 
         GUILayout . BeginArea ( menuBar , EditorStyles . toolbar );
         GUILayout . BeginHorizontal ();
 
         if ( GUILayout . Button ( new GUIContent ( "Save" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 )))
         {
             Save ();
         }
        
         GUILayout . Space ( 5 );
         GUILayout . Button ( new GUIContent ( "Load" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 ));
 
         GUILayout . EndHorizontal ();
         GUILayout . EndArea ();
     }

现在,打开基于节点的编辑器,放置几个节点,然后点击保存(在此之前,您必须在资源下有一个 Resources 文件夹)。 Unity会在 Resources 中创建一个名为nodes.xml的文件,如果您看不到它,只需右键单击资源,然后单击重新导入 Ctrl + R。 nodes.xml文件的内容应该是这样的:
<?xml version="1.0" encoding="us-ascii"?>
<ArrayOfNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Node>
<rect>
<x>166</x>
<y>183</y>
<position>
<x>166</x>
<y>183</y>
</position>
<center>
<x>266</x>
<y>208</y>
</center>
<min>
<x>166</x>
<y>183</y>
</min>
<max>
<x>366</x>
<y>233</y>
</max>
<width>200</width>
<height>50</height>
<size>
<x>200</x>
<y>50</y>
</size>
<xMin>166</xMin>
<yMin>183</yMin>
<xMax>366</xMax>
<yMax>233</yMax>
</rect>
</Node>
<Node>
<rect>
<x>345</x>
<y>345</y>
<position>
<x>345</x>
<y>345</y>
</position>
<center>
<x>445</x>
<y>370</y>
</center>
<min>
<x>345</x>
<y>345</y>
</min>
<max>
<x>545</x>
<y>395</y>
</max>
<width>200</width>
<height>50</height>
<size>
<x>200</x>
<y>50</y>
</size>
<xMin>345</xMin>
<yMin>345</yMin>
<xMax>545</xMax>
<yMax>395</yMax>
</rect>
</Node>
</ArrayOfNode>

所以,现在我们的节点可以被序列化,如果我们可以序列化 Connection s,我们的节点编辑器将是完全可序列化的。 那么我们继续吧。

序列化连接
Connection类只有两个可以序列化的属性: inPoint (ConnectionPoint)outPoint (ConnectionPoint) .。 但是,序列化这两个属性将毫无意义,因为对象在反序列化后不会保留对其他对象的引用。 这意味着,如果我们反序列化一个连接,它会创建两个连接点并连接它们,但这些连接点不属于它用来连接的节点(参见下图)。

序列化破坏连接,反序列化不能解决这个问题。

为了解决这个问题,我们需要某种连接点的标识符,即一个唯一的ID,这样在反序列化后,我们可以通过它们的ID来查找这些连接点,并为实际对象提供参考以恢复连接。
public class ConnectionPoint
{
     public string id ;
    
     [ XmlIgnore ] public Rect rect ;
 
     [ XmlIgnore ] public ConnectionPointType type ;
 
     [ XmlIgnore ] public Node node ;
 
     [ XmlIgnore ] public GUIStyle style ;
 
     [ XmlIgnore ] public Action < ConnectionPoint > OnClickConnectionPoint ;
 
     public ConnectionPoint () { }
    
     public ConnectionPoint ( Node node , ConnectionPointType type , GUIStyle style , Action < ConnectionPoint > OnClickConnectionPoint , string id = null )
     {
         this . node = node ;
         this . type = type ;
         this . style = style ;
         this . OnClickConnectionPoint = OnClickConnectionPoint ;
         rect = new Rect ( 0 , 0 , 10f , 20f );
 
         this . id = id ?? Guid . NewGuid (). ToString ();
     }

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

public class Node
{
public Rect rect ;
 
[ XmlIgnore ] public string title ;
[ XmlIgnore ] public bool isDragged ;
[ XmlIgnore ] public bool isSelected ;
 
public ConnectionPoint inPoint ;
public ConnectionPoint outPoint ;
 
[ XmlIgnore ] public GUIStyle style ;
[ XmlIgnore ] public GUIStyle defaultNodeStyle ;
[ XmlIgnore ] public GUIStyle selectedNodeStyle ;
 
[ XmlIgnore ] public Action < Node > OnRemoveNode ;
当然,我们需要更新我们的Save方法以包含连接:
     private void Save ()
     {
         XMLOp . Serialize ( nodes , "Assets/Resources/nodes.xml" );
         XMLOp . Serialize ( connections , "Assets/Resources/connections.xml" );
     }
这就结束了序列化(坦率地说,很难)的一部分。 现在我们有一个基于节点的编辑器的当前状态的XML表示。 我们所要做的就是将其转换回来。

反序列化
首先要做的是:Load按钮应该是功能性的。

     private void DrawMenuBar ()
     {
         menuBar = new Rect ( 0 , 0 , position . width , menuBarHeight );
 
         GUILayout . BeginArea ( menuBar , EditorStyles . toolbar );
         GUILayout . BeginHorizontal ();
 
         if ( GUILayout . Button ( new GUIContent ( "Save" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 )))
         {
             Save ();
         }
        
         GUILayout . Space ( 5 );
 
         if ( GUILayout . Button ( new GUIContent ( "Load" ), EditorStyles . toolbarButton , GUILayout . Width ( 35 )))
         {
             Load ();
         }
 
         GUILayout . EndHorizontal ();
         GUILayout . EndArea ();
     }

我们将在 Load() 方法中反序列化 XML 文件的内容,创建节点和连接并将它们分配给它们各自的属性。 反序列化XML文件是一个非常简单的过程; 我们所要做的就是调用 XMLOp.Deserialize<T>(string) :
     private void Load ()
     {
         var nodesDeserialized = XMLOp . Deserialize < List < Node >> ( "Assets/Resources/nodes.xml" );
         var connectionsDeserialized = XMLOp . Deserialize < List < Connection >> ( "Assets/Resources/connections.xml" );
 
         nodes = new List < Node > ();
         connections = new List < Connection > ();
     }
然而,单独对XML文件进行反序列化并不足以将编辑器恢复到最后一个状态,因为如上图所示,我们在序列化时打破了节点和连接之间的关系,我们需要重新连接它们。 此重新连接过程需要通过ID查找节点并在它们之间创建连接。 这就是为什么我们为ConnectionPoint类添加了唯一ID。 我们需要用这些ID重新创建ConnectionPoints,所以我们要添加另一个构造函数给Node类:
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 , string inPointID , string outPointID )
{
rect = new Rect ( position . x , position . y , width , height );
style = nodeStyle ;
inPoint = new ConnectionPoint ( this , ConnectionPointType . In , inPointStyle , OnClickInPoint , inPointID );
outPoint = new ConnectionPoint ( this , ConnectionPointType . Out , outPointStyle , OnClickOutPoint , outPointID );
defaultNodeStyle = nodeStyle ;
selectedNodeStyle = selectedStyle ;
OnRemoveNode = OnClickRemoveNode ;
}

这是一个新的构造函数,它将创建一个具有两个给定(而不是生成)ID的ConnectionPoint的Node。 现在我们将基于反序列化节点创建新节点:

     private void Load ()
     {
         var nodesDeserialized = XMLOp . Deserialize < List < Node >> ( "Assets/Resources/nodes.xml" );
         var connectionsDeserialized = XMLOp . Deserialize < List < Connection >> ( "Assets/Resources/connections.xml" );
 
         nodes = new List < Node > ();
         connections = new List < Connection > ();
 
         foreach ( var nodeDeserialized in nodesDeserialized )
         {
             nodes . Add ( new Node (
                 nodeDeserialized . rect . position ,
                 nodeDeserialized . rect . width ,
                 nodeDeserialized . rect . height ,
                 nodeStyle ,
                 selectedNodeStyle ,
                 inPointStyle ,
                 outPointStyle ,
                 OnClickInPoint ,
                 OnClickOutPoint ,
                 OnClickRemoveNode ,
                 nodeDeserialized . inPoint . id ,
                 nodeDeserialized . outPoint . id
                 )
             );
         }
     }
继续尝试。 创建几个节点,保存它,关闭编辑器,再次打开并点击Load按钮。 你会看到你的节点回到他们的位置。 让我们反序列化连接并完成我们基于节点的编辑器:

     private void Load ()
     {
         var nodesDeserialized = XMLOp . Deserialize < List < Node >> ( "Assets/Resources/nodes.xml" );
         var connectionsDeserialized = XMLOp . Deserialize < List < Connection >> ( "Assets/Resources/connections.xml" );
 
         nodes = new List < Node > ();
         connections = new List < Connection > ();
 
         foreach ( var nodeDeserialized in nodesDeserialized )
         {
             nodes . Add ( new Node (
                 nodeDeserialized . rect . position ,
                 nodeDeserialized . rect . width ,
                 nodeDeserialized . rect . height ,
                 nodeStyle ,
                 selectedNodeStyle ,
                 inPointStyle ,
                 outPointStyle ,
                 OnClickInPoint ,
                 OnClickOutPoint ,
                 OnClickRemoveNode ,
                 nodeDeserialized . inPoint . id ,
                 nodeDeserialized . outPoint . id
                 )
             );
         }
 
         foreach ( var connectionDeserialized in connectionsDeserialized )
         {
             var inPoint = nodes . First ( n => n . inPoint . id == connectionDeserialized . inPoint . id ). inPoint ;
             var outPoint = nodes . First ( n => n . outPoint . id == connectionDeserialized . outPoint . id ). outPoint ;
             connections . Add ( new Connection ( inPoint , outPoint , OnClickRemoveConnection ));
         }
     }

最后的话
这就结束了 。 您现在有一个功能齐全的基于节点的编辑器,具有保存和加载功能。

一如既往,下面是完整的脚本。 直到下一次。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;

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 float menuBarHeight = 20f;
private Rect menuBar;

[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);
DrawMenuBar();

DrawNodes();
DrawConnections();

DrawConnectionLine(Event.current);

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

if (GUI.changed) Repaint();
}

private void DrawMenuBar()
{
menuBar = new Rect(0, 0, position.width, menuBarHeight);

GUILayout.BeginArea(menuBar, EditorStyles.toolbar);
GUILayout.BeginHorizontal();

if (GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton, GUILayout.Width(35)))
{
Save();
}
GUILayout.Space(5);

if (GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton, GUILayout.Width(35)))
{
Load();
}

GUILayout.EndHorizontal();
GUILayout.EndArea();
}

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

private void Save()
{
XMLOp.Serialize(nodes, "Assets/Resources/nodes.xml");
XMLOp.Serialize(connections, "Assets/Resources/connections.xml");
}

private void Load()
{
var nodesDeserialized = XMLOp.Deserialize<List<Node>>("Assets/Resources/nodes.xml");
var connectionsDeserialized = XMLOp.Deserialize<List<Connection>>("Assets/Resources/connections.xml");

nodes = new List<Node>();
connections = new List<Connection>();

foreach (var nodeDeserialized in nodesDeserialized)
{
nodes.Add(new Node(
nodeDeserialized.rect.position,
nodeDeserialized.rect.width,
nodeDeserialized.rect.height,
nodeStyle,
selectedNodeStyle,
inPointStyle,
outPointStyle,
OnClickInPoint,
OnClickOutPoint,
OnClickRemoveNode,
nodeDeserialized.inPoint.id,
nodeDeserialized.outPoint.id
)
);
}

foreach (var connectionDeserialized in connectionsDeserialized)
{
var inPoint = nodes.First(n => n.inPoint.id == connectionDeserialized.inPoint.id).inPoint;
var outPoint = nodes.First(n => n.outPoint.id == connectionDeserialized.outPoint.id).outPoint;
connections.Add(new Connection(inPoint, outPoint, OnClickRemoveConnection));
}
}
}

using System;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

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

public Connection() { }
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 System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

public enum ConnectionPointType { In, Out }

public class ConnectionPoint
{
public string id;
[XmlIgnore] public Rect rect;

[XmlIgnore] public ConnectionPointType type;

[XmlIgnore] public Node node;

[XmlIgnore] public GUIStyle style;

[XmlIgnore] public Action<ConnectionPoint> OnClickConnectionPoint;

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

this.id = id ?? Guid.NewGuid().ToString();
}

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 System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

public class Node
{
public Rect rect;

[XmlIgnore] public string title;
[XmlIgnore] public bool isDragged;
[XmlIgnore] public bool isSelected;

public ConnectionPoint inPoint;
public ConnectionPoint outPoint;

[XmlIgnore] public GUIStyle style;
[XmlIgnore] public GUIStyle defaultNodeStyle;
[XmlIgnore] public GUIStyle selectedNodeStyle;

[XmlIgnore] public Action<Node> OnRemoveNode;

public Node()
{
}

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 Node(Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint,
Action<ConnectionPoint> OnClickOutPoint, Action<Node> OnClickRemoveNode, string inPointID, string outPointID)
{
rect = new Rect(position.x, position.y, width, height);
style = nodeStyle;
inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint, inPointID);
outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint, outPointID);
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.IO;
using System.Xml.Serialization;

public class XMLOp
{
public static void Serialize(object item, string path)
{
XmlSerializer serializer = new XmlSerializer(item.GetType());
StreamWriter writer = new StreamWriter(path);
serializer.Serialize(writer.BaseStream, item);
writer.Close();
}

public static T Deserialize<T>(string path)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StreamReader reader = new StreamReader(path);
T deserialized = (T)serializer.Deserialize(reader.BaseStream);
reader.Close();
return deserialized;
}
}

猜你喜欢

转载自blog.csdn.net/u010019717/article/details/80300763