Issue with not getting next dialogue node when option is selected

I am having this bug/issue where when I click on an option button, the dialogue is not being progressed to the children of the node even though the player node has child nodes.

I assume the NextDialogue() in PlayerConversant is working because when I click on the next button for NPC dialogue, it progresses. I tried a Debug log in the SelectOption() function to see if that was being called when I clicked on the button and it does not seem to be printing but I am not sure why.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace RPG.Dialogue
{
    public class PlayerConversant : MonoBehaviour
    {
        [SerializeField] Dialogue testDialogue;
        Dialogue currentDialogue;
        DialogueNode currentNode = null;
        bool isSelecting = false;
        public event Action onDialogueUpdated;
       
        IEnumerator Start() 
        {
            yield return new WaitForSeconds(2f);
            StartDialogue(testDialogue);
        }

        public void StartDialogue(Dialogue newDialogue)
        {
            currentDialogue = newDialogue;
            currentNode = currentDialogue.GetRootNode();
            onDialogueUpdated();
        }

        public void QuitDialogue()
        {
            currentDialogue = null;
            currentNode = null;
            isSelecting = false;
            onDialogueUpdated();
        }

        public bool IsActive()
        {
            return currentDialogue != null;
        }

        public bool IsSelectingOption()
        {
            return isSelecting;
        }

        public string GetText()
        {
            if (currentNode == null) 
            {
                return "";
            }
           
            return currentNode.GetText();
        }

        public IEnumerable<DialogueNode> GetOptions()
        {
            return currentDialogue.GetPlayerChildren(currentNode);
        }

        public void SelectOption(DialogueNode chosenNode)
        {
            Debug.Log("Option selected");
            currentNode = chosenNode;
            isSelecting = false;
            NextDialogue();
        }

        public void NextDialogue()
        {
            // If the dialogue is by player and there is more than one response, enter selection mode in dialogue
            int numOfPlayerOptions = currentDialogue.GetPlayerChildren(currentNode).Count();
            if (numOfPlayerOptions > 0)
            {
                isSelecting = true;
                onDialogueUpdated();
                return;
            }

            DialogueNode[] childNodes = currentDialogue.GetNPCChildren(currentNode).ToArray();
            int randomIndex = UnityEngine.Random.Range(0, childNodes.Count());
            currentNode = childNodes[randomIndex];
            onDialogueUpdated();
        }

        public bool HasNext()
        {
            // If the current node has children, return true as it can progress to next dialogue, otherwise return false
            return currentDialogue.GetAllChildren(currentNode).Count() > 0;
        }
    }
}


        private void BuildOptionsList()
        {
            foreach (Transform item in optionRoot)
            {
                Destroy(item.gameObject);
            }
            foreach (DialogueNode option in playerConversant.GetOptions())
            {
                GameObject optionButtonInstance = Instantiate(optionButtonPrefab, optionRoot);
                var buttonText = optionButtonInstance.GetComponentInChildren<TextMeshProUGUI>();
                buttonText.text = option.GetText();
                Button button = optionButtonInstance.GetComponentInChildren<Button>();
                button.onClick.AddListener(() => playerConversant.SelectOption(option)); // Function is only called when button is clicked
            }
        }

Ive attached my PlayerConversant.cs and the BuildOptionList() from DialogueUI.cs as that is where I attached the button listener (SelectionOption()). Any help is much appreciated!

Some clarification: Does the dialogue display the two choices (This is to text the random, and Oh great, another text, is it) and fail to display one of the four test options when one of the player options is clicked?
Or does the dialogue display no choices after the first Next is clicked?

It is displaying the 2 choices with the correct text from the player nodes but when I click on one of them, it does not progress the dialogue even though the nodes should have children. The next button on the first NPC node works fine though which is why I don’t think the problem lies in the NextDialogue() function

Everything looks right in NextDialogue(), paste in your complete DialogueUI.cs and we can check execution flow.

using System;
using UnityEngine;
using TMPro;
using RPG.Dialogue;
using UnityEngine.UI;

namespace RPG.UI
{
    public class DialogueUI : MonoBehaviour
    {
        [SerializeField] TextMeshProUGUI conversantName;
        [SerializeField] TextMeshProUGUI dialogueText;
        [SerializeField] Button nextButton;
        [SerializeField] GameObject npcContent;
        [SerializeField] Transform optionRoot;
        [SerializeField] GameObject optionButtonPrefab;
        [SerializeField] Button quitButton;
        PlayerConversant playerConversant;


        // Start is called before the first frame update
        void Start()
        {
            playerConversant = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerConversant>();
            playerConversant.onDialogueUpdated += UpdateUI;
            nextButton.onClick.AddListener(Next);
            quitButton.onClick.AddListener(Quit);

            UpdateUI();          
        }

        void Next()
        {
            playerConversant.NextDialogue();
        }

        void Quit()
        {
            playerConversant.QuitDialogue();
        }

        void UpdateUI()
        {
            gameObject.SetActive(playerConversant.IsActive());
            conversantName.text = playerConversant.GetCurrentConversantName();
            if (!playerConversant.IsActive()) return;

            npcContent.SetActive(!playerConversant.IsSelectingOption());
            optionRoot.gameObject.SetActive(playerConversant.IsSelectingOption());
            if (playerConversant.IsSelectingOption())
            {
                BuildOptionsList();
            }
            else
            {
                dialogueText.text = playerConversant.GetText();
                nextButton.gameObject.SetActive(playerConversant.HasNext());
            }
        }

