孙广东  2018.5.13

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

第二部分 在Unity中序列化基于节点的编辑器
重温基于节点的编辑器
上一篇文章 《

在Unity中创建基于Node节点的编辑器 (一)

重温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 类中,GUIStyles 可以不用序列化,因为它们已经由编辑器自己提供。 我们不需要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>
所以,现在我们的节点可以被序列化,如果我们可以序列化Connections,我们的节点编辑器将是完全可序列化的。 那么我们继续吧。
序列化连接
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;
}
}

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

  1. 在Unity中创建基于Node节点的编辑器 (一)

    孙广东   2018.5.13 Unity  AssetStore中关于Node节点 编辑器相关的插件可是数不胜数, 状态机,行为树,Shader 可视化等等. Unity自己也有 Animator的 ...

  2. 在Unity中实现基于粒子的水模拟(二:开始着色)

    在Unity中实现基于粒子的水模拟(二:开始着色) 文章目录 在Unity中实现基于粒子的水模拟(二:开始着色) 前言 一.生成顶点 二.偏移模拟 1.接收细分着色器输出的顶点 2.根据数据调用对应的 ...

  3. 学习在Unity中创建一个动作RPG游戏

    游戏开发变得简单.使用Unity学习C#并创建您自己的动作角色扮演游戏! 你会学到什么 学习C#,一种现代通用的编程语言. 了解Unity中2D发展的能力. 发展强大的和可移植的解决问题的技能. 了解 ...

  4. 学习用C#在Unity中创建一个2D Metroidvania游戏

    学习用C#在Unity中创建一个2D Metroidvania游戏 你会学到: 构建2D Unity游戏 用C#编程 玩家统计,水平提升,米尔和远程攻击 敌方人工智能系统 制定级别和级别选择 Lear ...

  5. 学会在Unity中创建一个Match-3益智游戏 Learn To Create a Match-3 Puzzle Game in Unity

    MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch 语言:英语+中英文字幕(根据原英文字幕机译更准确) |时长:48场讲座(6h 38m) |大小解压后:2.8 G ...

  6. Unity中创建本地多人游戏完整案例视频教程 Learn To Create A Local Multiplayer Game In Unity

    Unity中创建本地多人游戏完整案例视频教程 Learn To Create A Local Multiplayer Game In Unity MP4 |视频:h264,1280x720 |音频:A ...

  7. unity中创建游戏场景_在Unity中创建Beat Em Up游戏

    unity中创建游戏场景 Learn how to use Unity to create a 3D Beat Em Up game in this full tutorial from Awesom ...

  8. 在Unity中实现基于粒子的水模拟(三:混合屏幕)

    在Unity中实现基于粒子的水模拟(三:混合屏幕) 文章目录 在Unity中实现基于粒子的水模拟(三:混合屏幕) 前言 一.着色算法介绍 1.折射 2.反射 二.准备纹理 1.获取纹理 2.模糊纹理 ...

  9. Revit中创建基于线的砌体墙及【快速砌体排砖】

    ​ 墙可以更改内部结构和材质,但是很难画出砌块样式形成的墙体,我们可以用其他方式画出砌体排砖墙么?这里我们用基于线的常规模型做砌体排砖墙.在开始我们需要做两个族,作为砌体排砖墙的基本单位,也就是一个单 ...

最新文章

  1. mysql空间扩展 VS PostGIS
  2. Facebook SDE onsite面经
  3. AS3 鼠标指针样式
  4. 用 Hadoop 进行分布式并行编程, 第 1 部分 基本概念与安装部署
  5. Onvif备忘录(2)----客户端端设备发现
  6. UJAM Finisher FLUXX for Mac(综合效果器插件)
  7. MySQL性能优化设置
  8. Eclipse设置中文及取消
  9. 换IP软件用户如何选择?
  10. Nexperia | 采用Trench肖特基二极管,实现功率密度的显著提升
  11. 详细的log4j配置使用流程
  12. 洛谷——P1957 口算练习题
  13. 【清华大学】操作系统 陈渝——Part11 死锁
  14. 树莓派安装Homeassistant智能家居
  15. IR(红外遥控)基本原理
  16. LNK1181 无法打开输入文件“winmm.lib”
  17. 阿里云Landing Zone系列--2 资源目录之--多账号
  18. ubuntu 安装kali_如何在Linux,Windows,Kali,Ubuntu,Mint和示例中安装和使用exiftool
  19. 玩转黑群晖(持续更新)
  20. 计算机应届生理论知识面试,计算机应届生面试技巧

热门文章

  1. 云主机装黑果实践(6):处理云主机上变色龙启动后置过程:驱动和黑屏
  2. 政务云市场开启“狂飙”:一项前三,两项跃升!
  3. 第十一届“认证杯”数学中国数学建模国际赛 (2022 CERTIFICATE AUTHORITY CUP INTERNATIONAL
  4. 区块链入门教程(9)--使用WeBASE-Front部署及调用合约
  5. 微信小程序:缓存wx.getStorage
  6. 畅想未来的我计算机,畅想未来的电子计算机
  7. 结构型模式--Proxy代理模式
  8. unity3D游戏开发十二之疯狂的小球
  9. 天池-淘宝用户行为数据分析(python+Tableau)
  10. 鸿蒙和iqoo系统谁强,鸿蒙系统发布会,小米11降500,iQOO、酷派、魅族、惠普发布新品...