Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Not using public fields: Make a object text display the name of other object.

Discussion in 'Scripting' started by dreamtocode77, Feb 26, 2020.

  1. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Hey Guys! Getting back into programming after a hiatus. I am following a tutorial right now on building a simple game. The only deviation I am taking is that I don't want to use "Public fields" unless its necessary (he makes everything public).

    I have a player object that has a script attached to it that just contains a String variable called Red. I also have a panel ui with a child text object. Through the BattleManager script, I would like the panel text to display the name of the player object. Simple, but I am rusty and currently cant seem to get it to work. The tutorial just made it public and just drags everything into the slot in inspector.. don't want to do that.

    Problem is in the BattleManager Script: I get the "Nullreferenceexception: Object reference not set to an instance of an object. I know that means that something is connected but I cannot figure out, but I'm sure its a snake in grass and I'm looking right at it as always.


    Script attached to player object. Just a Name Variable.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class BaseStats : MonoBehaviour
    7. {
    8.     public string Name = "Red";
    9. }
    10.  

    This is attached to the Panel.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class PlayerHUB : MonoBehaviour
    7. {
    8.     Text nameText;
    9.    
    10.     public void SetUP(BaseStats baseStats)
    11.     {
    12.         nameText = GameObject.Find("PlayerHUB").GetComponentInChildren<Text>();
    13.         nameText.text = baseStats.Name;
    14.     }
    15. }
    16.  
    And Finally the BattleManger script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6.  
    7. public class BattleManager : MonoBehaviour
    8. {
    9.     PlayerHUB playerHUB;
    10.     BaseStats baseStats;
    11.  
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.  
    16.         playerHUB.SetUP(baseStats);
    17.     }
    18. }
     
  2. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Btw PlayerHUB is the name of the panel.
     
  3. akadird

    akadird

    Joined:
    Dec 19, 2017
    Posts:
    11
    Cant reach the one of the reference items. reason might be the Game Object u try to find in first script is inactive.
     
  4. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    The object called "PlayerHUB" is the name of the panel. Its as active as panel can be lol.
     
  5. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,122
    In the BattleManager script you need to make the playerHUB and baseStats fields public (or add the SerializeField attribute to them) to expose them in the inspector. After this you can drag and drop references to the components in the inspector. Otherwise playerHUB and baseStats will have the default value of null, resulting in the NullReferenceException.

    Alternatively, if the two components always exist on the same GameObject, you can use GetComponent to automatically find the references in the Start function.

    In this case I would also use RequireComponent to ensure that the two components will always be found on the GameObject.

    Code (CSharp):
    1. [RequireComponent(typeof(PlayerHUB)), RequireComponent(typeof(BaseStats))]
    2. public class BattleManager : MonoBehaviour
    3. {
    4.     PlayerHUB playerHUB;
    5.     BaseStats baseStats;
    6.     // Start is called before the first frame update
    7.     void Start()
    8.     {
    9.         playerHUB = GetComponent<PlayerHUB>();
    10.         baseStats = GetComponent<BaseStats>();
    11.         playerHUB.SetUP(baseStats);
    12.     }
    13. }
     
  6. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Yes; I understand that If I just made the references public that I could just drag in drop in the inspector but I would like to do that with just coding. I tried your alternative and I still get the error.
     
  7. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Apologies for double posting but the problem has to do it with: playerHUB.SetUP(baseStats)

    I legit am stumped. Running through the code multiple times for a hour and half and every reference looks like it should be set.
     
  8. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,122
    Yes the NullReferenceException occurs if playerHUB is null when playerHUB.SetUP(baseStats) is called, because you can't call an instance method if no instance exists.

    I suspect that the issue is that the RequireComponent attribute doesn't retroactively add the required components for any instances that already existed in your scenes, it only works from now on when you add the component to a GameObject. So what you need to do is remove the BattleManager component and add it again for the required components to also get added.

    When you actually see the PlayerHUB and BaseStats components on the GameObject with the BattleManager you know that everything is as intended.


    Btw using Debug.Assert is a good way to make sure that all relevant data is valid at specifics points in your code and to print a descriptive error if not. If you pass "this" as the second argument, you can also just click the error message in the console and the object with invalid data is highlighted. This can often be really useful in figuring out the exact sources of errors.
    Code (CSharp):
    1. void Start()
    2. {
    3.     playerHUB = GetComponent<PlayerHUB>();
    4.     baseStats = GetComponent<BaseStats>();
    5.  
    6.     Debug.Assert(playerHUB != null, "Required component PlayerHUB missing from GameObject!", this);
    7.     Debug.Assert(baseStats != null, "Required component BaseStats missing from GameObject!", this);
    8.  
    9.     playerHUB.SetUP(baseStats);
    10. }
     
    dreamtocode77 likes this.
  9. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Thank You!!!! It Work!!!

    OK!, but I need you to help me understand this better:

    1. When you do that [RequireComponet ......] thing at the top that is the in code of equivalent of the the public field drop in the inspector thing correct?

    2. I have been looking at vids all day and the ones I found on UI, EVERYONE just does the public thing. I am assuming because outside what you have shown me today that there is no other way to make reference to it? I am looking at my code and I don't see why that would not work the way I set up?
     
  10. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,742
    It's actually GetComponent<> that replaces the need to drag the thing into the public field slot. RequireComponent ensures that the component exists on the same GameObject; GetComponent actually finds a reference to that, and then you can store that in the field (public or private). RequireComponent is basically a safety net to ensure you don't accidentally run the script on an object without that component, which would cause NullReferenceExceptions.

    There are many many ways to get a reference to another component, those are just the most common. Public references are generally used to be able to point to components on different GameObjects easily. You could use GetComponentInChildren as well, but this won't distinguish between components of the same type. This is especially important in the case of UI code - you may have 100 Text objects on your panel, but the hit point counter is that one in particular, which can be quite annoying to find in a reliable way without the public field drag-and-drop.
     
    SisusCo and dreamtocode77 like this.
  11. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,122
    The RequireComponent attribute is only used to automatically add the required component to the same GameObject that holds the component with the attribute.

    So this...
    Code (CSharp):
    1. [RequireComponent(typeof(B))]
    2. public class A : MonoBehaviour { }
    ...is basically equivalent to this...
    Code (CSharp):
    1. public class A : MonoBehaviour
    2. {
    3.     // Reset is called when adding the component the first time.
    4.     void Reset()
    5.     {
    6.         if(GetComponent<B>() == null)
    7.         {
    8.             gameObject.AddComponent<B>();
    9.         }
    10.     }
    11. }
    This does not automatically assign a reference to the component into any field, it can just be used to make sure that required component exist on the GameObject so that references can be assigned afterwards e.g. using GetComponent or by dragging in the inspector.

    So actually the inspector dragging method...
    Code (CSharp):
    1. [RequireComponent(typeof(B))]
    2. public class A : MonoBehaviour
    3. {
    4.     // Assign this using the inspector.
    5.     [SerializeField]
    6.     private B referenceToB;
    7. }
    ...can be done programmatically like this...
    Code (CSharp):
    1. [RequireComponent(typeof(B))]
    2. public class A : MonoBehaviour
    3. {
    4.     // This field is hidden in the inspector but its value is still saved.
    5.     [SerializeField, HideInInspector]
    6.     private B referenceToB;
    7.  
    8.     // Reset is called when adding the component the first time.
    9.     void Reset()
    10.     {
    11.         referenceToB = GetComponent<B>();
    12.     }
    13. }
    ...or alternatively the reference can be fetched again every time the game runs (in which case we don't need to serialize it)...
    Code (CSharp):
    1. [RequireComponent(typeof(B))]
    2. public class A : MonoBehaviour
    3. {
    4.     private B referenceToB;
    5.  
    6.     // Start is called before the first frame update
    7.     void Start()
    8.     {
    9.         referenceToB = GetComponent<B>();
    10.     }
    11. }
    However GetComponent is only equivalent to the manual dragging method if the component always exists on the same GameObject. If you drag the reference in the inspector, it is possible to drag a reference from a different GameObject.

    This means that exposing the field in the inspector allows for more flexibility in the Setup phase. GetComponent on the other hand is more reliable because it removes human error (forgetting to assign the reference) from the equation.
     
    dreamtocode77 likes this.
  12. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    Thank you for the knowledge! I was actually thinking about how I would distinguish between two text elements in one panel haha.

    I want to go forward coding using public fields as little as possible, but do you think in the case of UI I should make a exception? I got what I am looking for but a piece of me is still like "Really? Why wouldn't my initial setup of my code work?" I reeeaaaally feel it should have worked hahaha.
     
  13. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46

    I'm looking at this and I am even more confused. Answer me this - What part of my code did not have the reference it needed and based off my code... why not?

    EDIT: just a thought; If i had multiple text elements in one panel I could make them unique by giving them each a tag right?
     
  14. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,122
    Sorry if I'm just adding to your confusion :D I'm actually trying my best to put it simply.

    There are two steps that always need to occur in order to get a reference to a component:
    1. Create the component.
    2. Acquire a reference to the component.
    Both of these can be done either manually in the inspector or using code.

    In your original code for the BattleManager...
    1. You did not create the components using code (RequireComponent, AddComponent, Instantiate...). You could still have done it manually in the inspector though.
    2. You did not find references to the components using code (GetComponent, GetComponentInChildren, FindObjectOfType...). Assigning the references manually using the inspector was also impossible because the fields were not serialized (public or SerializeField).
    If either one of these two steps is omitted, you won't have a reference to a component and will run into the NullReferenceException when you try to reference any fields or methods inside the missing component.

    So if you (1.) use RequireComponent to add the component, but (2.) forget to assign the reference in the inspector: NullReferenceException.

    Or if you (2.) use GetComponent to search for a reference, but (1.) forgot to add the component to the GameObject in the first place: NullReferenceException.

    Yes, if you can't simply use GetComponent, GetComponentInChildren or GetComponentInParent then using tags to differentiate between the multiple GameObjects is a simple solution that works.

    There are also many other ways to accomplish this. E.g. you could add a public field to the component:
    Code (CSharp):
    1. public string id;
    This could also be e.g. an int or enum field.

    Then you could use GetComponentsInChildren to find all components that are nested under the panel and use the id field to figure out which one is the one you're looking for:
    Code (CSharp):
    1. var menusInChildren = GetComponentsInChildren<Menu>();
    2. foreach(var menuInChildren in menusInChildren)
    3. {
    4.     if(menuInChildren.id == "Options")
    5.     {
    6.         menu = menuInChildren;
    7.     }
    8. }
     
  15. dreamtocode77

    dreamtocode77

    Joined:
    Nov 10, 2019
    Posts:
    46
    You did not find references to the components using code (GetComponent, GetComponentInChildren, FindObjectOfType...). Assigning the references manually using the inspector was also impossible because the fields were not serialized (public or SerializeField).

    So this part of the code was not successful? Why!?:

    Code (CSharp):
    1. public void SetUP(BaseStats baseStats)
    2.     {
    3.         nameText = GameObject.Find("PlayerHUB").GetComponentInChildren<Text>();
    4.         nameText.text = baseStats.Name;
    5.     }
    I am sorry for asking so many questions, but I need to understand why I did not do right. I was positive that this was correct and did not even consider the idea it was wrong. I thank 100 times over for educating me on this and I apologize for asking so many questions, but I really will not be using public fields a lot so knowing why connections among code are not working is crucial so I don't keep making the same mistake. Is it possible that u can show me a battle manager script done correctly if you have the time?
     
  16. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,122
    The PlayerHUB class wasn't the issue. It was the BattleManager class that was throwing the NullReferenceException, and its Start method looked like this:
    Code (CSharp):
    1.     // Start is called before the first frame update
    2.     void Start()
    3.     {
    4.         playerHUB.SetUP(baseStats);
    5.     }
    Here there is no GetComponent call, which means that the playerHUB variable will always have a value of null.
    Even if the PlayerHUB component exists on the GameObject it is not enough to make the code work, you also have to assign it to the playerHUB field.

    I thought I already did... check out my first post :D