/// <summary> /// Creates a working copy of the specified nodeCanvas, and optionally also of it's associated editorStates. /// This breaks the link of this object to any stored assets and references. That means, that all changes to this object will have to be explicitly saved. /// </summary> public static NodeCanvas CreateWorkingCopy(NodeCanvas nodeCanvas, bool editorStates = true) { if (nodeCanvas == null) { return(null); } // Lists holding initial and cloned version of each ScriptableObject for later replacement of references List <ScriptableObject> allSOs = new List <ScriptableObject> (); List <ScriptableObject> clonedSOs = new List <ScriptableObject> (); System.Func <ScriptableObject, ScriptableObject> copySOs = (ScriptableObject so) => ReplaceSO(allSOs, clonedSOs, so); // Clone and enter the canvas object and it's referenced SOs nodeCanvas = AddClonedSO(allSOs, clonedSOs, nodeCanvas); AddClonedSOs(allSOs, clonedSOs, nodeCanvas.GetScriptableObjects()); // Iterate over the core ScriptableObjects in the canvas and clone them for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++) { Node node = nodeCanvas.nodes[nodeCnt]; // Clone Node and additional scriptableObjects Node clonedNode = AddClonedSO(allSOs, clonedSOs, node); AddClonedSOs(allSOs, clonedSOs, clonedNode.GetScriptableObjects()); // Update representative port list connectionPorts with all ports and clone them ConnectionPortManager.UpdatePortLists(clonedNode); AddClonedSOs(allSOs, clonedSOs, clonedNode.connectionPorts); } // Replace every reference to any of the initial SOs of the first list with the respective clones of the second list nodeCanvas.CopyScriptableObjects(copySOs); for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++) { // Replace SOs in all Nodes Node node = nodeCanvas.nodes[nodeCnt]; // Replace node and additional ScriptableObjects with their copies Node clonedNode = nodeCanvas.nodes[nodeCnt] = ReplaceSO(allSOs, clonedSOs, node); clonedNode.canvas = nodeCanvas; clonedNode.CopyScriptableObjects(copySOs); // Replace ConnectionPorts foreach (ConnectionPortDeclaration portDecl in ConnectionPortManager.GetPortDeclarationEnumerator(clonedNode, true)) { // Iterate over each static port declaration and replace it with it's connections ConnectionPort port = (ConnectionPort)portDecl.portField.GetValue(clonedNode); port = ReplaceSO(allSOs, clonedSOs, port); for (int c = 0; c < port.connections.Count; c++) { port.connections[c] = ReplaceSO(allSOs, clonedSOs, port.connections[c]); } port.body = clonedNode; portDecl.portField.SetValue(clonedNode, port); } for (int i = 0; i < clonedNode.dynamicConnectionPorts.Count; i++) { // Iterate over all dynamic connection ports ConnectionPort port = clonedNode.dynamicConnectionPorts[i]; port = ReplaceSO(allSOs, clonedSOs, port); for (int c = 0; c < port.connections.Count; c++) { port.connections[c] = ReplaceSO(allSOs, clonedSOs, port.connections[c]); } port.body = clonedNode; clonedNode.dynamicConnectionPorts[i] = port; } } if (editorStates) // Clone the editorStates { nodeCanvas.editorStates = CreateWorkingCopy(nodeCanvas.editorStates, nodeCanvas); } // Fix references in editorStates to Nodes in the canvas foreach (NodeEditorState state in nodeCanvas.editorStates) { state.selectedNode = ReplaceSO(allSOs, clonedSOs, state.selectedNode); } return(nodeCanvas); }
/// <summary> /// Creates a node of the specified ID at pos on the specified canvas, optionally auto-connecting the specified output to a matching input /// silent disables any events, init specifies whether OnCreate should be called /// </summary> public static Node Create(string nodeID, Vector2 pos, NodeCanvas hostCanvas, ConnectionPort connectingPort = null, bool silent = false, bool init = true) { if (string.IsNullOrEmpty(nodeID) || hostCanvas == null) { throw new ArgumentException(); } if (!NodeCanvasManager.CheckCanvasCompability(nodeID, hostCanvas.GetType())) { throw new UnityException("Cannot create Node with ID '" + nodeID + "' as it is not compatible with the current canavs type (" + hostCanvas.GetType().ToString() + ")!"); } if (!hostCanvas.CanAddNode(nodeID)) { throw new UnityException("Cannot create Node with ID '" + nodeID + "' on the current canvas of type (" + hostCanvas.GetType().ToString() + ")!"); } // Create node from data NodeTypeData data = NodeTypes.getNodeData(nodeID); Node node = (Node)CreateInstance(data.type); if (node == null) { return(null); } // Init node state node.canvas = hostCanvas; node.name = node.Title; node.autoSize = node.DefaultSize; node.position = pos; ConnectionPortManager.UpdateConnectionPorts(node); if (init) { node.OnCreate(); } if (connectingPort != null) { // Handle auto-connection and link the output to the first compatible input for (int i = 0; i < node.connectionPorts.Count; i++) { if (node.connectionPorts[i].TryApplyConnection(connectingPort, true)) { break; } } } // Add node to host canvas hostCanvas.nodes.Add(node); if (!silent) { // Callbacks hostCanvas.OnNodeChange(connectingPort != null ? connectingPort.body : node); NodeEditorCallbacks.IssueOnAddNode(node); hostCanvas.Validate(); NodeEditor.RepaintClients(); } #if UNITY_EDITOR if (!silent) { List <ConnectionPort> connectedPorts = new List <ConnectionPort>(); foreach (ConnectionPort port in node.connectionPorts) { // 'Encode' connected ports in one list (double level cannot be serialized) foreach (ConnectionPort conn in port.connections) { connectedPorts.Add(conn); } connectedPorts.Add(null); } Node createdNode = node; UndoPro.UndoProManager.RecordOperation( () => NodeEditorUndoActions.ReinstateNode(createdNode, connectedPorts), () => NodeEditorUndoActions.RemoveNode(createdNode), "Create Node"); // Make sure the new node is in the memory dump NodeEditorUndoActions.CompleteSOMemoryDump(hostCanvas); } #endif return(node); }
/// <summary> /// Saves the the specified NodeCanvas as a new asset at path, optionally as a working copy and overwriting any existing save at path /// </summary> public static void SaveNodeCanvas(string path, ref NodeCanvas nodeCanvas, bool createWorkingCopy, bool safeOverwrite = true) { #if !UNITY_EDITOR throw new System.NotImplementedException(); #else if (string.IsNullOrEmpty(path)) { throw new System.ArgumentNullException("Cannot save NodeCanvas: No path specified!"); } if (nodeCanvas == null) { throw new System.ArgumentNullException("Cannot save NodeCanvas: The specified NodeCanvas that should be saved to path '" + path + "' is null!"); } if (nodeCanvas.GetType() == typeof(NodeCanvas)) { throw new System.ArgumentException("Cannot save NodeCanvas: The NodeCanvas has no explicit type! Please convert it to a valid sub-type of NodeCanvas!"); } if (nodeCanvas.allowSceneSaveOnly) { throw new System.InvalidOperationException("Cannot save NodeCanvas: NodeCanvas is marked to contain scene data and cannot be saved as an asset!"); } nodeCanvas.Validate(); if (nodeCanvas.livesInScene) { Debug.LogWarning("Attempting to save scene canvas '" + nodeCanvas.name + "' to an asset, references to scene object may be broken!" + (!createWorkingCopy? " Forcing creation of working copy!" : "")); createWorkingCopy = true; } if (UnityEditor.AssetDatabase.Contains(nodeCanvas) && UnityEditor.AssetDatabase.GetAssetPath(nodeCanvas) != path) { Debug.LogWarning("Trying to create a duplicate save file for '" + nodeCanvas.name + "'! Forcing creation of working copy!"); nodeCanvas = CreateWorkingCopy(nodeCanvas); } // Prepare and update source path of the canvas path = ResourceManager.PreparePath(path); nodeCanvas.UpdateSource(path); // Preprocess the canvas NodeCanvas processedCanvas = nodeCanvas; processedCanvas.OnBeforeSavingCanvas(); if (createWorkingCopy) { processedCanvas = CreateWorkingCopy(processedCanvas); } // Differenciate canvasSave as the canvas asset and nodeCanvas as the source incase an existing save has been overwritten NodeCanvas canvasSave = processedCanvas; NodeCanvas prevSave; if (safeOverwrite && (prevSave = ResourceManager.LoadResource <NodeCanvas> (path)) != null && prevSave.GetType() == canvasSave.GetType()) { // OVERWRITE: Delete contents of old save Object[] subAssets = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(path); for (int i = 0; i < subAssets.Length; i++) { // Delete all subassets except the main canvas to preserve references if (subAssets[i] != prevSave) { Object.DestroyImmediate(subAssets[i], true); } } // Overwrite main canvas OverwriteCanvas(ref prevSave, processedCanvas); canvasSave = prevSave; } else { // Write main canvas UnityEditor.AssetDatabase.DeleteAsset(path); UnityEditor.AssetDatabase.CreateAsset(processedCanvas, path); } // Write editorStates AddSubAssets(processedCanvas.editorStates, canvasSave); // Write nodes + contents foreach (Node node in processedCanvas.nodes) { // Write node and additional scriptable objects AddSubAsset(node, canvasSave); AddSubAssets(node.GetScriptableObjects(), node); // Make sure all node ports are included in the representative connectionPorts list ConnectionPortManager.UpdatePortLists(node); foreach (ConnectionPort port in node.connectionPorts) { AddSubAsset(port, node); } } UnityEditor.AssetDatabase.SaveAssets(); UnityEditor.AssetDatabase.Refresh(); NodeEditorCallbacks.IssueOnSaveCanvas(canvasSave); #endif }
/// <summary> /// Creates a node of the specified ID at pos on the specified canvas, optionally auto-connecting the specified output to a matching input /// silent disables any events, init specifies whether OnCreate should be called /// </summary> public static Node Create(string nodeID, Vector2 pos, NodeCanvas hostCanvas, ConnectionPort connectingPort = null, bool silent = false, bool init = true) { if (string.IsNullOrEmpty(nodeID) || hostCanvas == null) { throw new ArgumentException(); } if (!NodeCanvasManager.CheckCanvasCompability(nodeID, hostCanvas.GetType())) { throw new UnityException("Cannot create Node with ID '" + nodeID + "' as it is not compatible with the current canavs type (" + hostCanvas.GetType().ToString() + ")!"); } if (!hostCanvas.CanAddNode(nodeID)) { throw new UnityException("Cannot create Node with ID '" + nodeID + "' on the current canvas of type (" + hostCanvas.GetType().ToString() + ")!"); } // Create node from data NodeTypeData data = NodeTypes.GetNodeData(nodeID); Node node = (Node)CreateInstance(data.type); if (node == null) { return(null); } // Init node state node.canvas = hostCanvas; node.name = node.Title; node.autoSize = node.DefaultSize; node.position = pos; Undo.RecordObject(hostCanvas, "NodeEditor_新增保存"); NodeEditorSaveManager.AddSubAsset(node, hostCanvas); ConnectionPortManager.UpdateConnectionPorts(node); if (init) { node.OnCreate(); } if (connectingPort != null) { // Handle auto-connection and link the output to the first compatible input for (int i = 0; i < node.connectionPorts.Count; i++) { if (node.connectionPorts[i].TryApplyConnection(connectingPort, silent)) { break; } } } // Add node to host canvas hostCanvas.nodes.Add(node); if (!silent) { // Callbacks NodeEditorCallbacks.IssueOnAddNode(node); hostCanvas.Validate(); NodeEditor.RepaintClients(); } return(node); }