        private void BuildOptionsList()
        {
            foreach (Transform item in optionRoot)
            {
                Destroy(item.gameObject);
            }

            foreach (DialogueNode option in playerConversant.GetOptions())
            {
                GameObject optionButtonInstance = Instantiate(optionButtonPrefab, optionRoot);
                var buttonText = optionButtonInstance.GetComponentInChildren<TextMeshProUGUI>();
                buttonText.text = option.GetText();
                Button button = optionButtonInstance.GetComponentInChildren<Button>();
                button.onClick.AddListener(() => playerConversant.SelectOption(option)); // Function is only called when button is clicked
            }
        }
    }
}

This is my DialogueUI.cs. One of the other bugs I have noticed is that it will display the player node text as a text in one of the option buttons even if that particular player node is not a child. I might be missing something completely but I cannot see why that is happening.

For example, it will display all 3 player nodes in the options after the root node even though the other 2 player nodes are not childed to them.

That may have been needed to be mentioned earlier…
Let’s take a look at your Dialogue.cs as well… I’m starting to think the issue may be in the retrieval of the child nodes…

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace RPG.Dialogue
{
    [CreateAssetMenu(fileName = "New Dialogue", menuName = "Dialogue", order = 0)]
    public class Dialogue : ScriptableObject, ISerializationCallbackReceiver
    {
        // STATE
        [SerializeField] List<DialogueNode> nodes = new List<DialogueNode>();
        [SerializeField] Vector2 newNodeOffset = new Vector2(250, 0);

        // DATA CONTAINER
        Dictionary<string, DialogueNode> nodeLookup = new Dictionary<string, DialogueNode>();

        // Called when values of SO are changed or when it is loaded
        private void OnValidate() 
        {
            nodeLookup.Clear();
            foreach (DialogueNode node in GetAllNodes())
            {          
                if (node != null)
                {
                    nodeLookup[node.name] = node;
                }
            }
        }

        // Return IEnumerable because it allows the changing of the nodes type from List to another iterable type
        public IEnumerable<DialogueNode> GetAllNodes()
        {
            return nodes;
        }

        public DialogueNode GetRootNode()
        {
            return nodes[0];
        }

        public IEnumerable<DialogueNode> GetAllChildren(DialogueNode parentNode)
        {
            foreach (string childID in parentNode.GetChildren())
            {
                if (nodeLookup.ContainsKey(childID)) // If Dictionary contains this key return it
                {
                    yield return nodeLookup[childID]; // Repeats method and returns the next child to the node 
                }  
            }
        }
  
        public IEnumerable<DialogueNode> GetPlayerChildren(DialogueNode currentNode)
        {
            foreach (DialogueNode node in GetAllChildren(currentNode))
            {
                if (node.GetSpeaker() == Speaker.Player)
                {
                    yield return node;
                }
            }
        }

        public IEnumerable<DialogueNode> GetNPCChildren(DialogueNode currentNode)
        {
            foreach (DialogueNode node in GetAllChildren(currentNode))
            {
                if (node.GetSpeaker() != Speaker.Player)
                {
                    yield return node;
                }
            }
        }

#if UNITY_EDITOR
        public void CreateNode(DialogueNode parentNode)
        {
            DialogueNode newNode = MakeNode(parentNode);
            Undo.RegisterCreatedObjectUndo(newNode, "Created Dialogue Node");
            Undo.RecordObject(this, "Added Dialogue Node");
            AddNode(newNode);
        }

        public void DeleteNode(DialogueNode nodeToDelete)
        {
            Undo.RecordObject(this, "Deleted Dialogue Node");
            nodes.Remove(nodeToDelete);
            OnValidate();
            CleanOrphanNodes(nodeToDelete);
            Undo.DestroyObjectImmediate(nodeToDelete); // Execute after CleanOrphanNodes() so there is no  null reference (name of node that has been deleted)
        }
        private void AddNode(DialogueNode newNode)
        {
            nodes.Add(newNode);
            OnValidate();
        }

        private DialogueNode MakeNode(DialogueNode parentNode)
        {
            DialogueNode newNode = CreateInstance<DialogueNode>();
            newNode.name = Guid.NewGuid().ToString();
            if (parentNode != null)
            {
                parentNode.AddChild(newNode.name);
                newNode.SetPosition(parentNode.GetRect().position + newNodeOffset);
            }

            return newNode;
        }
        private void CleanOrphanNodes(DialogueNode nodeToDelete)
        {
            foreach (DialogueNode node in GetAllNodes())
            {
                node.RemoveChild(nodeToDelete.name);
            }
        }
#endif

        public void OnBeforeSerialize()
        {
#if UNITY_EDITOR
            // If Dialogue has no root node, create a root node then serialize it
            if (nodes.Count == 0)
            {
                DialogueNode newNode = MakeNode(null);
                AddNode(newNode);
            }

            if (AssetDatabase.GetAssetPath(this) != "")
            {
                foreach (DialogueNode node in GetAllNodes())
                {
                    if (AssetDatabase.GetAssetPath(node) == "")
                    {
                        AssetDatabase.AddObjectToAsset(node, this);
                    }
                }
            }
#endif
        }

        public void OnAfterDeserialize()
        {
        }
    }
}

I managed to find the problem after a bit, not sure how I missed it haha. I’ll post Dialogue.cs here anyway issue was when I was getting player and npc node children I was not iterating through the correct list, I was iterating through GetAllNodes() instead of GetAllChildren(currentNode). Ive changed it and now it seems to work…

public IEnumerable<DialogueNode> GetPlayerChildren(DialogueNode currentNode)
        {
            foreach (DialogueNode node in GetAllChildren(currentNode))
            {
                if (node.GetSpeaker() == Speaker.Player)
                {
                    yield return node;
                }
            }
        }

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms