public static bool addNodeResponse(DialogueNode node) { if (node == null) { return(false); } DialogueResponse[] oldResponses = node.responses; bool oldWasEmpty = oldResponses == null || oldResponses.Length == 0; int oldResponseCount = oldWasEmpty ? 0 : oldResponses.Length; DialogueResponse[] newResponses = new DialogueResponse[oldWasEmpty ? 1 : oldResponseCount + 1]; if (!oldWasEmpty) { for (int i = 0; i < oldResponseCount; ++i) { newResponses[i] = oldResponses[i]; } } DialogueResponse newResponse = DialogueResponse.Blank; newResponse.responseText = "[...]"; newResponses[oldResponseCount] = newResponse; node.responses = newResponses; EditorUtility.SetDirty(node); return(true); }
public bool selectNode(DialogueNode newNode) { if (newNode == null) { Debug.LogError("[DialogueController] Error! Unable to select null dialogue node! Aborting selection."); return(false); } if (newNode.content == null || newNode.content.Length == 0) { Debug.LogError("[DialogueController] Error! Dialogue nodes must have at least 1 content item! Aborting selection."); return(false); } // Assign new node as current: currentNode = newNode; // Reset flags and indices: currentContentIndex = 0; // Execute the new node's first content's binding: executeBinding(ref currentNode.content[0].eventBinding); // Load the node's responses and extract those whose conditions have been met already: DialogueResponse[] allResponses = CurrentNode.responses; currentResponses.Clear(); for (int i = 0; i < allResponses.Length; ++i) { DialogueResponse resp = allResponses[i]; if (string.IsNullOrEmpty(resp.conditions.keyword)) { currentResponses.Add(resp); } else if (trigger != null && trigger.checkDialogueCondition(ref resp.conditions)) { currentResponses.Add(resp); } } return(true); }
public bool selectResponse(int responseIndex) { // Make sure there currently is an active node: if (currentNode == null) { Debug.LogError("[DialogueController] Error! Unable to select response from null node! Aborting response."); return(false); } // Ignore response index and show next content item instead, if multiple contents exist for the current node: if (currentNode.content != null && currentNode.content.Length > 1 && currentContentIndex < currentNode.content.Length - 1) { // Increment content index: currentContentIndex++; // Execute any binding assigned to the new content: executeBinding(ref currentNode.content[currentContentIndex].eventBinding); // Notify the dialogue trigger that a next content will now be displayed: if (trigger != null) { trigger.notifyDialogueEvent(DialogueEvent.ContentChanged); } return(true); } // Verify index and currently active selection: DialogueResponse[] responses = getCurrentResponses(); if (responses == null || responseIndex < 0 || responseIndex >= responses.Length) { Debug.LogError("[DialogueController] Error! Invalid response index " + responseIndex + " selected! Aborting response."); return(false); } // If a followup node was provided, select it right away: DialogueResponse selected = responses[responseIndex]; if (selected.nextNode != null) { // Notify the dialogue trigger that a response was selected: if (trigger != null) { trigger.notifyDialogueEvent(DialogueEvent.PlayerResponse); } // Select followup node: return(selectNode(selected.nextNode)); } // There was no followup node, so proceed according to the dialogue's behaviour mode instead: DialogueBehaviour behaviour = dialogue.behaviour; bool result = true; switch (behaviour.onNullResponse) { // End dialogue: case DialogueBehaviour.NullResponseAction.End: result = endDialogue(); break; // Return to root node: case DialogueBehaviour.NullResponseAction.ReturnToRoot: { DialogueRoot root = DialogueRoot.Blank; getRootNode(ref root); result = selectNode(root.node); } break; // Refuse any further actions and just stay on the current node: case DialogueBehaviour.NullResponseAction.None: result = false; break; // Unidentified behaviour, throw an error. Though I can't even begin to imagine how you'd mess this one up: default: Debug.LogError("[DialogueController] Error! Unidentified NullResponse behaviour type: " + behaviour.onNullResponse.ToString()); result = false; break; } return(result); }
public bool drawNodes(Dialogue asset, List <Node> inNodes, Vector2 inOffet) { if (asset == null || inNodes == null) { return(false); } offset = inOffet; bool changed = false; nodes = inNodes; Vector2 rootPos = new Vector2(0, Screen.height * 0.5f); Rect rootRect = new Rect(rootPos.x - offset.x - 39, rootPos.y - offset.y - 7, 38, 14); EditorGUI.DrawRect(new Rect(rootRect.x - 1, rootRect.y - 1, rootRect.width + 2, rootRect.height + 2), Color.black); EditorGUI.DrawRect(rootRect, new Color(0.75f, 0.75f, 0.75f)); EditorGUI.LabelField(rootRect, "Root"); if (nodes != null && nodes.Count != 0) { // Draw links between nodes first: for (int i = 0; i < nodes.Count; ++i) { Node node = nodes[i]; DialogueNode dNode = node.node; // Links from dialogue root: if (node.rootId >= 0) { DialogueRoot root = asset.rootNodes[node.rootId]; bool rootConditions = !string.IsNullOrEmpty(root.conditions.keyword); drawNodeLink(rootPos, node, i, 0, rootConditions); } // Links to response nodes: if (dNode.responses != null) { for (int j = 0; j < dNode.responses.Length; ++j) { DialogueResponse resp = dNode.responses[j]; Vector2 respPos = getResponsePosition(node, j); // Draw short red lines to indicate a response is no linked to any node: if (resp.nextNode == null) { respPos -= offset; Handles.color = Color.red; Handles.DrawLine(respPos, respPos + Vector2.right * 32); continue; } // Figure out the editor node representing the response's connected node asset: Node targetNode = Node.Blank; int k = 0; for (k = 0; k < nodes.Count; ++k) { Node n = nodes[k]; if (n.node == resp.nextNode) { targetNode = n; break; } } // Draw link in a different color if conditions apply for the associated response: bool hasConditions = !string.IsNullOrEmpty(resp.conditions.keyword); // Draw a bezier curve starting at the response button and leading to the connected node: drawNodeLink(respPos, targetNode, i, k, hasConditions); } } } // Draw the actual individual nodes: for (int i = 0; i < nodes.Count; ++i) { Node node = nodes[i]; // If a node is null, mark it for later deletion: if (node.node == null) { node.rootId = -1; nodes[i] = node; continue; } bool isSelected = selected.node == node.node; // Update drag&drop: NodeAction actions = new NodeAction() { changed = false, selected = false, startDragDrop = false }; if (isSelected && dragNDrop) { Vector2 mousePos = Event.current.mousePosition; node.rect.x = mousePos.x - 123 + offset.x; node.rect.y = mousePos.y - 5 + offset.y; actions.changed = true; } // Round positions to whole numbers, 'cause without it the damn thing looks ugly as hell: node.rect.x = Mathf.Round(node.rect.x); node.rect.y = Mathf.Round(node.rect.y); // Draw the node on screen: actions = drawNode(ref node, isSelected, i); // Raise the dirty flag if the node has been changed: if (actions.changed) { nodes[i] = node; changed = true; } // Select the node if a related action was performed: if (actions.selected) { setSelection(node); } // Start drag&drop of the node if a related action was performed: if (actions.startDragDrop) { toggleDragNDrop(); } } // Draw response dropdown overlay: if (selectedResponseDropdown && selectedResponse >= 0 && selectedResponse < selected.node.responses.Length) { Node respNode = selected; DialogueNode respDNode = respNode.node; DialogueResponse resp = respDNode.responses[selectedResponse]; const float w = 160; const float h = 94; Rect respRect = respNode.rect; float respPosY = Mathf.Round(respRect.y + 33 + (-respDNode.responses.Length * .5f + selectedResponse) * 17); respRect = new Rect(respRect.x - offset.x + 138, respPosY - offset.y, w + 2, h + 2); GUI.BeginGroup(respRect); EditorGUI.DrawRect(new Rect(0, 0, w + 2, h + 2), Color.black); EditorGUI.DrawRect(new Rect(1, 1, w, h), new Color(0.75f, 0.75f, 0.75f)); EditorGUI.LabelField(new Rect(2, 2, w, 16), string.Format("Edit response {0}:", selectedResponse)); // Button for creating a new node linked to this response: if (GUI.Button(new Rect(2, 20, 78, 16), "New Node")) { if (createNewNode(asset, ref nodes)) { int newNodeId = nodes.Count - 1; if (createNodeLink(selected, selectedResponse, newNodeId)) { //Debug.Log("Creating new node connected to selected node's response " + selectedResponse); setSelection(nodes[newNodeId]); } } } // Button for removing the currently set link: bool uiShowClearLink = selected.node.responses != null && selectedResponse >= 0 && selectedResponse < selected.node.responses.Length && selected.node.responses[selectedResponse].nextNode == null; EditorGUI.BeginDisabledGroup(uiShowClearLink); if (GUI.Button(new Rect(82, 20, 78, 16), "Clear Link")) { //Debug.Log("Resetting response " + selectedResponse + " in selected node."); createNodeLink(respNode, selectedResponse, -1); } EditorGUI.EndDisabledGroup(); // Button and input field for linking to a specific node using its displayed node ID: if (GUI.Button(new Rect(2, 38, 128, 16), "Link to ID")) { createNodeLink(respNode, selectedResponse, responseTargetNodeId); //Debug.Log("Connecting selected node's response " + selectedResponse + " to node " + responseTargetNodeId); } responseTargetNodeId = EditorGUI.DelayedIntField(new Rect(131, 38, 29, 16), responseTargetNodeId); bool prevChanged = GUI.changed; GUI.changed = false; EditorGUI.LabelField(new Rect(2, 60, 34, 16), "Text"); resp.responseText = EditorGUI.DelayedTextField(new Rect(36, 60, w - 36, 16), resp.responseText); EditorGUI.LabelField(new Rect(2, 78, 34, 16), "Cond"); resp.conditions.keyword = EditorGUI.DelayedTextField(new Rect(36, 78, w - 36, 16), resp.conditions.keyword); if (GUI.changed) { EditorUtility.SetDirty(asset); } GUI.changed = GUI.changed || prevChanged; GUI.EndGroup(); } } return(changed); }
public void autoLayoutNodes() { if (nodes == null) { return; } int[] depths = new int[nodes.Count]; Dictionary <DialogueNode, int> nodeIndices = new Dictionary <DialogueNode, int>(); for (int i = 0; i < depths.Length; ++i) { depths[i] = -1; DialogueNode n = nodes[i].node; if (n != null) { nodeIndices.Add(n, i); } } List <int> currentNodeIndices = new List <int>(); List <int> nextNodeIndices = new List <int>(); foreach (Node n in nodes) { if (n.node != null && n.rootId >= 0) { int nIndex; nodeIndices.TryGetValue(n.node, out nIndex); currentNodeIndices.Add(nIndex); } } int currentDepth = 0; List <List <Node> > depthCounts = new List <List <Node> >(); while (currentNodeIndices.Count != 0 && currentDepth < 256) { // NOTE: 256 was set as an arbitrary limit to prevent crashes from loops in dialogue structure. List <Node> depthList = new List <Node>(); depthCounts.Add(depthList); for (int i = 0; i < currentNodeIndices.Count; i++) { int currentIndex = currentNodeIndices[i]; Node node = nodes[currentIndex]; DialogueNode dNode = node.node; if (dNode == null) { depths[currentIndex] = 255; continue; } // Skip nodes that have already been sorted (for whatever reason): if (depths[currentIndex] >= 0) { continue; } depths[currentIndex] = currentDepth; depthList.Add(node); for (int j = 0; j < dNode.responses.Length; ++j) { DialogueResponse resp = dNode.responses[j]; if (resp.nextNode == null) { continue; } int respNodeIndex; if (!nodeIndices.TryGetValue(resp.nextNode, out respNodeIndex)) { continue; } Node respNode = nodes[respNodeIndex]; if (respNode.node != null && respNode.node != dNode && depths[respNodeIndex] < 0) { nextNodeIndices.Add(respNodeIndex); } } } List <int> newNextIndices = currentNodeIndices; currentNodeIndices = nextNodeIndices; nextNodeIndices = newNextIndices; nextNodeIndices.Clear(); currentDepth++; } // Push any disconnected, invalid or new nodes to the very end of the depth spectrum: int maxDepth = depthCounts.Count; depthCounts.Add(new List <Node>()); foreach (Node n in nodes) { if (n.node == null) { continue; } int nIndex; if (!nodeIndices.TryGetValue(n.node, out nIndex)) { continue; } int nDepth = depths[nIndex]; if (nDepth < 0) { depths[nIndex] = maxDepth; depthCounts[maxDepth].Add(n); } } // Sort nodes vertically, to avoid overly spaghetti-like links: float[] weights = new float[nodes.Count]; for (int i = 0; i < weights.Length; ++i) { weights[i] = 0.0f; } for (int i = 0; i < depthCounts.Count - 1; ++i) { List <Node> depthNodes = depthCounts[i]; int[] depthNodeIndices = new int[depthNodes.Count]; for (int j = 0; j < depthNodes.Count; ++j) { Node node = depthNodes[j]; if (node.node == null) { continue; } int sourceIndex; nodeIndices.TryGetValue(node.node, out sourceIndex); depthNodeIndices[j] = sourceIndex; float sourceWeight = weights[sourceIndex]; for (int k = 0; k < node.node.responses.Length; ++k) { // Calculate weighting based on precursing nodes' weight and the responses' indices: float weight = sourceWeight * 1.317f + k; // note: odd multiplier value makes identical weightings less likely. DialogueResponse resp = node.node.responses[k]; if (resp.nextNode == null) { continue; } int targetIndex; if (!nodeIndices.TryGetValue(resp.nextNode, out targetIndex)) { continue; } weights[targetIndex] += weight; } } // Sort nodes on a same depth based on their cumulative weighting: float lowestWeight = 1.0e+8f; for (int j = 0; j < depthNodes.Count; ++j) { for (int k = j; k < depthNodes.Count; ++k) { int sourceIndex = depthNodeIndices[k]; float kWeight = weights[sourceIndex]; if (kWeight < lowestWeight) { Node jNode = depthNodes[j]; depthNodes[j] = depthNodes[k]; depthNodes[k] = jNode; lowestWeight = kWeight; } } } } // Calculate screen positions for all nodes: const float nodeOffsetX = 42; const float nodeOffsetY0 = 10; const float nodeHeight0 = 70; for (int i = 0; i < nodes.Count; ++i) { float nodeOffsetY = nodeOffsetY0 + (Screen.height - nodeHeight0) * 0.5f; const float nodeWidth = 130 + 64 + 50; const float nodeHeight = nodeHeight0 + 18; Node node = nodes[i]; int nIndex; if (!nodeIndices.TryGetValue(node.node, out nIndex)) { continue; } int nDepth = depths[nIndex]; List <Node> depthNodes = depthCounts[nDepth]; int nDepthIndex = depthNodes.IndexOf(node); float countOffset = -0.5f * (depthNodes.Count - 1) + nDepthIndex; float posX = nodeOffsetX + nDepth * nodeWidth; float posY = nodeOffsetY + countOffset * nodeHeight; node.rect = new Rect(posX, posY, 130, 70); nodes[i] = node; } }
private NodeAction drawNode(ref Node node, bool isSelected, int nodeIndex) { NodeAction actions = new NodeAction() { changed = false, selected = false }; bool prevChanged = GUI.changed; GUI.changed = false; const float nodeHeight = 70; Rect nRect = node.rect; nRect.position -= offset; // Draw incoming link endpoint: EditorGUI.DrawRect(new Rect(nRect.x - 4, nRect.y + nodeHeight * 0.5f - 2, 4, 5), Color.black); // Draw black outline: EditorGUI.DrawRect(new Rect(nRect.x - 1, nRect.y - 1, 130, nodeHeight), Color.black); // Draw content rect: GUI.BeginGroup(new Rect(nRect)); Color boxColor = isSelected ? new Color(0.5f, 0.75f, 0.5f) : new Color(0.75f, 0.75f, 0.75f); EditorGUI.DrawRect(new Rect(0, 0, 128, 68), boxColor); EditorGUI.DrawRect(new Rect(0, 15, 128, 1), Color.black); DialogueNode dNode = node.node; // HEADER: // Display editor ID and name of the node: string nodeTitleTxt = nodeIndex + ") "; if (isSelected) { dNode.name = (EditorGUI.DelayedTextField(new Rect(20, 0, 98, 16), dNode.name)); } else { nodeTitleTxt += dNode.name; } EditorGUI.LabelField(new Rect(0, 0, 106, 16), nodeTitleTxt); // Draw a tiny drag&drop button at the top right: if (GUI.Button(new Rect(119, 1, 8, 8), "")) { actions.startDragDrop = true; actions.selected = true; } // EDITING: string contTxt = dNode.content != null && dNode.content.Length != 0 && !string.IsNullOrEmpty(dNode.content[0].text) ? dNode.content[0].text : "<empty>"; EditorGUI.LabelField(new Rect(0, 17, 106, 16), contTxt, EditorStyles.miniLabel); EditorGUI.LabelField(new Rect(0, 34, 106, 16), "Contents: " + dNode.content.Length.ToString()); for (int i = 0; i < dNode.content.Length; ++i) { Binding b = dNode.content[i].eventBinding; if (!string.IsNullOrEmpty(b.path)) { EditorGUI.LabelField(new Rect(0, 51, 126, 16), b.ToString(), EditorStyles.miniLabel); break; } } // Button to select a node for content editing: if (GUI.Button(new Rect(107, 17, 20, 16), ">")) { actions.selected = true; } // Button for adding content items: if (GUI.Button(new Rect(107, 34, 20, 16), "+")) { actions.changed = true; addNodeContent(dNode, dNode.content.Length); } /* * // Button for adding responses: * if(GUI.Button(new Rect(107,51,20,16), "+")) * { * actions.changed = true; * addNodeResponse(dNode); * } */ GUI.EndGroup(); // Draw buttons for each response in a column directly to the right of the node itself: if (dNode.responses != null) { int respCount = dNode.responses.Length; float respSize = respCount * 17.0f; Rect respRect = new Rect(nRect.x + 129, nRect.y + 34 - respSize * .5f, 100, respSize + 17); GUI.BeginGroup(respRect); for (int i = 0; i < respCount; ++i) { DialogueResponse response = dNode.responses[i]; if (GUI.Button(new Rect(0, i * 17 - 1, 20, 16), i.ToString())) { setSelection(node); int prevSelectedResponse = selectedResponse; selectedResponse = i; if (Event.current.button == 1) { if (selectedResponse == prevSelectedResponse) { toggleResponseDropdown(); } } else { selectedResponseDropdown = false; } } EditorGUI.LabelField(new Rect(20, i * 17 - 1, 80, 16), response.responseText, EditorStyles.miniLabel); } if (isSelected && GUI.Button(new Rect(0, respCount * 17 - 1, 20, 16), "+")) { actions.changed = true; addNodeResponse(dNode); } GUI.EndGroup(); } actions.changed = GUI.changed; GUI.changed = prevChanged || GUI.changed; return(actions); }