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

Problem with GameObject.Find(...)

Discussion in 'Scripting' started by boylesg, May 14, 2016.

  1. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    I have this sort of hierarchy in the Unity editor (Bingo3D):

    Options->Pattern2Row->Card->Counter1
    ...
    Counter25

    Pattern2Col->Card->Counter1
    ...
    Counter25
    PatternX->Card->Counter1
    ...
    Counter25

    Counters 1-25 are just small circles used to represent the buttons placed by players on their bingo card and they are children of the Card objects if that is not clear above.
    The game objects with the name 'Card' are just 2D sprites representing the bingo cards.

    Scripts/classes of the same name are attached to Pattern2Row, Pattern2Col and PatternX

    These scripts/class are derived from a parent script/class called PatternManager and simply set an enumerated type member variable, in their constructors.

    The base script/class then use that enumerated type member variable to determine which pattern to display on the bingo card sprite, i.e. by simply showing and hiding Counter1-25. In effect it is an animation I suppose and could have been done with blender....but never mind.

    In the base script/class I have GameObject members like this:

    private GameObject m_gameobjCounter1;
    ...
    private GameObject m_gameobjCounter25;

    I want to use GameObject.Find("...") to set each of these GameObject members to the appropriate game object in the Unity editor hierarchy described above.

    In effect I am trying to avoid having to drag and drop over 100 game objects in the Unity editor and also quarantine the links from any changes I might want to make that involves removing or moving scripts - often when you do that sort of thing Unity editor deletes all the links and you have to drag and drop all over again.

    So my question....

    I know that you can specify a path as well as a name in GameObject.Find("...")

    But how can I ensure, for example, that the path "Card/Counter1" refers to "Pattern2Row->Card->Counter1" for the instance of the script/class attached to Pattern2Row game object in the Unity editor?
     
  2. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,133
    This is kinda hard for me to visualize what your after. Can you post a screen shot of the gameobjects?

    So the primary GameObject is "Pattern2Row" with subject "Card" and it has a subobject "Couter1". Then you have script attached to the primary GameObject. This script has 25 private variables, and you want the script to auto assign the subjects to those?
    That can be done with GetComponentsInChildren<>()

    but I'm not sure how you're going to distinguish between them to make sure, Couter1 in assigned to m_gameobjectCounter1. That maybe the hard part to figure out.
    And I don't know what you need to access on the child object.

    You might be able to do it the reverse, create a script for the child object to updates the variable on the parent object. That would be easy to create. I could help you with that.
     
    Last edited: May 14, 2016
  3. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    Screen shot attached.

    I thought the GetComponent functions were exclusively for fetching components within a single game object - rigid bodies, colliders and such like?
     

    Attached Files:

  4. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    Never mind I figured it out.

    This works nicely:

    m_gameobjCounter1 = gameObject.transform.Find("Card/Counter1").gameObject;
     
    Kiwasi likes this.
  5. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,133
    here is what I was thinking.

    for the parent object

    Code (CSharp):
    1. public class Pattern : MonoBehaviour {
    2.  
    3.     [SerializeField]
    4.     private GameObject m_gameobjCounter1;
    5.     [SerializeField]
    6.     private GameObject m_gameobjCounter2;
    7.     [SerializeField]
    8.     private GameObject m_gameobjCounter3;
    9.     [SerializeField]
    10.     private GameObject m_gameobjCounter4;
    11.     [SerializeField]
    12.     private GameObject m_gameobjCounter5;
    13.  
    14.  
    15.  
    16.     // Use this for initialization
    17.     void Start () {
    18.    
    19.     }
    20.    
    21.     // Update is called once per frame
    22.     void Update () {
    23.    
    24.     }
    25.  
    26.     public void SetGameObjectInParent(int i, GameObject childObject)
    27.     {
    28.         switch (i)
    29.         {
    30.             case 5:
    31.                 m_gameobjCounter5 = childObject;
    32.                 break;
    33.             case 4:
    34.                 m_gameobjCounter4 = childObject;
    35.                 break;
    36.             case 3:
    37.                 m_gameobjCounter3 = childObject;
    38.                 break;
    39.             case 2:
    40.                 m_gameobjCounter2 = childObject;
    41.                 break;
    42.             case 1:
    43.                 m_gameobjCounter1 = childObject;
    44.                 break;
    45.             default:
    46.                 print("Not found");
    47.                 break;
    48.         }
    49.  
    50.  
    51.     }

    and for the child object
    Code (CSharp):
    1. public class Counter : MonoBehaviour {
    2.     public int myInt = 1;
    3.  
    4.     // Use this for initialization
    5.     void Start () {
    6.         GetComponentInParent<Pattern>().SetGameObjectInParent(myInt, gameObject);
    7.     }
    8.    
    9.     // Update is called once per frame
    10.     void Update () {
    11.    
    12.     }
    13. }
     
  6. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,133
    What about the issue of manually setting 100+ objects
    The problem you'll find with your code it that if you have multiple gameobjects with the same name. it might not assign the correct Counter1 to it parent.
     
    Last edited: May 14, 2016
  7. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    Well the transform version of Find(...) apparently works like the gold old MS DOS 6.22 concept of a 'current working directory'.

    If you care copying files with in the 'current working directory' then you don't have to specify a full path (from the root) in either the source file or the destination location.

    Apparently GameObject.Find(...) assumes a full path from the root regardless of whether or not you specify it in the string you pass to the function.
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I'd approach this a different way. I'd Instantiate the pieces from prefabs and then keep references to them in an array (or other appropriate collection).

    Essentially just a couple of for loops to set up. Then you have solid references forever.
     
  9. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    My problem with that method is that it is just a flat list. So if I wanted to send the same message to only particular objects in that list then I would have a lot of code to determine the identity of each game object and whether or not it should receive that particular message type.

    And every time I added another game object I would have to go an modify that if/else statement to take account of the new game object.

    In the scheme I have come up with the list is structured in that a message type is associated with the game objects that should receive it if it is broadcast - a lot less overhead than iterating through a full array of game objects.

    Populating my array is as simple as doing these steps:

    Add your message type into this:
    Code (CSharp):
    1. public enum MessageIDEnum
    2. {
    3.    MSGID_CHANGE_PATTERN,
    4.    MSGID_CHANGE_AUTO_SPIN,
    5.    MSGID_SAVE_CHANGE_PATTERN,
    6.    MSGID_SAVE_CHANGE_AUTO_SPIN,
    7.    MSGID_SHOW_OPTIONS,
    8.    MSGID_HIDE_OPTIONS,
    9.    MSGID_SHOW_BINGO,
    10.    MSGID_HIDE_BINGO,
    11.    MSGID_SHOW_HIDE_COUNTER,
    12. }
    Register you script class to receive a particular message type like this:

    Code (CSharp):
    1. public PatternManager()
    2.    {
    3.      RegisterMessageReceiver("PatternManager", MessageIDEnum.MSGID_SAVE_CHANGE_PATTERN);
    4.    }

    Post a message to registered receivers of that message type like this:

    Code (CSharp):
    1.     Parameters p = new Parameters();
    2.      p.m_objParam1 = m_bAutospin;
    3.      PostMessage(MessageIDEnum.MSGID_SAVE_CHANGE_AUTO_SPIN, p);
    And process your received messages like this:

    Code (CSharp):
    1. protected override void DoReceiveMessage(MessageIDEnum enMsgID, Parameters p)
    2.    {
    3.      if (enMsgID == MessageIDEnum.MSGID_CHANGE_PATTERN)
    4.      {
    5.        m_enPattern = (PatternEnum)p.m_objParam1;
    6.      }
    7.      else if (enMsgID == MessageIDEnum.MSGID_CHANGE_AUTO_SPIN)
    8.      {
    9.        m_bAutospin = (bool)p.m_objParam1;
    10.      }
    11.      base.DoReceiveMessage(enMsgID, p);
    12.    }
    13.  
    14.      }
    15.      else if (enMsgID == MessageIDEnum.MSGID_CHANGE_AUTO_SPIN)
    16.      {
    17.        m_bAutospin = (bool)p.m_objParam1;
    18.      }
    19.      base.DoReceiveMessage(enMsgID, p);
    20.    }
    21.  
    22.  
     
  10. boylesg

    boylesg

    Joined:
    Feb 18, 2016
    Posts:
    275
    My idea is not really original.

    It has been spawned from message maps in Microsoft Foundation Classes

    In windows programing the windows sub-systems communicate asynchronously with the various windows in your application by means of 'messages' which amount to unique integer IDs plus any parameters.

    MFC has a series of macros that you use to build a message map.

    In your class header file you put this in your class definition: DECLARE_MESSAGE_MAP(ClassName)
    In your class cpp file you put this:

    BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    ON_COMMAND(ID_MYCMD, &CMyDoc::OnMyCommand)
    END_MESSAGE_MAP()

    There are dozens and dozens of macros that correspond to the various windows messages that you application can receive from the windows sub-system.

    Visual Studio makes this very easy with a 'Class Wizard'.

    You select the class that you want to add a message handler to, then select the type of windows message you want your class to receive and the name of the function that will handle that message.

    Then the class wizard populates the above message map with the appropriate macro and it creates the handler function for you along with any specific pre-defined parameters that it needs.

    It makes maintaining your application and adding functionality to it very quick and easy.

    For example, when I see the ON_COMMAND macro in a message map, then I immediately know that it is related to the menu system (as File->open). The first parameter is the unique integer ID of the menu command that is being acted upon. Normally the name of you ID would reflect the hierarchy of the menu e.g. ID_FILE_OPEN, ID_FILE_SAVE etc. The second parameter is a pointer to the function that handles than menu command.

    So the MFC message map provides you with an easily identifiable table of contents, of sorts, for the major functional components of your windows class.

    So I am trying to emulate this sort of convenience in my Unity scripts.
     
    Last edited: May 14, 2016