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

Question How to call the same method on all objects in the game scene ?

Discussion in 'Scripting' started by marplast, Jun 23, 2020.

  1. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    I have a script attached to three text boxes which need to update with the total number of resources held.

    If I disable all but one and use the following code it all works fine:

    FindObjectOfType<WindowGameResources>().UpdateResourceTextObject();

    When there are more than one it doesn't work. Any idea how to make this work with multiple objects ?

    Thanks.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    It's hard to tell without seeing your code and understanding your object hierarchy.

    Also a better description of your problem than "it doesn't work" would be helpful.
     
  3. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Hi Praetor,

    I am attaching the relevant two scripts. GameResources is attached to an empty game object of the same name. The other script is attached to empty game objects within the canvas, each having a text child. The code doesn't work in the sense that the fields are not being updated. Thanks.

    public class GameResources : MonoBehaviour
    {

    public enum ResourceType
    {
    apple,
    orange,
    lemon,
    }


    static Dictionary<ResourceType, int> ResourceAmountDictionary;


    private void Awake()
    {
    Init();
    }

    public static void Init()
    {
    ResourceAmountDictionary = new Dictionary<ResourceType, int>();
    foreach (ResourceType resourceType in Enum.GetValues(typeof(ResourceType)))
    {
    ResourceAmountDictionary[resourceType] = 0;
    }

    }

    public static void AddResourceAmount(ResourceType resourceType, int amount)
    {
    ResourceAmountDictionary[resourceType] += amount;

    FindObjectOfType<WindowGameResources>().UpdateResourceTextObject();
    }

    public static int GetResourceAmount(ResourceType resourceType)
    {
    return ResourceAmountDictionary[resourceType];
    }

    public class WindowGameResources : MonoBehaviour
    {
    [SerializeField] GameResources.ResourceType resourceType;
    Text resourceText;

    private void Start()
    {
    resourceText = transform.Find("Text").GetComponent<Text>();
    UpdateResourceTextObject();
    }

    public void UpdateResourceTextObject()
    {
    resourceText.text =
    GameResources.GetResourceAmount(resourceType).ToString();
    }
    }
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
  5. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Code (CSharp):
    1. public class WindowGameResources : MonoBehaviour
    2. {
    3.     [SerializeField] GameResources.ResourceType resourceType;
    4.     Text resourceText;
    5.  
    6.     private void Start()
    7.     {
    8.         resourceText = transform.Find("Text").GetComponent<Text>();
    9.         UpdateResourceTextObject();
    10.     }
    11.  
    12.     public void UpdateResourceTextObject()
    13.     {
    14.             resourceText.text =
    15.                 GameResources.GetResourceAmount(resourceType).ToString();
    16.     }
    17. }
    Code (CSharp):
    1. public class GameResources : MonoBehaviour
    2. {
    3.  
    4.     public enum ResourceType
    5.     {
    6.         apple,
    7.         orange,
    8.         lemon,
    9.     }
    10.  
    11.  
    12.     static Dictionary<ResourceType, int> ResourceAmountDictionary;
    13.  
    14.  
    15.     private void Awake()
    16.     {
    17.             Init();
    18.     }
    19.  
    20.     public static void Init()
    21.     {
    22.         ResourceAmountDictionary = new Dictionary<ResourceType, int>();
    23.         foreach (ResourceType resourceType in Enum.GetValues(typeof(ResourceType)))
    24.         {
    25.             ResourceAmountDictionary[resourceType] = 0;
    26.         }
    27.  
    28.     }
    29.  
    30.     public static void AddResourceAmount(ResourceType resourceType, int amount)
    31.     {
    32.         ResourceAmountDictionary[resourceType] += amount;
    33.        
    34.         FindObjectOfType<WindowGameResources>().UpdateResourceTextObject();
    35.     }
    36.  
    37.     public static int GetResourceAmount(ResourceType resourceType)
    38.     {
    39.         return ResourceAmountDictionary[resourceType];
    40.     }
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
  7. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    I am aware but it does not allow me to call the method if I use the plural .
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    The more appropriate approach would be to invert that dependency. Why does WindowGameResources need to know about GameResources AND GameResources know about WindowGameResources. This is called a "coupled dependency". One can't exist with out the other.

    It'd be like if I was reading a book... my reading the book depends on the book existing. But it'd be really weird if the book existing depended on my reading it.
     
    marplast likes this.
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Loop the array that is returned?
     
    marplast likes this.
  10. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    If this was my game, WindowGameResources would be listening for C# events from GameResources and updating whenever the resource value it was interested in changed. I understand that might be a lot for a beginner though.
     
    lordofduct likes this.
  11. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Oh wow, Lord, thank you very much. I am quite new to scripting, but this worked, as in the looping through the array. I didn't realise it was an array.

    I am particularly interested in your other comment about the dependancy. Please, can you elaborate ? How to create such a structure ?
     
  12. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Hi Praetor, I actually had an event previously but it caused me problems when I tried to reload the scene and thought I would try to achieve it with methods. But yes, I agree, that with events this particular problem did not exist.
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Events is one way like @PraetorBlue suggested. And honestly is the first thing I'd jump to in this situation if rapidly trying to get something set up.

    Something like (note I personally wouldn't use singleton either, but this is the simplest thing to do here without getting to far off base from resolving your issue):

    Code (csharp):
    1.  
    2. public class GameResources : MonoBehaviour
    3. {
    4.  
    5.     public enum ResourceType
    6.     {
    7.         apple,
    8.         orange,
    9.         lemon,
    10.     }
    11.  
    12.     //simple singleton
    13.     private static GameResources _instance;
    14.     public static GameResources Instance { get { return _instance; } }
    15.  
    16.     public event System.EventHandler ResourceAmountAdded;
    17.  
    18.     Dictionary<ResourceType, int> ResourceAmountDictionary;
    19.  
    20.  
    21.     private void Awake()
    22.     {
    23.         //simple singleton initialization
    24.         if(_instance != null)
    25.         {
    26.             Destroy(this);
    27.             return;
    28.         }
    29.      
    30.         _instance = this;
    31.      
    32.         //our proper init
    33.         ResourceAmountDictionary = new Dictionary<ResourceType, int>();
    34.         foreach (ResourceType resourceType in Enum.GetValues(typeof(ResourceType)))
    35.         {
    36.             ResourceAmountDictionary[resourceType] = 0;
    37.         }
    38.     }
    39.  
    40.     public void AddResourceAmount(ResourceType resourceType, int amount)
    41.     {
    42.         ResourceAmountDictionary[resourceType] += amount;
    43.      
    44.         ResourceAmountAdded?.Invoke(this, System.EventArgs.Empty);
    45.     }
    46.  
    47.     public int GetResourceAmount(ResourceType resourceType)
    48.     {
    49.         return ResourceAmountDictionary[resourceType];
    50.     }
    51.  
    52.     //...
    53. }
    54.  
    Code (csharp):
    1.  
    2. public class WindowGameResources : MonoBehaviour
    3. {
    4.     [SerializeField]
    5.     GameResources.ResourceType resourceType;
    6.     [SerializeField]
    7.     Text resourceText; //assign this through the editor or some other manner, don't use find in awake
    8.  
    9.     private void Start()
    10.     {
    11.         UpdateResourceTextObject();
    12.      
    13.         GameResources.Instance.ResourceAmountAdded += OnResourceAmountAdded;
    14.     }
    15.  
    16.     private void OnDestroy()
    17.     {
    18.         GameResources.Instance.ResourceAmountAdded -= OnResourceAmountAdded;
    19.     }
    20.  
    21.     private void OnResourceAmountAdded(object sender, System.EventArgs e)
    22.     {
    23.         UpdateResourceTextObject();
    24.     }
    25.  
    26.     public void UpdateResourceTextObject()
    27.     {
    28.             resourceText.text =
    29.                 GameResources.GetResourceAmount(resourceType).ToString();
    30.     }
    31. }
    32.  
     
    PraetorBlue likes this.
  14. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Lord, I don't want to say a big word but you might have hit two birds with one stone here. I was previously using an Event similar to what you have in your code except for the singleton bit and the OnDestroy method. On reloading the scene, it was giving me a weird message that it cannot find the text boxes to update the resource values. I suspect your additions will fix that. Well, many thanks, you have been very helpful. I banged my head for many hours over this (I know).
     
    PraetorBlue likes this.
  15. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    637
    If you are really interrested, heres a full thread about code architecture in unity. ^^
    https://forum.unity.com/threads/how-to-make-the-programming-architecture-of-my-game.916661/
     
  16. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Hi Lord,

    I implemented your event solution today with a few tweaks as I was getting some compile issues. I experimented a bit with it. Firstly, I commented out the singleton bit and the game works fine. Secondly, I commented out the OnDestory() unsubscribe bit and the game gives me an error 'missing reference exception, object of type text has been destroyed.

    So, I guess I just wanted your take on why you think the singleton bit was important as the game still works without it? Also, why is the event tied to a specific text object ? Shouldn't the event just trigger the appropriate method to update ANY text box that is referenced in the code ? Many thanks.

    Code (CSharp):
    1. public class WindowGameResources : MonoBehaviour
    2. {
    3.     [SerializeField] GameResources.ResourceType resourceType = default;
    4.     [SerializeField] Text resourceText = default;
    5.  
    6.     private void Start()
    7.     {
    8.         GameResources.ResourceAmountAdded += OnResourceAmountAdded;
    9.         UpdateResourceTextObject();
    10.     }
    11.  
    12.     private void OnDestroy()
    13.     {
    14.        // GameResources.ResourceAmountAdded -= OnResourceAmountAdded;
    15.     }
    16.  
    17.  
    18.     private void OnResourceAmountAdded(object sender, System.EventArgs e)
    19.     {
    20.         UpdateResourceTextObject();
    21.     }
    22.  
    23.     public void UpdateResourceTextObject()
    24.     {
    25.             resourceText.text =
    26.                 GameResources.GetResourceAmount(resourceType).ToString();
    27.     }
    28. }
    Code (CSharp):
    1. public class GameResources : MonoBehaviour
    2. {
    3.  
    4.     public enum ResourceType
    5.     {
    6.         tomato,
    7.         orange,
    8.         lemon,
    9.     }
    10.  
    11.     // private static GameResources _instance;
    12.     // public static GameResources Instance { get { return _instance; } }
    13.  
    14.     public static event System.EventHandler ResourceAmountAdded;
    15.  
    16.     static Dictionary<ResourceType, int> ResourceAmountDictionary;
    17.  
    18.  
    19.     private void Awake()
    20.     {
    21.        /* if (_instance != null)
    22.         {
    23.             Destroy(this);
    24.             return;
    25.         }
    26.  
    27.         _instance = this;*/
    28.    
    29.         ResourceAmountDictionary = new Dictionary<ResourceType, int>();
    30.         foreach (ResourceType resourceType in Enum.GetValues(typeof(ResourceType)))
    31.         {
    32.             ResourceAmountDictionary[resourceType] = 0;
    33.         }
    34.  
    35.     }
    36.  
    37.     public static void AddResourceAmount(ResourceType resourceType, int amount)
    38.     {
    39.         ResourceAmountDictionary[resourceType] += amount;
    40.         ResourceAmountAdded?.Invoke(null, EventArgs.Empty);
    41.     }
    42.  
    43.     public static int GetResourceAmount(ResourceType resourceType)
    44.     {
    45.         return ResourceAmountDictionary[resourceType];
    46.     }
    47. }
     
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Because static states are fragile design. It makes any refactoring difficult, there are some gotchas in terms of how the Unity editor behaves, and it's just good habit to avoid static state because in more complicated setting than this its fragility can get more rough.

    This isn't to say Singleton is much better... and is why I said I wouldn't necessarily use a singleton either. But completely refactoring your code would have wildly changed everything to an extent that you wouldn't have recognized it. And well singleton is a common enough pattern here in the unity community that you will end up becoming famliar with it in due time anyways. And Singleton at least reintroduces the concept of "object identity" that static lacks.

    It's not?

    The event is part of GameResources. It allows GameResources to signal that something has changed.

    Each of your WindowGameResources can then listen for that event. And it decides to update the textbox, that textbox being the one it has referenced.

    Beyond that if there's some functionality/behaviour/intent I'm missing... that's just because I don't know what it is you're attempting to accomplish. Your code is so far out of context and has such generic names I honestly don't know what any of it is actually supposed to be doing in the grand scheme of things... I'm just applying general techniques to how you accomplish signaling that the state of one object has changed without creating a tightly coupled dependency.

    Like I said before though, this isn't necessarily the only way to do it... nor is it the best way since I don't know what you exactly want to accomplish. But it's a straight forward and simple way and is inline with what PraetorBlue was talking about as well.
     
  18. marplast

    marplast

    Joined:
    Jun 21, 2020
    Posts:
    15
    Thanks for your help. I am a beginner so some of this stuff is flying over my head.

    Re: the second point, I have a timer and when the time is up the user gets prompted to 'retry' which reloads the scene using the scenemanager. I am guessing all objects get destroyed and recreated - at that stage I am getting an error (the object of type text has been destroyed) at

    resourceText.text = GameResources.GetResourceAmount(resourceType).ToString();

    Not an issue as your code below solves the problem, just got curious why it was happening since the event should be independent of the 'listeners':

    GameResources.ResourceAmountAdded -= OnResourceAmountAdded;