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

List or GameObject.Find?

Discussion in 'Scripting' started by Impervion, Feb 20, 2016.

  1. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    Hello,

    Noobie here, I was wondering what is better to do -

    I have a GameController Object with a GCController Script, inside I have a List of game objects.
    Then I made another script for every object I add to my game, which on Start adds to the above list.

    Another approach was to create many GameObject.Find's to locate/manipulate game objects to my liking within my needed scripts.

    Is listing them bad?
    Is GameObject.Find fine?

    Only reason I don't like the List format is trying to access my GameObjects, I found out how using a foreach, but then I'd need to do this within every script, which wouldn't be too bad since I'd be doing GameObject.Find a couple times as well instead. Also I find the list has a slight delay on startup, which makes sense since - list needs to be created, then game objects are added, then game objects can be manipulated.

    If anyone has any guidance on such a issue please let me know, I like messing around with things and/or get stuff to work, but don't know if its "proper" or a better way to do it.
     
  2. SlyRipper

    SlyRipper

    Joined:
    Jun 19, 2012
    Posts:
    251
    Well, First of all I need to say that you should try searching the forums first before asking such general questions.. there are plenty of Information about this topic.

    To answer your questions:
    Gameobject.find is a heavy function that you should avoid if you can. Lists to store and search your objects is much better. Lists on the other hand are also slow, so if you use duplicates in your list, use hashset instead. It's also better to create a static or singleton Version of your gamemanager that holds your list, then simply add your own find function to it. That way you can use it the same way like gameobject.find but with better performance, without the need to recode your function on each Script. Oh and if the delay on list creation is annoying you, then simply don't fill it at once on the Start, add objects when you create or need them, or google for lazy loading.
     
  3. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    I'm not sure what you mean, but I tried something like this :

    New Project with 3 Game Objects : GC/Blah/Ugh.

    GC has a script called GC with :

    Code (CSharp):
    1. public class GC : MonoBehaviour {
    2.  
    3.     public List<GameObject> objs = new List<GameObject>();
    4.  
    5. }
    6.  
    Blah and Ugh both have AddToList attached to them containing :
    Code (CSharp):
    1. public class AddToList : MonoBehaviour {
    2.  
    3.     GameObject gc;
    4.     public GC gcScript;
    5.  
    6.     void Awake(){
    7.         gc=GameObject.Find("GC");
    8.         gcScript = gc.GetComponent<GC> ();
    9.         gc.GetComponent<GC> ().objs.Add (gameObject);
    10.  
    11.     }
    12. }
    Then Blah has another script called BlahScript containing :
    Code (CSharp):
    1. public class BlahScript : MonoBehaviour {
    2.  
    3.     public void Rawr(){
    4.  
    5.         Debug.Log ("Rawr");
    6.  
    7.     }
    8.  
    9.  
    10.  
    11. }
    12.  
    Ugh also has a script called UghScript containing :
    Code (CSharp):
    1. public class UghScript : MonoBehaviour {
    2.  
    3.     void Update () {
    4.     foreach (GameObject game in GetComponent<AddToList>().gcScript.objs) {
    5.  
    6.             if(game.name=="Blah"){
    7.                 BlahScript blah = game.GetComponent<BlahScript>();
    8.                 blah.Rawr();
    9.                 }
    10.             }
    11.  
    12.  
    13.     }
    14.  
    15.  
    16.  
    17. }
    18.  
    Which then runs the Rawr() function without having to do the whole GameObject.Find stuff...?
     
  4. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520
    You should probably have a single GameManager script that manages all your game objects.

    Although seeing that you're new you really should not bother with optimizations since any benefits would be negligible.
    Just see what works and go with the flow.

    Also using Debug.Log is pointless for performance tests since this is probably one of the slowest functions in the engine, because it extracts stack trace before displaying the text, which takes a while.
     
  5. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,950
    Depends on how often you are accessing the list of objects. If it is infrequent, a dictionary works nicely. If it is often, use a builtin array. Find isn't a performance impact if you are using it in start/awake.
     
  6. SlyRipper

    SlyRipper

    Joined:
    Jun 19, 2012
    Posts:
    251
    I would create a script as GameManager, that holds the list of objects, like you're first script.. the simplest way that works here is to make the class static. Then add the AddToList(GameObject obj); function also to your GameManager as it's way better to maintain and also better coding ;)

    after that you simply need to call the GameManager.AddToList(this); on your instantiated gameobjects, for example in your Start/Awake functions.. and you get your lists. You can then simply access the List with GameManager.objs and use an index or other functions to get your desired objects..
     
  7. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520


    If you make the GameManager class static then you wont be able to attach it to a GameObject and it will not receive the Update event, which makes it pointless. What you might have meant is that GameManager class should have a static property which would hold the list of objects.

    Also by suggesting the OP should call GameManager.AddToList(this); from all GameObjects you imply that all those GameObjects should have a script attached to them, which beats the point of having a GameManager script.
    A much better way would be for GameManager script to find those GameObjects and add them to the list by itself, which would mean you wont have to make anything static.

    Btw when you make a property static it does not get reset when you switch scenes or reload a scene. So if you reloaded your scene you would have ended up with a list of non-existing GameObjects .
     
  8. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    If you want, you can use this little script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. public class ObjectTracker : MonoBehaviour
    5. {
    6.      static List<GameObject> emptyList = new List<GameObject>();
    7.      static Dictionary<string, List<GameObject>> collection = new Dictionary<string, List<GameObject>>();
    8.      public string tag = "myTag";
    9.      void Awake()
    10.      {
    11.           if (!collection.ContainsKey( tag ))
    12.               collection[tag] = new List<GameObject>();
    13.           collection[tag].Add( gameObject );
    14.      }
    15.      void OnDestroy()
    16.      {
    17.           collection[tag].Remove( gameObject );
    18.      }
    19.      public static List<GameObject> GetObjectsWithTag( string tag )
    20.      {
    21.           return collection.ContainsKey( tag ) ? collection[tag] : emptyList;
    22.      }
    23.      public static void Enumerate( string tag, System.Action<GameObject, int> callback)
    24.      {
    25.           if (!collection.ContainsKey( tag ))
    26.                return;
    27.           List<GameObject> list = collection[tag];
    28.           for (int i = 0; i < list.Count; i++)
    29.           {
    30.                if ( callback != null )
    31.                     callback( list[i], i);
    32.           }
    33.      }
    34.      public static void Enumerate<T>( string tag, System.Action<T, int> callback)
    35.      {
    36.           if (!collection.ContainsKey( tag ))
    37.                return;
    38.           List<GameObject> list = collection[tag];
    39.           for (int i = 0; i < list.Count; i++)
    40.           {
    41.                if ( callback != null )
    42.                {
    43.                     T component = list[i].GetComponent<T>();
    44.                     if ( component != null )
    45.                          callback( list[i], i);
    46.                }
    47.           }
    48.      }
    49. }
    Add this script to any object you want to have in a list and assign a tag via inspector. This approach is kinda neat because objects will add themselves to the list when they are created, and remove themselves when they are destroyed. To access your list via tag, just use:

    Code (CSharp):
    1. void Test()
    2. {
    3.      ObjectTracker.Enumerate( "Units", ( obj, i ) =>
    4.      {
    5.           Debug.Log("Unit - "+ i +". "+obj.name);
    6.      });
    7.  
    8.     // OR
    9.  
    10.     List<GameObject> list = ObjectTracker.GetObjectsWithTag("Units");
    11.     foreach (GameObject obj in list)
    12.     {
    13.          //....
    14.     }
    15. }
    Hope this helps!
     
    Last edited: Feb 21, 2016
    Vytek likes this.
  9. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Line 27 should be collection[tag], not collections[tag].
     
    Fajlworks likes this.
  10. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Yup, thanks for noticing the error, fixed :)
     
    User340 likes this.
  11. SlyRipper

    SlyRipper

    Joined:
    Jun 19, 2012
    Posts:
    251
    Well, it was just a simple solution with the static version, it might be better to create a singleton for such managers that hold stuff for lot of things.. however you can still use an update function elsewhere that gets the list from GameManager and uses it, that way you just have this static script that holds every variable you need and passes it to other scripts to work with..

    I haven't tested if for myself, but as far as I know the GameObject.Find() Method should be avoided to be used, and I guess a simple script on a GameObject that just adds it to a static list somewhere doesn't take that much performance/memory as to "search" for those GameObjects every time.

    Yepp that's true, there's just this version of it and it exists throughout the whole game, but it might come in handy to have something like that, as you can access this static list from everywhere to manipulate it.. shouldn't be so hard to clear the list on level changes if needed ;) but good you mentioned it ^^

    (btw was not my intention to troll you :p )
     
  12. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520
    @SlyRipper Obviously you didn't understand most of what I said...
     
  13. SlyRipper

    SlyRipper

    Joined:
    Jun 19, 2012
    Posts:
    251
    @zoran404 hmm, whatever :D hope we could at least help Impervion ;-)
     
  14. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,735
    @Impervion: Have you considered using a Dictionary in place of your list? You're right that a foreach every frame is bad, (and GameObject.Find is worse, as it's effectively a foreach lookup going through every GameObject) but a Dictionary lookup is very fast.

    You can and should make your game controller a singleton, or simply have objs be a static member of your AddToList class, either of which will avoid the initial GameObject.Find to find the controller. I'll use the first technique in the sample code below.

    You should use OnEnable and OnDisable, so that if your object gets destroyed, the entry is no longer pointing to a null object.

    Code (csharp):
    1. public class GC : MonoBehaviour {
    2. public static GC main;
    3. public Dictionary<string, GameObject> objs;
    4.  
    5. void Awake() {
    6. main=this;
    7. objs = new Dictionary<string, GameObject>();
    8. }
    9. }
    10.  
    11. // AddToList
    12. public class AddToList : MonoBehaviour {
    13. void OnEnable() {
    14. GC.main.objs.Add(gameObject.name, gameObject);
    15. }
    16. void OnDisable() {
    17. GC.main.objs.Remove(gameObject.name);
    18. }
    19. }
    20.  
    21. //anywhere else
    22. GC.main.objs["Blah"].GetComponent<BlahScript>().Rawr();
    23.  
     
  15. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    Have no idea what a "singleton" is, will have to look it up. Will also try to test your guys posts, thanks.

    Also @StarManta is that 2 different c# scripts or just 1 with the double MonoBehaviours? I;m assuming 2 using my previous "AddToList"..?
     
  16. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,735
    Intended as 2 different scripts, yes.

    A "singleton" is simply a class that has a static reference to one instance of that class, so that one instance can easily be accessed from anywhere (in this case, GC.main). They're really convenient in Unity because that instance can have references you can assign in its inspector.
     
  17. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    That's another thing, is inspector referencing bad(creating global and dragging into)? I figured it was a bad way to do things? In the sense of not "seeing" what your doing within code etc, if/when assigned via inspector(events etc, so I changed to coding the events, felt it was easier to find what was doing what.
     
  18. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    Hmm i started a new project and the first part worked, but when I made a "AddToScript", and used the
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class AddToScript : MonoBehaviour {
    6.  
    7.     void OnEnable() {
    8.         GC.main.objs.Add(gameObject.name, gameObject);
    9.     }
    10.     void OnDisable() {
    11.         GC.main.objs.Remove(gameObject.name);
    12.     }
    13. }
    14.  
    15.  
    I'm getting errors with the lines GC.~ does not exist in current context.

    Never mind, understood above stuff and realized the GC, needed to be changed to my new name(GCScript). Well that fixes the 1 script, but when I added to a new GameObject, I get a null reference error.

    did the above code :
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class GCScript : MonoBehaviour {
    6.  
    7.     public static GCScript main;
    8.     public Dictionary<string, GameObject> objs;
    9.  
    10.     void Awake() {
    11.         main=this;
    12.         objs = new Dictionary<string, GameObject>();
    13.     }
    14. }
    15.  
    16.  
    and
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class AddToScript : MonoBehaviour {
    6.  
    7.  
    8.     void OnEnable() {
    9.         GCScript.main.objs.Add(gameObject.name, gameObject);
    10.     }
    11.     void OnDisable() {
    12.         GCScript.main.objs.Remove(gameObject.name);
    13.     }
    14. }
    15.  
    16.  
    So basically my GC object has the GCScript attached to it, and then a random object has the AddToScript attached to it, and it returns a null reference.

    Have no idea whats going on.. but I re-applied the script to my GC object with the GCScript, and things work.. huh?(Maybe because I attached the script as soon as it was made).
     
    Last edited: Feb 23, 2016
  19. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    Made a new script and did this :
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Test : MonoBehaviour {
    5.  
    6.     public GameObject yo;
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.         yo = GCScript.main.objs ["Stuff"];
    11.     }
    12.    
    13.     // Update is called once per frame
    14.     void Update () {
    15.         yo .transform.position= new Vector3 (yo.transform.position.x+0.01F, yo.transform.position.y, yo.transform.position.z);
    16.     }
    17. }
    18.  
    And my object moves, so i may like this approach a lot more, definitely easier to locate stuff and call them etc.
     
  20. Impervion

    Impervion

    Joined:
    Oct 1, 2015
    Posts:
    44
    Opened up my test project again, and got null reference errors until I re-applied my GCScript to my GC object.. why? Also added a new object, attached AddToScript tried to run - null reference until I re-applied GCScript to my GC object....... so clearly I'm not understanding something.
     
    Last edited: Feb 23, 2016
  21. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,735
    It's possible that your AddToScript functions are being called before your GCScript's Awake has been run, so .objs is uninitialized when you're trying to add stuff to it. You can protect against this using encapsulation:
    Code (csharp):
    1. public class GCScript : MonoBehaviour {
    2. public static GCScript main {
    3. get {
    4. if (_main == null) _main = FindObjectOfType<GCScript>();
    5. if (_main == null) Debug.LogWarning("No GCScript in the scene!");
    6. return _main;
    7. }
    8. }
    9. private static GCScript _main;
     
  22. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Even better - guarantee it's always there
    Code (csharp):
    1.  
    2. public static GCScript main
    3. {
    4.     if (_main == null)
    5.         _main = new GameObject("GCScript").AddComponent<GCScript>();
    6.     return _main;
    7. }
    8.  
     
  23. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,735
    If this is an order-of-execution problem, that could result in two GCScript objects - AddToScript #1 would Awake, and a new GCScript would be created (and add #1 to its list), then existing-GCScript would Awake and set main to itself, then AddToScript #2 would Awake and add itself to existing-GCScript's list, and now you've got two incomplete lists.

    Both could be combined though, for maximum safety in all scenarios:
    Code (csharp):
    1.  
    2. public static GCScript main {
    3. get {
    4. if (_main == null) _main = FindObjectOfType<GCScript>();
    5. if (_main == null)
    6.         _main = new GameObject("GCScript").AddComponent<GCScript>();
    7. return _main;
    8. }
    9. }