NullReference Error

The error points to this line within the PlayerConversant.cs:
else
{ return currentConversant.GetName();
}

AIConversant.cs

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

namespace RPG.Dialogue
{
public class AIConversant : MonoBehaviour
{    
[SerializeField] Dialogue dialogue = null;
[SerializeField] Transform playerController;
[SerializeField] string conversantName;

public string GetName()
{

      return conversantName;
    }
private void Update() 
{
   float distance = Vector3.Distance(transform.position, playerController.position);
    if (distance <= 7f)
      {           
        if(Input.GetKeyDown(KeyCode.E))
        {        
           playerController.GetComponent<PlayerConversant>().StartDialogue(dialogue);
        }
      }
  
    }
    
  }
}


DialogueUI.cs

�using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using RPG.Dialogue;

using TMPro;

using UnityEngine.UI;

namespace RPG.UI

{

    public class DialogueUI : MonoBehaviour

    {

        PlayerConversant playerConversant;

        [SerializeField] TextMeshProUGUI AIText;

        [SerializeField] Button nextButton;

        [SerializeField] GameObject AIResponse;

        [SerializeField] Transform choiceRoot;

        [SerializeField] GameObject choicePrefab;

        [SerializeField] Button quitButton;

        [SerializeField] TextMeshProUGUI conversantName;

        // Start is called before the first frame update

        void Start()

        {

            playerConversant = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerConversant>();

            playerConversant.onConversationUpdated += UpdateUI;

            nextButton.onClick.AddListener(() => playerConversant.Next());

            quitButton.onClick.AddListener(() => playerConversant.Quit());

            UpdateUI();

        }

       

        void UpdateUI()

        {

            gameObject.SetActive(playerConversant.IsActive());

            if (!playerConversant.IsActive())

            {

                return;

            }

            conversantName.text = playerConversant.GetCurrentConversantName();

            AIResponse.SetActive(true);

            choiceRoot.gameObject.SetActive(playerConversant.IsChoosing());

            if (playerConversant.IsChoosing())

            {

                BuildChoiceList();

                nextButton.gameObject.SetActive(!playerConversant.HasNext());

            }

            else

            {

                AIText.text = playerConversant.GetText();

                nextButton.gameObject.SetActive(playerConversant.HasNext());

            }

        }

        private void BuildChoiceList()

        {

            choiceRoot.DetachChildren();

            foreach (DialogueNode choice in playerConversant.GetChoices())

            {

                GameObject choiceInstance = Instantiate(choicePrefab, choiceRoot);

                var textComp = choiceInstance.GetComponentInChildren<TextMeshProUGUI>();

                textComp.text = choice.GetText();

                Button button = choiceInstance.GetComponentInChildren<Button>();

                button.onClick.AddListener(() =>

                {

                    playerConversant.SelectChoice(choice);

                });

            }

        }

    }

}

PlayerConversant.cs

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

namespace RPG.Dialogue
{
    public class PlayerConversant : MonoBehaviour
    {
        [SerializeField] string playerName;
        AIConversant currentConversant;
        Dialogue currentDialogue;
        DialogueNode currentNode = null;
        bool isChoosing = false;

        public event Action onConversationUpdated;

        
        public void StartDialogue(Dialogue newDialogue)
        {
            currentDialogue = newDialogue;
            currentNode = currentDialogue.GetRootNode();
            onConversationUpdated();
        }
        public void Quit()
        {
            currentDialogue = null;
            currentNode = null;
            isChoosing = false;
            onConversationUpdated();

        }

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

        public bool IsChoosing()
        {
            return isChoosing;
        }

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

            return currentNode.GetText();
        }

        public IEnumerable<DialogueNode> GetChoices()
        {
            return currentDialogue.GetPlayerChilren(currentNode);
        }

        public void SelectChoice(DialogueNode chosenNode)
        {
            currentNode = chosenNode;
            isChoosing = false;
            Next();
        }

