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. Dismiss Notice

Question How to access different variables depending on content of a string?

Discussion in 'Scripting' started by Boozebeard, Jul 10, 2023.

  1. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    I want to write a method that reads and writes data to different variables depending on the tag my game object has. I had it set up with a bunch of if statements, which works but is obviously not very elegant. What are some better ways to do this? I've tried doing it with reflections and I've had success reading values this way but I can't seem to get writing to them to work.

    This works:
    Code (CSharp):
    1. public GameObject GetStickerGO(string stickerTag, int arrayPos)
    2.     {
    3.         GameObject[] stickerArray = (GameObject[])this.GetType().GetField(stickerTag + "Stickers").GetValue(this);
    4.  
    5.         return stickerArray[arrayPos];
    6.     }
    This doesn't work:
    Code (CSharp):
    1.     public void SetCurrentPortraitData(string stickerTag, GameObject sticker)
    2.     {
    3.         this.GetType().GetField(stickerTag + "Sticker").SetValue(this, sticker);
    4.         Debug.Log(BaseSticker.name);
    5.     }
     
  2. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    If I may, I'd like to suggest to take a step back and address this from the data storage side. How about a data structure that allows you to store and retrieve data based on strings, like a Dictionary. Is there any reason these have to be fields of the object?
     
    Bunny83 and dlorre like this.
  3. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    I don't think a dictionary works for what I need. I am looking for other classes and scripts to change their behavior based on the tag of an object I am instantiating, rather than the tag to represent some specific type of data. I'm very new to all this though, so maybe I am misunderstanding.
     
  4. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    Hm, I am not quite sure I know enough about what you want to achieve exactly. There are many many ways to modify behaviours of scripts. Can you please elaborate a bit on what you are trying to do? Maybe with some code examples.

    What I am pretty sure of is that looking up variables by string names based on tags is not a very good idea. You will run into trouble as soon as you rename a variable or convert it to a property or need to combine behaviours or, or, or, ... . And it's also bad from a performance/memory perspective.
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    There's definitely no reason to be using reflection here, especially within the same object. Why not just have a collection of these stickers, and provide a method that takes a predicate?
     
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    What's the purpose of the system that will be using this code? What platforms do you want to support?
     
  7. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    Thanks guys. Don't know exactly what I'm gonna do yet, but it's obvious I need to re think how I have stuff structured in general.
     
    _geo__ likes this.
  8. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    OK, let me show an example of my data and want I'm trying to do.

    I have stickers, which are game objects in unity, that I've saved as prefabs. The stickers fall into different categories. I've got arrays for each of these categories and I drag and drop all the sticker prefabs into their corresponding array in the editor, to store them and access them in the rest of my code.

    Then I have methods that I use to get information out of these arrays but I need a way to specify which of the arrays to look into. So I am passing strings into the methods, that correspond to the name of the array that I want it to check.

    Here's my code, and this actually works, though I'm getting the impression it's not a good way to do it.

    Code (CSharp):
    1. public class StickerManager : MonoBehaviour
    2. {
    3.  
    4.     [SerializeField] public GameObject[] hairStickers;
    5.     [SerializeField] public GameObject[] baseStickers;
    6.     [SerializeField] public GameObject[] outfitStickers;
    7.     [SerializeField] public GameObject[] weaponStickers;
    8.  
    9.     public GameObject GetStickerGO(string stickerTag, int arrayPos)
    10.     {
    11.         GameObject[] stickerArray = (GameObject[])this.GetType().GetField(stickerTag + "Stickers").GetValue(this);
    12.  
    13.         return stickerArray[arrayPos];
    14.     }
    15.  
    16.     public int GetStickerArrayLength(string stickerTag)
    17.     {
    18.         GameObject[] stickerArray = (GameObject[])this.GetType().GetField(stickerTag + "Stickers").GetValue(this);
    19.         int arrayLength = stickerArray.Length;
    20.      
    21.         return arrayLength;
    22.     }
    23. }
     
  9. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    You can use a dictionary to find the correct array given a tag.

    Code (csharp):
    1. public class StickerManager : MonoBehaviour
    2. {
    3.     [SerializeField] public GameObject[] hairStickers;
    4.     [SerializeField] public GameObject[] baseStickers;
    5.     [SerializeField] public GameObject[] outfitStickers;
    6.     [SerializeField] public GameObject[] weaponStickers;
    7.  
    8.     private Dictionary<string, GameObject[]> stickerArrays = new Dictionary<string, GameObject[]>();
    9.  
    10.     private void Awake()
    11.     {
    12.         stickerArrays["hair"] = hairStickers;
    13.         stickerArrays["base"] = baseStickers;
    14.         stickerArrays["outfit"] = outfitStickers;
    15.         stickerArrays["weapon"] = weaponStickers;
    16.     }
    17.  
    18.     public GameObject GetStickerGO(string stickerTag, int arrayPos)
    19.     {
    20.         if (stickerArrays.TryGetValue(stickerTag, out var stickerArray))
    21.         {
    22.             if (arrayPos >= 0 && arrayPos < stickerArray.Length)
    23.             {
    24.                 return stickerArray[arrayPos];
    25.             }
    26.         }
    27.  
    28.         return null;
    29.     }
    30.  
    31.     public int GetStickerArrayLength(string stickerTag)
    32.     {
    33.         if (stickerArrays.TryGetValue(stickerTag, out var stickerArray))
    34.         {
    35.             return stickerArray.Length;
    36.         }
    37.  
    38.         return 0;
    39.     }
    40. }
     
    Bunny83 and Boozebeard like this.
  10. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    530
    There is nothing in the code you pasted above which prevents usage of dictionaries or arrays instead of reflections, suggestions expressed by the others still apply.

    Few advice:
    * At runtime you don't have to use the serialized data directly. Unity serialization system and editor UI doesn't support dictionaries, but for the serialization and and automatic editor UI purpose you can store it as arrays and at runtime create the dictionary on demand. Need to be careful if you also want to change those lists at runtime as the simplest implementation won't automatically sync them. But in many common cases it's sufficient to just create the dictionary once on wakeup or first time it's needed without additional syncing afterwards.
    * Unity serialization system doesn't support nested arrays but you solve the issue by wrapping them in serialized classes.
    * Don't do everything at the same time - instead of creating a bunch of methods which lookup a category and then manipulates it in some way. Have separate method for doing the lookup, and separate for manipulation/querying. That way you might not even need to make additional methods for manipulation, but might be able to use the regular ones directly. That is instead of having GetStickerGOAt(string tag, int pos)->GO and GetStiackerGOArrayLength(string) it would be better to just have GetStickerArray(string tag)->GO[] , then you can do whatever you need with returned array without having to create a wrapper method for every possible array method.
     
    Boozebeard likes this.
  11. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    I guess ultimately what I was hoping for was a way to use the tag string that wouldn't require me to manually define each tag. The dictionary is kinda just a cleaner way of what I was doing before, which was if tag == x give arrayx for each possible tag.
     
  12. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    Well at some point you will have to make the connection between tags and sticker lists. You can either do that purely in code or use something like the class below to do it in the inspector.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class StickerManager : MonoBehaviour
    5. {
    6.     [System.Serializable]
    7.     public class SickerList
    8.     {
    9.         public string Tag;
    10.         public List<GameObject> Stickers;
    11.     }
    12.  
    13.     public List<SickerList> StickerLists;
    14.  
    15.     public GameObject GetStickerGO(string stickerTag, int arrayPos)
    16.     {
    17.         var stickers = GetStickersForTag(stickerTag);
    18.         if (stickers != null && arrayPos < stickers.Count)
    19.             return stickers[arrayPos];
    20.  
    21.         return null;
    22.     }
    23.  
    24.     public List<GameObject> GetStickersForTag(string stickerTag)
    25.     {
    26.         foreach (var list in StickerLists)
    27.         {
    28.             if (list.Tag == stickerTag)
    29.             {
    30.                 return list.Stickers;
    31.             }
    32.         }
    33.  
    34.         return null;
    35.     }
    36.  
    37.     public int GetStickerArrayLength(string stickerTag)
    38.     {
    39.         var stickers = GetStickersForTag(stickerTag);
    40.         if (stickers != null)
    41.             return stickers.Count;
    42.  
    43.         return 0;
    44.     }
    45. }
    46.  
    With this you can set them in the inspector.

    upload_2023-7-11_17-5-53.png
     
    Ryiah and Boozebeard like this.
  13. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    Ooook. I think I get it. You are making a list class, that has its own tag property(field? I get confused on the terminology lol), so I can check against that?
     
  14. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    Yes, it's an old trick since the Unity serializer can not serialize Dictionaries. With this you can define the Tag in one place (the inspector). No code changes needed for new tags.
     
    Ryiah and Boozebeard like this.
  15. Boozebeard

    Boozebeard

    Joined:
    Jun 10, 2015
    Posts:
    10
    Also, with these nested lists, could I not just put all my stickers prefabs into the top level list and have it sort them into new nested lists based on their tags?

    Edit: I guess that's what you meant by doing it in code instead of the inspector?
     
  16. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,111
    For each tag you add a new sticker list in the inspector, set the tag and then drag in your stickers for that tag into said list.
     
  17. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    An alternative to this is to bring in the free open source Odin Serializer since it is capable of serializing Dictionaries (and a ton of other things in the process).

    https://github.com/TeamSirenix/odin-serializer
     
    Boozebeard and _geo__ like this.