0
\$\begingroup\$

When structuring the script to control a gameobject with several generations of children underneath, I gather from one of Jason Weimann's videos that it's a bad idea to use the string based methods to get your references like "transform.Find("objectName") because those names could change and break things.

So what's the best way to do it? I'd prefer not to have to wire everything up via the inspector. I guess you can use GetChild() but then you run the risk of breaking it a different way if that index ever changes.

Thanks for any advice.

EDIT - more detail

As an example, I currently have a custom UI slider prefab that is a gameObject containing several child gameObjects, which in turn have components or subchildren with components such as text and Shapes (from the Shapes plugin), which move dynamically during interaction and therefore need their own local transforms. There is also a raw image to catch interactions.

The whole thing is controlled by a script on the root gameObject, so it needs references to all the children to set their position / values / colors etc.

The dependencies are all in the master script, none of the sub components know about the master script, their parameters get set by it.

Currently I have it set up to reference via things like transform.Find("child1").Find("child2").gameObject.GetComponent<>....

Is that the best way in this case? Would it be better to refactor so each component has its own script and just referenced the master script?

EDIT 2 -

One thing I don't think I made clear:

This isn't a regular slider that sets the parameters of shapes, it is its self a new stylable UI element I've created that replaces Unity's standard slider, and is built out of Shapes and textMeshPro assets.

There aren't any testible rules about which object should be referenced, the variable for the "Outline thickness" will always need to link to that parameter on a particular Shape component a few levels down the hierarchy.

Maybe in this case just using names and making sure they don't get changed is fine?

\$\endgroup\$
5
  • \$\begingroup\$ To know which object we should find, we need to know something about the criteria that distinguish a "correct" object selection from an "incorrect" selection. So, you'll need to add some concrete detail here. Can you give us an example of a particular instance where you need to look up child objects at runtime? In that instance, what characterizes/distinguishes the correct objects to be returned? \$\endgroup\$
    – DMGregory
    Dec 6, 2021 at 22:12
  • \$\begingroup\$ @DMGregory Hi, added more detail in original post. \$\endgroup\$ Dec 7, 2021 at 16:59
  • \$\begingroup\$ We're still missing a clear statement of a rule of the form "You will know you have the correct object when it has the trait [X]". Before we can teach the computer to follow that rule in code, we need to have a clear picture of what that rule is. So, walk us through the specific shapes and text that you want this slider to set. What sets each shape/text item apart from the others? What are the different operations that the master script has to perform on each? (I know this may sound overly specific, but in solving the specific problem we can show techniques that generalize to similar cases) \$\endgroup\$
    – DMGregory
    Dec 7, 2021 at 18:12
  • 2
    \$\begingroup\$ Your exact situation is unclear to me but maybe look for a ways to make the whole thing more "modular" where each parent is only responsible for its immediate children and deals only with them. Then the next "level" have its own component and so on. This way the number of inspector references won't get overwhelmingly many and the relations of who does what will be more clear. Anyway it seems to me that maybe inspector references are the right way even if it is left as one MonoBehaviour. \$\endgroup\$
    – Nikaas
    Dec 8, 2021 at 7:49
  • \$\begingroup\$ Related: docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/… \$\endgroup\$
    – Evorlor
    Jan 7 at 22:21

1 Answer 1

0
\$\begingroup\$

There are a few ways you can do this, with different impact on performance, and different ease of use. You haven't specified why you want to avoid setting them in the inspector, so it's hard to say if the solution offered will be better for you. Still, here are a couple that may work.

Option 1: Create a component that specifies what type of child this is (ChildTagger in my example below), add it to each of the children you want to reference to, then use GetComponentsInChildren to iterate these children and assign them each according to their tag. Changing hierarchy and object names will not break this as long as you keep this component and tags.

public class ChildTagger : MonoBehaviour {
    public string myTag;
}

public class MyClass : MonoBehaviour {
    private Transform circle;
    private Transform triangle;

    private void Awake() {
        foreach (ChildTagger child in gameObject.GetComponentsInChildren<ChildTagger>()) {
            switch (child.myTag) {
                case "circle":
                    this.circle = child.transform;
                    break;
                case "triangle":
                    this.triangle = child.transform;
                    break;
                default:
                    Debug.LogError("Unhandled child tag: " + child.myTag);
                    break;
            }
        }
    }
}

Option 2: Iterate the full child hierarchy and search for object by name. This is similar to the transform.find that you used in your example above, but it will not be dependant on the child's exact position in the hierarchy, whether it is a direct child or grandchild. It will, however, take longer to run, so don't use this if you plan to run this code many times per frame.

public class MyClass : MonoBehaviour {
    private Transform circle;
    private Transform triangle;

    private void Awake() {
        void IterateTransform(Transform t) {
            // Check if this object is one that we need to assign
            switch (t.name) {
                case "circle":
                    this.circle = t;
                    break;
                case "triangle":
                    this.triangle = t;
                    break;
            }               

            // Iterate child objects
            for (int i = 0; i < t.childCount; i++) {
                IterateTransform(t.GetChild(i));
            }
        }
    }
}

Last note, if you use this search methods for convenience, so you don't have to manually assign in the inspector, I would still recommend allowing for the options to assign in the inspector, and letting your component know that it should skip the search in such cases. That would allow much better performance in cases where it matters (by using the inspector), but still allow for convenience in cases where performance is not an issue.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .