Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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);
        }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 5
0
        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;
            }
        }
Ejemplo n.º 6
0
        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);
        }