Search code examples
c#unity-game-enginenullreferenceexceptioncomposite

Unity-NullReferenceException When Using Composite Design Pattern


So I'm trying to make a simple dialogue tree in the form of a composite design pattern, where each member of the hierarchy has basically the same customizable properties, in this case, a question and two options. In Unity, I'm trying to assign the question to a textbox and each option into a button. When you click a button, you go to another level of the tree, or a leaf. For whatever reason, I'm getting the null reference exception error and its pointing to this line. I'll try my best to answer any further questions.

        Textbox.GetComponent<Text>().text = question;

It might be something simple I can fix, but I'm not really sure where to look right now. If anyone could help me in the right direction, I would very much appreciate it. Thank you. Here is my script for the levels in the composite pattern.

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


public class Composite : MonoBehaviour {


    private void Start()
    {

        Level Level1 = new Level("Hello", "Hi", "Shut Up");
        Level leaf1 = new Level("Don't be Rude");

        Level Level2 = new Level("What you Doing?", "Not Much", "None of your Business");
        Level leaf2 = new Level("Well Excuuuuse Me");

        Level Level3 = new Level("Can I do that too?", "Sure", "Go Away");
        Level leaf3 = new Level("Fine. Be a Jerk");

        Level Level4 = new Level("This is boring, can we do something else?", "Why not?", "You're boring");
        Level leaf4 = new Level("I'll go be boring somewhere else");

        Level Level5 = new Level("You want ice cream?", "Sounds Good", "I'm allergic");
        Level leaf5 = new Level("ok.......");
        Level leaf = new Level("I Want Chocolate");



        Level1.add(Level1);
        Level1.add(leaf1);

        Level2.add(Level3);
        Level2.add(leaf2);

        Level3.add(Level4);
        Level3.add(leaf3);

        Level4.add(Level5);
        Level4.add(leaf4);

        Level5.add(leaf5);
        Level5.add(leaf);

    }


    public class Level
    {
        public static Text Textbox;
        public static Text Button1;
        public static Text Button2;

        public string OptionA;
        public string OptionB;
        public string Question;

        public string Leaf;

        private List<Level> levels;

        public Level(string question, string optionA, string optionB)
        {
            this.Question = question;
            this.OptionA = optionA;
            this.OptionB = optionB;


            Textbox.GetComponent<Text>().text = Question;
            Button1.GetComponent<Text>().text = OptionA;
            Button2.GetComponent<Text>().text = OptionB;


            levels = new List<Level>();

        }


        public Level(string leaf)
        {
            this.Leaf = leaf;
            Textbox.text = leaf;
        }

        public void add(Level lvl)
        {
            levels.Add(lvl);
        }

        public List<Level> getLevels()
        {
            return levels;
        }

    }

}

Initializer Script

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

public class Initializer : MonoBehaviour {

    public Text Textbox;
    public Text Button1;
    public Text Button2;

    void Awake()
    {
        Composite.Level.Textbox = this.Textbox;
        Composite.Level.Button1 = this.Button1;
        Composite.Level.Button2 = this.Button2;
    }
}

Solution

  • I think it looks pretty straight forward.

    You are creating new instances of the level Class, but never set the Textand Button property.

    This part is also redundant, because you are telling it to find a reference of itself! ("Textbox" is of type Text)

    Textbox.GetComponent<Text>()....
    

    To fix your nullreference, you could make the fields static.

    public static Text Textbox;
    public static Text Button1;
    public static Text Button2;
    

    And assign them from somewhere else. Maybe an initializer script with public fields? You could do a lot of different things. Here is an example where you add this script to an empty gameobject, then drag the textbox/ buttons into the inspector slots:

    public class Initializer : MonoBehaviour
    {
       public Text Textbox;
       public Button Button1;
       public Button Button2;
    
     // Awake is called before Start, so Start() in your Composite script will be called 
     // after these fields have been initialized
       void Awake() 
       {
          Level.Textbox = this.Textbox;
          Level.Button1 = this.Button1;
          Level.Button2 = this.Button2;
       }
    }
    

    finally Remove these commented lines, they do exactly the same.

        // Textbox.GetComponent<Text>().text = question;
        Textbox.text = question;
        Button1.GetComponent<Text>().text = optionA;
        // Button1.text = optionA;
        Button2.GetComponent<Text>().text = optionB;
        // Button2.text = optionA;