         public string GetCurrentConversantName()
        {
            if (isChoosing)
            {
                return playerName;
            }
            else
            {
                return currentConversant.GetName();
            }
        }

        public void Next()
        {
            int numPlayerResponses = currentDialogue.GetPlayerChilren(currentNode).Count();
            if (numPlayerResponses > 0)
            {
                isChoosing = true;
                onConversationUpdated();
                return;
            }

            DialogueNode[] children = currentDialogue.GetAIChildren(currentNode).ToArray();
            int randomIndex = UnityEngine.Random.Range(0, children.Count());
            currentNode = children[randomIndex];
            onConversationUpdated();
        }

        public bool HasNext()
        {
            return currentDialogue.GetAllChildren(currentNode).Count() > 0;
        }

        
    }
}

Ive assigned the names to the NPC and the Player and assigned the currentspeaker to the dialogue field.

You’re not assigning the currentConversant.

StartDialogue needs to have a reference to the conversant in the header and then set it in the method

        public void StartDialogue(AIConversant newConversant, Dialogue newDialogue)
        {
            currentConversant = newConversant;
            //rest of the method

This also means that in AIConversant in the HandleRaycast method, you’ll need to pass the conversant to the StartDialogue method

callingController.GetComponent<PlayerConversant>().StartDialogue(this, dialogue);

thank you that solves that issue, I encountered a different issue (not related to this lecture) when I finish a dialogue (clicking the last dialogue node within a dialogue while playing the game).

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

namespace RPG.Dialogue
{
    public class PlayerConversant : MonoBehaviour
    {
        [SerializeField] string playerName;

        Dialogue currentDialogue;
        DialogueNode currentNode = null;
        AIConversant currentConversant = null;
        bool isChoosing = false;

        public event Action onConversationUpdated;

        public void StartDialogue(AIConversant newConversant, Dialogue newDialogue)
        {
            currentConversant = newConversant;
            currentDialogue = newDialogue;
            currentNode = currentDialogue.GetRootNode();
            
            onConversationUpdated();
        }

        public void Quit()
        {
            currentDialogue = null;
            
            currentNode = null;
            isChoosing = false;
            currentConversant = null;
            onConversationUpdated();
        }

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

        public bool IsChoosing()
        {
            return isChoosing;
        }

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

            return currentNode.GetText();
        }

        public string GetCurrentConversantName()
        {
            if (isChoosing)
            {
                return playerName;
            }
            else
            {
                return currentConversant.GetName();
            }
        }

        public IEnumerable<DialogueNode> GetChoices()
        {
            return currentDialogue.GetPlayerChilren(currentNode);
        }

        public void SelectChoice(DialogueNode chosenNode)
        {
            currentNode = chosenNode;
            
            isChoosing = false;
            Next();
        }
    
        public void Next()
        {
            int numPlayerResponses = currentDialogue.GetPlayerChilren(currentNode).Count();
            if (numPlayerResponses > 0)
            {
                isChoosing = true;
                
                onConversationUpdated();
                return;
            }

            DialogueNode[] children = currentDialogue.GetAIChildren(currentNode).ToArray();
            int randomIndex = UnityEngine.Random.Range(0, children.Count());
            
            currentNode = children[randomIndex];
            
            onConversationUpdated();
        }

        public bool HasNext()
        {
            return currentDialogue.GetAllChildren(currentNode).Count() > 0;
        }

        
    }
}

Is there an error with the array or something?

The error is here. If there are no children, the array will have 0 elements and the Random.Range() will return 0. This is the first element, but there is no first element.

You need to check if anything returned from GetAIChildren

thanks!

If there are children on the current node, but they are all filtered away, then HasNext() will be true, but the children array will be empty, tricking the system.
A simple check manages this

if(children.Length>0)
{   
     currentNode = children[randomIndex];
}
1 Like

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

Privacy & Terms