0
\$\begingroup\$

In my use of Unity so far, I've been defining element names programmatically with classes like this:

public class MyClass : MonoBehaviour
{
    public const string Name = "MyName";

    public void Awake()
    {
        name = Name;
    }
}

My hope with this is to avoid magic strings in my project. Any other object that wants this object can run GameObject.Find with this constant and if I ever want to change the name for whatever reason, I only have to change one place and everything should just work.

public void Awake()
{
    GameObject x = GameObject.Find(MyClass.Name);
}

The issue that I'm seeing is Unity sometimes failing to find the object, instead just giving me null. I'm sure the problem is the order of different objects getting ran by Unity, but I'm not sure what the best practice would be here.

One option would be to use Awake for naming and Start for finding other objects, but this seems to be the opposite of what the documentation recommends for these functions. Would doing this prove to have negative consequences in the future? Is there another approach developers take to avoiding magic strings akin to this?

\$\endgroup\$
4
  • 2
    \$\begingroup\$ I would consider any use of GameObject.Find to be a code smell and something to remove, rather than build to accommodate. I bet that there are more robust systems that will let you avoid relying on names at all. If you have trouble eliminating name searches in a particular case, that would be worth posting a question about, and folks can suggest suitable architectures to serve that use case. \$\endgroup\$
    – DMGregory
    Commented Apr 13, 2022 at 2:32
  • \$\begingroup\$ I'm certainly open to alternatives. I do save instantiated prefabs like in that example. Currently, I'm using GameObject.Find to connect the base parts of my scene, like the GameManager and Camera both needing the Player object. Should I have the GameManager instantiated everything then call methods so everyone gets the references they need? That seems like it could get fairly overwhelming, but maybe it's manageable with good code structure. \$\endgroup\$ Commented Apr 13, 2022 at 2:45
  • \$\begingroup\$ For finding the player, FindObjectOfType would often work better. \$\endgroup\$
    – DMGregory
    Commented Apr 13, 2022 at 3:18
  • \$\begingroup\$ Great point, no need for Find for one-time objects. Thanks for the help - I'll be glad to no longer be dealing with names. \$\endgroup\$ Commented Apr 13, 2022 at 3:46

1 Answer 1

2
\$\begingroup\$

Is there another approach developers take to avoiding magic strings akin to this?

Yes, they don't use Find(name). It's slow, it's error-prone and it's fragile. The only reason why you name objects in Unity should be so you can tell them apart when you look at the scene tree in the editor. If you need objects to communicate with each other, then there are several better ways to do that than by linking them with Find:

  • Use tags and FindWithTag (which also has problems, but less so than Find)
  • Set up relationships via drag-and-drop inspector fields.
  • Have objects communicate via UnityEvents
  • When you are 100% sure there will only ever be one of a certain object in the scene, find it via the Singleton pattern.
  • Find objects at runtime by a specific component they are supposed to have, like via FindObjectOfType or GetComponentInChildren. When you have no fitting component for your particular use-case, then don't hesitate to create pure "tag components" which contain no methods or variables and only exist so you can tell an object apart from others.
  • Have objects instantiate each other from prefabs, and then keep the reference returned by Instantiate (myMinions.Add(Instantiate(minionPrefab));).
  • When you are interested in objects not just because of what they are but based on where they are in the game, detect them using trigger colliders or spatial detection with the Physics.OverlapWhatever methods, and then use GetComponent on the returned objects to filter them down to those you are actually interested in.
  • Use "Manager" behaviours which use any of the above patterns to keep track of references to certain objects and are able to provide them to other behaviors on request (like an EnemyManager with a method IEnumerable<Enemy> GetEnemiesWhichCanSee(Player p)).
\$\endgroup\$

You must log in to answer this question.

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