打开SO数据 使用Unity内置函数添加双击打开事件 使用Unity提供的OnOpenAsset(0),然后重新设置graphView的数据并重绘。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [OnOpenAsset(0) ] public static bool OnOpen (int instanceID, int line ){ GraphSoData nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as GraphSoData; if (nodeGraph != null ) { Open(nodeGraph); return true ; } return false ; } public static void Open (GraphSoData nodeGraph ){ ShowExample(); m_graphView.SetGraphData(nodeGraph); m_graphView.RebuildGraph(); }
获取So数据后进行节点的创建 RebuildGraph函数先清空原有的所有节点和节点连接,再重新创建。
1 2 3 4 5 public void RebuildGraph (){ ClearNodeAndEdge(); CreateNode(m_graphData.nodeSoDataList); }
清空节点 contentContainer是Unity节点内的所有元素的容器,遍历这个容器,把NodeBase和Edge删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public virtual void ClearNodeAndEdge (){ if (contentContainer == null || contentContainer.childCount <= 0 ) { return ; } for (int i = contentContainer.childCount - 1 ; i >= 0 ; i--) { var elementItem = contentContainer.ElementAt(i); if (elementItem is NodeBase node) { contentContainer.RemoveAt(i); } if (elementItem is Edge edge) { contentContainer.RemoveAt(i); } } }
创建节点 拿到GraphSoData中的NodeSoData列表信息进行遍历。对每个Node数据信息,拿到TypeName后使用反射实例化出具体的Node子类,随后通过AddElement函数把Node添加到图中管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public virtual List<NodeBase> CreateNode (List<NodeSoData> nodeDataList ){ if (nodeDataList == null || nodeDataList.Count <= 0 ) { return null ; } List<NodeBase> nodeViewList = new List<NodeBase>(nodeDataList.Count); foreach (NodeSoData node in nodeDataList) { Type nodeType = Type.GetType(node.nodeFullTypeName); NodeBase newNode = (NodeBase)Activator.CreateInstance(nodeType); newNode.Init(node, this ); nodeViewList.Add(newNode); AddElement(newNode); } LinkNode(); return nodeViewList; }
创建连接 为了避免找不到未生成节点的先后顺序问题,所以在所有的节点创建完成后,再进行节点连接的创建。节点之间的连接通过之前记录在端口中的LinkData数据,通过节点ID和端口ID找到唯一的端口,并通过ConnectTo函数连接两个节点,最后把Edge连接通过AddElement函数添加到Graph中管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 protected virtual void LinkNode (){ foreach (var node in nodes) { NodeBase nodeBase = node as NodeBase; if (nodeBase == null ) { continue ; } foreach (var portData in nodeBase.nodeData.outputPortDataList) { long curPortID = portData.portID; Port curPort = GetPortByID(curPortID); foreach (var linkData in portData.linkData) { long linkPortID = linkData.linkPortID; Port linkPort = GetPortByID(linkPortID); if (linkPort == null ) { Debug.LogError($"nodeID={nodeBase.nodeData.nodeID} linkPortID={linkPortID} ,port is null" ); } var edge = curPort.ConnectTo(linkPort); AddElement(edge); } } } }
保存运行时序列化数据 前面的GraphSoData是仅作为编辑器使,而且运行时的时候比如一些节点的大小、位置这些信息是用不到的。为了数据的精简和高效,所以在原有的NodeSoData创建了一个NodeSerializationData用于保存运行时序列化信息。在运行时一张图就转变为一个List数据。 这个NodeSerializationData数据由NodeBase提供一个虚方法,并由各个具体的子类继承实现。
1 2 3 4 public virtual NodeSerializationData GetSerializationData (){ return new NodeSerializationData(nodeData); }
窗口添加保存按钮 在对应的windows窗口中添加一个ToolBar并在其中添加一个按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private void AddToolBar (){ Toolbar toolbar = new Toolbar(); Button saveBtn = new Button(); saveBtn.text = "保存" ; saveBtn.clicked += SaveBtnClick; toolbar.Add(saveBtn); rootVisualElement.Add(toolbar); } private void SaveBtnClick (){ if (m_graphView == null ) { return ; } m_graphView.SaveData(); }
核心逻辑为调用GraphView中的SaveData接口,在里面遍历所有的Nodebase的获取序列化数据的接口,并且获得数据都存入list容器中最后写入文件。 我这里的处理办法比较简单粗暴,用了NewtonJson去序列化每个Node信息后直接使用|去分割每个Node的信息后序列化成了string保存起来。可以使用protobuf等二进制数据去序列化,进一步地优化文件的体积大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public virtual void SaveData (){ List<NodeSerializationData> nodeSaveData = new List<NodeSerializationData>(); foreach (var node in nodes) { if (node == null ) { continue ; } if (node is NodeBase nodeBase) { nodeSaveData.Add(nodeBase.GetSerializationData()); } } if (nodeSaveData.Count <= 0 ) { Debug.Log("Graph的Node数量为0!" ); return ; } var data = MergeNodeData(nodeSaveData); GraphEditorUnility.SaveDataToBytes(data, m_graphData.graphName); } private string MergeNodeData (List<NodeSerializationData> nodeData ){ StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < nodeData.Count; i++) { string data = nodeData[i].Serialize(); sb.Append(data); if (i < nodeData.Count - 1 ) { sb.Append("|" ); } } return sb.ToString(); }
结果 因为是继承Unity的GraphView和Node,所以现在已经能自由的添加/删除连接、添加/删除节点了。节点编辑器的基本功能已经实现完成了。