/// <summary> /// Saves the nodeCanvas in the current scene under the specified name along with the specified editorStates or, if specified, their working copies /// If also stored as an asset, it will loose the reference to the asset first /// </summary> public static void SaveSceneNodeCanvas(string saveName, ref NodeCanvas nodeCanvas, bool createWorkingCopy, bool safeOverwrite = true) { if (string.IsNullOrEmpty(saveName)) { Debug.LogError("Cannot save Canvas to scene: No save name specified!"); return; } if (nodeCanvas.GetType() == typeof(NodeCanvas)) { throw new UnityException("Cannot save NodeCanvas: The NodeCanvas has no explicit type: '" + nodeCanvas.GetType().ToString() + "'. Please convert it to a valid type!"); } if (!nodeCanvas.livesInScene #if UNITY_EDITOR // Make sure the canvas has no reference to an asset || UnityEditor.AssetDatabase.Contains(nodeCanvas) #endif ) { //Debug.LogWarning ("Forced to create working copy of '" + saveName + "' when saving to scene because it already exists as an asset!"); nodeCanvas = CreateWorkingCopy(nodeCanvas, true); } else { nodeCanvas.Validate(true); } nodeCanvas.livesInScene = true; nodeCanvas.name = saveName; nodeCanvas.OnBeforeSavingCanvas(); nodeCanvas.UpdateSource("SCENE/" + saveName); NodeCanvas savedCanvas = nodeCanvas; // Preprocess canvas ProcessCanvas(ref savedCanvas, createWorkingCopy); // Get the saveHolder and store the canvas NodeCanvasSceneSave sceneSave; #if UNITY_EDITOR if ((sceneSave = FindSceneSave(saveName)) != null && safeOverwrite) // OVERWRITE { OverwriteCanvas(ref sceneSave.savedNodeCanvas, savedCanvas); } else { if (sceneSave == null) { sceneSave = CreateSceneSave(saveName); } sceneSave.savedNodeCanvas = savedCanvas; } if (!Application.isPlaying) { #if UNITY_5_3_OR_NEWER UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene()); #else UnityEditor.EditorApplication.MarkSceneDirty(); #endif } #else sceneSave = FindOrCreateSceneSave(saveName); sceneSave.savedNodeCanvas = savedCanvas; #endif #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(sceneSaveHolder); #endif }
/// <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) { nodeCanvas.Validate(true); // 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]; node.CheckNodeKnobMigration(); // Clone Node and additional scriptableObjects Node clonedNode = AddClonedSO(allSOs, clonedSOs, node); AddClonedSOs(allSOs, clonedSOs, clonedNode.GetScriptableObjects()); // Clone NodeKnobs foreach (NodeKnob knob in clonedNode.nodeKnobs) { AddClonedSO(allSOs, clonedSOs, knob); } } // 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.CopyScriptableObjects(copySOs); // Replace NodeKnobs and restore Inputs/Outputs by NodeKnob type clonedNode.Inputs = new List <NodeInput> (); clonedNode.Outputs = new List <NodeOutput> (); for (int knobCnt = 0; knobCnt < clonedNode.nodeKnobs.Count; knobCnt++) { // Clone generic NodeKnobs NodeKnob knob = clonedNode.nodeKnobs[knobCnt] = ReplaceSO(allSOs, clonedSOs, clonedNode.nodeKnobs[knobCnt]); knob.body = clonedNode; knob.CopyScriptableObjects(copySOs); // Add inputs/outputs to their lists again if (knob is NodeInput) { clonedNode.Inputs.Add(knob as NodeInput); } else if (knob is NodeOutput) { clonedNode.Outputs.Add(knob as NodeOutput); } } } 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 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) { nodeCanvas.Validate(true); nodeCanvas = Clone(nodeCanvas); // Take each SO, make a clone of it and store both versions in the respective list // This will only iterate over the 'source instances' List <ScriptableObject> allSOs = new List <ScriptableObject> (); List <ScriptableObject> clonedSOs = new List <ScriptableObject> (); for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++) { Node node = nodeCanvas.nodes[nodeCnt]; node.CheckNodeKnobMigration(); // Clone Node and additional scriptableObjects Node clonedNode = AddClonedSO(allSOs, clonedSOs, node); AddClonedSOs(allSOs, clonedSOs, clonedNode.GetScriptableObjects()); foreach (NodeKnob knob in clonedNode.nodeKnobs) { // Clone NodeKnobs and additional scriptableObjects AddClonedSO(allSOs, clonedSOs, knob); AddClonedSOs(allSOs, clonedSOs, knob.GetScriptableObjects()); } } // Replace every reference to any of the initial SOs of the first list with the respective clones of the second list for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++) { // Clone Nodes, structural content and additional scriptableObjects Node node = nodeCanvas.nodes[nodeCnt]; // Replace node and additional ScriptableObjects Node clonedNode = nodeCanvas.nodes[nodeCnt] = ReplaceSO(allSOs, clonedSOs, node); clonedNode.CopyScriptableObjects((ScriptableObject so) => ReplaceSO(allSOs, clonedSOs, so)); // We're going to restore these from NodeKnobs, no need to Replace muliple times clonedNode.Inputs = new List <NodeInput> (); clonedNode.Outputs = new List <NodeOutput> (); for (int knobCnt = 0; knobCnt < clonedNode.nodeKnobs.Count; knobCnt++) { // Clone generic NodeKnobs NodeKnob knob = clonedNode.nodeKnobs[knobCnt] = ReplaceSO(allSOs, clonedSOs, clonedNode.nodeKnobs[knobCnt]); knob.body = clonedNode; // Replace additional scriptableObjects in the NodeKnob knob.CopyScriptableObjects((ScriptableObject so) => ReplaceSO(allSOs, clonedSOs, so)); // Add it into Inputs/Outputs again if (knob is NodeInput) { clonedNode.Inputs.Add(knob as NodeInput); } else if (knob is NodeOutput) { clonedNode.Outputs.Add(knob as NodeOutput); } } } if (editorStates) { nodeCanvas.editorStates = CreateWorkingCopy(nodeCanvas.editorStates, nodeCanvas); foreach (NodeEditorState state in nodeCanvas.editorStates) { state.selectedNode = ReplaceSO(allSOs, clonedSOs, state.selectedNode); } } else { foreach (NodeEditorState state in nodeCanvas.editorStates) { state.selectedNode = null; } } return(nodeCanvas); }
/// <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> /// Saves the nodeCanvas in the current scene under the specified name, optionally as a working copy and overwriting any existing save at path /// If the specified canvas is stored as an asset, the saved canvas will loose the reference to the asset /// </summary> public static void SaveSceneNodeCanvas(string saveName, ref NodeCanvas nodeCanvas, bool createWorkingCopy, bool safeOverwrite = true) { if (string.IsNullOrEmpty(saveName)) { throw new System.ArgumentNullException("Cannot save Canvas to scene: No save name specified!"); } if (nodeCanvas == null) { throw new System.ArgumentNullException("Cannot save NodeCanvas: The specified NodeCanvas that should be saved as '" + saveName + "' 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 (saveName.StartsWith("SCENE/")) { saveName = saveName.Substring(6); } nodeCanvas.Validate(); if (!nodeCanvas.livesInScene #if UNITY_EDITOR // Make sure the canvas has no reference to an asset || UnityEditor.AssetDatabase.Contains(nodeCanvas) #endif ) { Debug.LogWarning("Creating scene save '" + nodeCanvas.name + "' for canvas saved as an asset! Forcing creation of working copy!"); nodeCanvas = CreateWorkingCopy(nodeCanvas); } // Update the source of the canvas nodeCanvas.UpdateSource("SCENE/" + saveName); // Preprocess the canvas NodeCanvas processedCanvas = nodeCanvas; processedCanvas.OnBeforeSavingCanvas(); if (createWorkingCopy) { processedCanvas = CreateWorkingCopy(processedCanvas); } // Get the saveHolder and store the canvas NodeCanvas savedCanvas = processedCanvas; NodeCanvasSceneSave sceneSave = FindSceneSave(saveName, true); #if UNITY_EDITOR if (sceneSave.savedNodeCanvas != null && safeOverwrite && sceneSave.savedNodeCanvas.GetType() == savedCanvas.GetType()) // OVERWRITE { OverwriteCanvas(ref sceneSave.savedNodeCanvas, savedCanvas); } if (!Application.isPlaying) { // Set Dirty UnityEditor.EditorUtility.SetDirty(sceneSave.gameObject); #if UNITY_5_3_OR_NEWER || UNITY_5_3 UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(sceneSave.gameObject.scene); #else UnityEditor.EditorApplication.MarkSceneDirty(); #endif } #endif sceneSave.savedNodeCanvas = savedCanvas; NodeEditorCallbacks.IssueOnSaveCanvas(savedCanvas); }
/// <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> /// 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); }