Search Unity

Is there no way to get reference of inactive gameObject?

Discussion in 'Scripting' started by leegod, May 26, 2017.

  1. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    So I am using GameObject.FindWithTag to get reference of some UI variables,

    but I want this process silently be hidden from user's eyesight, so I made gameobject inactive at inspector.

    But result null reference error.

    So I should make that UI gameobject active, but then I can't work because that UI hinders scene screen.

    I thought another way of moving UI gameobject to another position like (2000, 2000, 0) and then make it again to (0, 0, 0)

    but this also result in abnormal NGUI scrollview behaviour like after children generated, scrollview's x position set to unintended position like 8000.

    So I will blame NGUI and but this project was made from era of there is no UGUI, and still NGUI's scrollview movement has more feature than UGUI's (like drag and drop children moving between scrollviews)

    So what can be solution?
     
    Jdubedition likes this.
  2. FMark92

    FMark92

    Joined:
    May 18, 2017
    Posts:
    1,243
    Jdubedition and naxovr like this.
  3. listener

    listener

    Joined:
    Apr 2, 2012
    Posts:
    187
    There are couple of options to this.

    - Keep object active then disable
    - Disable only renderers if possible

    And then you can also use:

    transform.Find()
    transform.GetComponentsInChildren(true)
    Resources.FindObjectsOfTypeAll()

    I hope some of this helps.
     
  4. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
  5. FMark92

    FMark92

    Joined:
    May 18, 2017
    Posts:
    1,243
    Can you post example code?
     
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Don't search for the GameObject in the first place. Define a variable in your script, and assign it in the inspector.
     
    choudaniel3 and Ryiah like this.
  7. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    As @TonyLi mentions. Is there a reason you need to "find" it instead of creating a reference to it and connecting it in the inspector?
     
  8. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    if the Object is disabled. Just use "Resources.FindObjectsOfTypeAll" it finds everything thats loaded, including disabled objects.
    Code (CSharp):
    1. var obj= Resources
    2.     .FindObjectsOfTypeAll<GameObject>()
    3.     .FirstOrDefault(g=>g.CompareTag("TagName"));
    (you need to be using System.Linq for FirstOrDefault())

    Do keep in mind that this way is slow. the best way to handle this if to have a serializableField in your script and manually drag the UI Gameobject to it.
     
  9. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,525
    Stop using Find() methods. There is always, guaranteed, literally, every time, a better way.

    For example with UI, I've found that organizing things into panels with empty wrappers to hold the managing script is the ideal way to enable/disable child elements (ui graphics) while retaining the ability to communicate on the wrapper/managing script.

    Something like this:

    +Canvas
    ++CharacterPanel [UI_CharacterDisplay.cs] (enables/disables "Wrapper" as needed)
    +++Wrapper
    ++++CharLvlText
    ++++CharHPBar
    ++++CharMPBar

    Now, irregardless of whether or not the UI is displayed I can still have UI_CharacterDisplay.cs looking for Action Events, subscribing to stuff, updating the inactive UI, doing whatever I want it to.

    If these are very persistent items, such as they always exist in the scene, then just make a static class that houses references to them and anything can simply register itself on the static class, like a Game.cs class with `public static UI_CharacterDisplay CharDetails;` which literally anything can call at anytime they want without dealing with the stupidity of Find() methods.

    This is just one example but you will find (haha..)that there is always a better solution than using some kind of Find() method after doing a little bit of strategizing.
     
    Articnos, drgooo and Ryiah like this.
  10. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    example code is in above link.

    here it is.

    Code (CSharp):
    1. public static GameObject FindObject(this GameObject parent, string name)
    2. {
    3.      Transform[] trs= parent.GetComponentsInChildren(typeof(Transform), true);
    4.      foreach(Transform t in trs){
    5.          if(t.name == name){
    6.               return t.gameObject;
    7.          }
    8.      }
    9.      return null;
    10. }
    But so this result in error at [this], [Transform[]] part.
     
  11. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    Actually I prefer connecting directly to inspector way, but this was part of other developer worked and there is some workload to re-do. (over 10~20 things, of course I can do maybe in 20 minutes, but ... human is lazy)

    And this way has some advantages that, even if scene is broken and all inspector connecting info was gone, this can preserve because this uses code way.

    And also, prefab can't preserve connecting info to scene's object, but this can do.
     
  12. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    Thx, quite seems good. but slowness is.... bad. All gamers hate hiccups at start of the game. (this is because of while getting references in code..at background?)
     
  13. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    Thanks for let me know. This seems quite good way, but I can't understand how to do code exactly. Can you post the code to do this trick?
    And yes .Find is very sick and seems stupid.
     
  14. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    He means simply make obj a public field and you set it directly in the inspector, this is both the same thing I mentioned and recommend.

    Code (CSharp):
    1. public GameObject UiGameObject;
    now set the reference in the inspector. No need for Find. and the only thing you have to check is if the reference is null (cause you didn't set if or the gameobject was destroyed) before you start using it.

    Then have them both connect via a ScriptableObject Asset as I mentioned in this thread. This allows all instances of a prefab communicate to all instances of another prefab (in a many-to-many way) and it does so so that prefabs don't have to reference scene objects.
     
  15. Ironmax

    Ironmax

    Joined:
    May 12, 2015
    Posts:
    890
    Never use find. You should make a empty gameobject as a placeholder for its child component. So that the child can be inactive / active. Make the reference with public accessor.
     
  16. LemonLimeGames

    LemonLimeGames

    Joined:
    Jul 8, 2014
    Posts:
    23
    ...
     
  17. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,525
    Quality necropost
     
  18. M_R_M

    M_R_M

    Joined:
    Jan 31, 2019
    Posts:
    26
    To anyone who comes across this thread looking for various options on how to handle persistent objects or data: static classes are not only kind of counter to the purpose of object oriented programming, it can introduce bugs that can be very difficult to track down, and could make for exponential use case issues the longer or the more frequent the game is run. I personally think it's a pretty bad trade off for shaving a few milliseconds off when loading a scene. I won't say this is the "wrong" way of handing this, there are certainly instances where global data is extremely useful, but I very strongly disagree with notion that using any set of very simple functions that is an absolutely basic part of Unity is something that should be avoided at all costs in favor of something that is inherently often dangerous with OOP.

    Simply adding the objects to script via the inspector is fine for most things, but sometimes, as in the unique cases I have, there may be instances where you will be enabling and disabling persistent objects as various conditions are met in different scenes (as opposed to storing these objects in every single scene and ALSO having to find them each scene with multiple objects, and Unity will obviously not remember these stored references once the objects they are referring to or the objects referring TO them are destroyed, even if they are prefabs), and so far the least buggy and easiest way I've found of doing this is to simply set a single script in each scene that manages triggers for the various objects that will enable and disable them so they can be found by other objects. So far performance on scene loading has not even remotely been an issue, especially since I utilize proper transitions between scenes, and I'm also making a mobile game and testing on older hardware.

    Utilizing Start, OnEnable, and OnDisable has allowed me to set up a pretty complex system in almost all of the ways I needed without issue, and it's been very easy to manage.

    Just remember of course to never use any Find function in an update method of any kind.
     
  19. ImanIrt

    ImanIrt

    Joined:
    Sep 2, 2017
    Posts:
    3
    Found this on stack overflow, might be useful to someone:
    https://stackoverflow.com/questions/44456133/find-inactive-gameobject-by-name-tag-or-layer?rq=1

    Find in-active GameObject by Name:

    Code (CSharp):
    1.  
    2. GameObject FindInActiveObjectByName(string name)
    3. {
    4.     Transform[] objs = Resources.FindObjectsOfTypeAll<Transform>() as Transform[];
    5.     for (int i = 0; i < objs.Length; i++)
    6.     {
    7.         if (objs[i].hideFlags == HideFlags.None)
    8.         {
    9.             if (objs[i].name == name)
    10.             {
    11.                 return objs[i].gameObject;
    12.             }
    13.         }
    14.     }
    15.     return null;
    16. }
     
    fmontserrat and Lohaz like this.
  20. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Since this is getting necro'd anyway, now you can use UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects(). This returns all root GameObjects, including inactive ones. Then recursively search through them by transform.

    Or, as I mentioned a few years ago, assign a reference so you don't have to search for it.
     
  21. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    @TonyLi
    I found this thread by happenstance and I do have a question for you on this topic given the following situation:
    I have about 5 static do not destroy on load managers in my game and when I switch scenes, they all lose the inspector assigned UI element.
    I learned about not using the find function and instead opted to FindGameObjectWithTag and assign tags to those same game objects.
    The issue arises when the game object is not active. Nullreference errors become abound.
    Would the GetRootGameObjects() you mentioned pose a problem when loading big scenes with multiple objects just to find that one inactive object?
     
  22. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    It's certainly not a good idea to search through the entire hierarchy every frame. If you're doing it once at the beginning of the scene it's not so bad.

    However, why search in the first place? Better to maintain references that you've assigned at design time.

    If you need to assign scene-specific UIs to your DontDestroyOnLoad managers, you can add a script to the scene that holds references to those managers:
    Code (csharp):
    1. public class SceneUIElements : MonoBehaviour {
    2.     public Text sceneInfo;
    3.     public Slider sceneScore;
    4. }
    From there, you can update references in one direction or the other. For example, the script could assign its references to the managers:
    Code (csharp):
    1. public class SceneUIElements : MonoBehaviour {
    2.     public Text sceneInfo;
    3.     public Slider sceneScore;
    4.  
    5.     void Awake() {
    6.         InfoManager.instance.infoText = sceneInfo;
    7.         ScoreManager.instance.scoreSlider = sceneScore;
    8.     }
    9. }
    I'm not a huge fan of this. If something is in a scene, it should probably be managed by scripts in the scene. But I don't know your specific situation, so that may not apply.
     
    twobob and Ryiah like this.
  23. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    That is actually a pretty good idea! I didn't think of having them as public placeholders to be picked up on awake.
    In my situation, I have a HP manager, a hunger manager, a cash manager and a time manager.

    When I change scenes, the errors I get are "NullReferences" in that they cannot find the UI.text components to update and represent the values of each instances. I'll try it out and see how it goes! :)
     
  24. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    Hm, did not work as I would have hoped.
    When loading another scene, the instances that persist never recover the new UI element of the same name/type...

    The only timing that seemed to work was neither Awake or Start but Update and using the if (==null) condition.
    I am going to guess this is a terrible way of doing it as it would run on every frame but no other timing seems to work for this.
     
  25. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    You probably want to run it after scene loads.

    https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager-sceneLoaded.html
     
  26. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    My example above assumes that SceneUIElements exists individually in each scene, and InfoManager.instance & ScoreManager.instance point to your DontDestroyOnLoad managers.

    When the SceneUIElements in the newly-loaded scene runs its Awake() method, it should set the managers' values properly.
     
  27. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    Based on that doc, Void start should work and I retried it with a small modification and indeed it worked!
    Thank you! :)

    So, in case this Necro becomes relevant again, what I did was set up an empty game object with a "UI reference".
    It looks like this:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class UI_References : MonoBehaviour {
    7.     public GameObject HealthBar, HungerBar;
    8.     public Text TimerText, CashText, CashShopText;
    9.  
    10.     private void Start()
    11.     {
    12.         ProtoMoneyManager.protoMoneyManager.moneyText = CashText;
    13.         ProtoMoneyManager.protoMoneyManager.moneyTextShop = CashShopText;
    14.         ProtoMoneyManager.protoMoneyManager.UpdateUI();
    15.     }
    16. }
    The ProtoMoneyManager is static and has a DoNotDestroyOnLoad.
     
  28. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    I am not sure why but when I do Awake, it works for my timer, but not my cash count.
    When I do Start, it works for both.
    I think I have an idea why, I will do some testing.
     
  29. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    If it's in the scene in which your managers start, then it's probably because their Awake methods haven't run yet. Putting it in Start is fine, too.
     
  30. tetto_green

    tetto_green

    Joined:
    Dec 22, 2015
    Posts:
    35
    Try this solution. In short, you grab all root game objects in active scene and then iterate through there children:
    Code (CSharp):
    1. List<GameObject> FindObjectsWithTag(string tag)
    2. {
    3.     List<GameObject> rootObjects = new List<GameObject>();
    4.     List<GameObject> result = new List<GameObject>();
    5.  
    6.     Scene scene = SceneManager.GetActiveScene();
    7.     scene.GetRootGameObjects( rootObjects );
    8.  
    9.     // iterate root objects
    10.     for (int i = 0; i < rootObjects.Count; ++i)
    11.     {
    12.         var objectsWithTag = FindObjectsWithTag(tag, rootObjects[i]);
    13.  
    14.         if (objectsWithTag != null)
    15.         {
    16.             result.AddRange(objectsWithTag);
    17.         }
    18.     }
    19.  
    20.     return result;
    21. }
    22.  
    23. List<GameObject> FindObjectsWithTag(string tag, GameObject parent)
    24. {
    25.  
    26.     List<GameObject> result = new List<GameObject>();
    27.     var children = parent.GetComponentsInChildren<Transform>(true);
    28.    
    29.     if (parent.CompareTag(tag))
    30.     {
    31.         result.Add(parent);
    32.     }
    33.    
    34.     foreach (Transform child in children)
    35.     {
    36.         if (child.CompareTag(tag))
    37.         {
    38.             result.Add(child.gameObject);
    39.         }
    40.     }
    41.  
    42.     return result;
    43. }
     
    MatheusPereiraBarros likes this.
  31. Volcanicus

    Volcanicus

    Joined:
    Jan 6, 2018
    Posts:
    169
    Huh, that's a pretty neato solution. My workaround was to increase the load time by 0.5 seconds and it resolved the issue.
     
  32. kris007_iron

    kris007_iron

    Joined:
    Oct 5, 2020
    Posts:
    3
    [QUOTE = "TonyLi, post: 3084528, member: 100586"] Nie szukaj w pierwszej kolejności GameObject. Zdefiniuj zmienną w swoim skrypcie i przypisz ją w inspektorze. [/ QUOTE]
    But what if you have a prefab?
     
  33. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Point your variable to the prefab. In Awake, instantiate the prefab. In Start/OnEnable, you can use the instance. Example:
    Code (csharp):
    1. public GameObject buttonPrefab;
    2. private GameObject button;
    3.  
    4. void Awake()
    5. {
    6.     button = Instantiate(buttonPrefab, uiPanel);
    7. }
    8.  
    9. void Start()
    10. {
    11.     // Here you can work with 'button'.
    12. }
     
  34. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    Have there been any changes since the last post?

    My app's adding an object (not a prefab) with a "click me" script at runtime, which has to enable an already existing (but inactive) panel once clicked, but I can't assign the panel in the inspector because the object simply doesn't exist yet. Inside the script's "Start" function I store a reference to the panel, so I won't have to look for it again when I want to enable it again.
    What's the best/fastest way to find the (still disabled) panel (and its children, including a TMP text and 2 buttons), preferably without iterating through all the objects in the scene?
     
    Last edited: Jan 26, 2022
  35. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    The typical approach I take is to just have a manager script in the scene that has a reference to the object. Then instead of the object itself, you simply access the variable in the manager script and use it that way.

    Another option is instead of making the gameobject inactive is to turn off certain components, such as the rendering component (image, sprite, whatever) and then you can still access the gameobject and just turn on the components.

    Generally though, I choose the manager approach to try and avoid "Find" calls.
     
    Kurt-Dekker likes this.
  36. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    Is your manager script static? You can't make a "MonoBehaviour" static and can't use the inspector for static scripts either, so you'd need another, non-static script to set its references. If not, how do you get access to its references in other scripts?
    If I want to access another script from a script (e.g. to disable it), then I have to use its GameObject's ".GetComponent<MyScript>()", which requires a "Find" call if you can't use the inspector (like this).
    If I disable an e.g. panel's image, then its children are still visible and there's no component on a scrollbar that you can disable to make it disappear, you can only disable the whole thing.
     
    Last edited: Jan 27, 2022
  37. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    You should look into the concept of a manager script. They are similar to singletons and manager scripts can be singleton, but they don't always have to be limited to a single instance.
    In a nutshell though, it maintains a static reference to itself, but the script isn't static.
    And trust that I've been doing this a long time. I know many of the things you can and can't do on scripts. :D

    There are a bunch of ways to make a whole group invisible. A canvas group with alpha at 0 and interactable set to false can cover many things in a group.
    Another solution is when you disable your script, you have an ondisable method that loops through and turns off the children. Then when the script is enabled, you can have it loop through and turn them on. Also, yes, this would require a single find call, which is why I don't normally use this approach.

    There are many ways to accomplish what you want to do. But again, simple approach is a manager script.
     
  38. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    @Brathnann
    Oh, so a "manager script" is an actual type of script, not just a script named "ManagerScript"? I did create the latter, connected it to an empty GO and it works but yes, it requires a single "Find" call.
    Canvas group? I always just use a single panel that contains the other UI elements and set that panel active/inactive, which also automatically disables the children. The panel I'm talking about is always disabled in the beginning. It's only enabled when the player clicks on the object and it's disabled again when the player clicks on the panel's "Close" button. The script is only connected to the GO (as a component) once the GO is actually added to the scene and is then enabled automatically but it's never disabled. Once the GO isn't needed anymore, the script is destroyed with it.
     
  39. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,330
    Here is one fast solution for retrieving an instance that works even if the object is inactive:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class Panel : MonoBehaviour
    5. {
    6.     private static readonly List<Panel> instances = new List<Panel>(3);
    7.  
    8.     public Panel() => instances.Add(this);
    9.     ~Panel() => instances.Remove(this);
    10.  
    11.     public static Panel GetInstance()
    12.     {
    13.         for(int i = instances.Count - 1; i >= 0; i--)
    14.         {
    15.             var instance = instances[i];
    16.  
    17.             // Remove references to prefab assets and temporary
    18.             // deserialization-related objects that no longer exist.
    19.             if(IsNullOrPrefab(instance))
    20.             {
    21.                 instances.RemoveAt(i);
    22.             }
    23.         }
    24.  
    25.         return instances.Count > 0 ? instances[0] : null;
    26.     }
    27.  
    28.     private static bool IsNullOrPrefab(Panel instance) => instance == null || !instance.gameObject.scene.IsValid();
    29. }
    This works because the constructor is called even for inactive components unlike Awake. The downside is that the constructor also gets called for prefab assets and temporary deserialization-related objects so we need to add special handling to ignore those.


    Personally I would just keep it simple though and use
    Object.FindObjectOfType(true)
    to fetch the instance the first time and then cache the result for subsequent retrievals.
     
  40. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    If you set it up as a manager script, there should be no need for a Find call.

    If you're interested in a canvas group, you should look it up. If not, don't worry about it, I tossed it out there as an option. There are multiple ways to achieve things in Unity.
     
  41. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    If I also want to access inactive buttons or a text, then I'd have to also use an edited version of that script on their GO, correct?

    Are you talking about a "Game Manager" (like this - but if it's not connected to a GO, then how do I assign stuff to it in the inspector?)? I googled "manager script" but only found something about a static class on answers.unity, which is probably not what you were talking about.
     
  42. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    Essentially, they are usually like this.

    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.    public static GameManager instance;
    4.  
    5.    private void Awake()
    6.    {
    7.       instance = this;
    8.    }
    9. }
    Then, any other class can access GameManager through

    GameManager.instance.aPublicMethodOrField

    As I mentioned, look into singletons. The code above is not a singleton pattern because it doesn't limit itself to 1 copy and isn't DontDestroyOnLoad, but not every manager needs to be a singleton, but a singleton should give you an idea of how this works without needing to use a Find call.
     
    SimRuJ likes this.
  43. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,330
    I think would be better to only retrieve the panel using the static GetInstance method and then serialize component references in the Panel to everything else that the Panel needs to function.

    Code (CSharp):
    1. public class Panel : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private TMP_Text text;
    5.  
    6.     [SerializeField]
    7.     private Button confirmButton;
    8.  
    9.     [SerializeField]
    10.     private Button closeButton;
    11.  
    12.     public void Open(string text, Action onConfirmed)
    13.     {
    14.         this.text.text = text;
    15.         confirmButton.onClicked.AddListener(onConfirmed);
    16.     }
    17.  
    18.     ...
    19. }
     
  44. JDyo2

    JDyo2

    Joined:
    Sep 1, 2017
    Posts:
    2
    Gracias, lo logré. Necesitas hallar el Objeto Padre y luego usar su transform para hallar su hijo e invocar el gameObject para ponerlo true.

    Ty, i did it. You need to find the parent gameObject and then to use his transform to find his child and invoke the gameObject to set it active true.
    Code (CSharp):
    1. GameObject goPadre = GameObject.Find("ParentActive");
    2. goPadre.transform.Find("ChildUnactive").gameObject.SetActive(true);
     
    Last edited: Oct 3, 2022