Unity Devs, stop using GameObject.Find!
Becoming confident in your app's stability using the Unity Inspector
The problem
Unity offers several approaches for resolving a reference to a GameObject in the scene. A common choice is to use the object's name, either with the GameObject.Find method, which will look through all the objects in the scene, or by searching through the names of another GameObject's children for a match. Both of these options are bad practices.
Why? Well, let's look at a small example. Here's a script that makes some text display how many objects are inside the scene's UI canvas.
using UnityEngine;
using UnityEngine.UI;
public class ShowNumberOfCanvasChildren : MonoBehaviour
{
private GameObject _canvas;
private Text _childText;
private void Start()
{
// grab an object in the scene called Canvas
_canvas = GameObject.Find("Canvas");
// and grab some text in a child of the object
// that this script is attached to
_childText = this.transform.FindChild("ChildText").GetComponent<Text>();
}
private void Update()
{
// on every frame, modify that text
_childText.text = "Canvas child count: " + _canvas.transform.childCount;
}
}
This script introduces a number of problems:
- I don't know if those objects actually exist under those names. What if I typo'd "Canvas" as "Camvas"? What if the "ChildText" was called "ChildTextObject" instead? My compiler won't give me any support with these string constants, so I won't have any idea until I run the app and my script breaks.
- I can't change my scene layout without breaking this script. If I change the child text's parent, or move the text component into the parent instead of keeping the component inside the child, my script will no longer be able to find these objects, causing the application to throw errors.
- I can't rename objects freely. If I realize later that "ChildText" is a vague name, I might rename it inside the inspector without checking any scripts beforehand. This simple and seemingly harmless act will be violently punished with a torrent of NullReferenceExceptions.
All of these issues are symptomatic of a disease in your codebase: The dependencies for your scripts are hidden away instead of being stated outright. When you attach your script, you have no idea what its requirements are. This prevents you from knowing whether or not that script can be reused throughout the codebase or if it will only work within a specific context. This breaks the whole idea that your script should be a reusable component with only a single responsibility.
This is the way your script will appear inside the inspector after you attach it to some object.
We just see at the bottom-right that it is a script, with no extra information given.
When I attach a script that looks like this, I think: "Uhh...I guess this will work? Maybe?", and then I run my game and more often than not the whole thing will blow up in my face. This happens because I have no clue what context my script needs to work properly. Soon the developers on your team will be petrified of touching anything in the scene, worried that any small modification will break some random script.
A better approach
Instead of using object names that are hidden away in our script to resolve our dependencies, let's show our dependencies in the inspector instead. This way, we know exactly what a script needs when we attach it. To do this, let's make our fields public.
using UnityEngine;
using UnityEngine.UI;
public class ShowNumberOfCanvasChildren : MonoBehaviour
{
public GameObject Canvas;
public Text NumChildrenText;
private void Update()
{
NumChildrenText.text = "Canvas child count: " + Canvas.transform.childCount;
}
}
Alternatively, if you dislike making things public(and you should), you can use the SerializeField attribute instead to make your private variables available to edit in the inspector. The documentation seems to discourage using SerializeField with an explicit note that you will never need to use it, but I can't come up with any reason why you shouldn't.
using UnityEngine;
using UnityEngine.UI;
public class ShowNumberOfCanvasChildren : MonoBehaviour
{
[SerializeField] private GameObject _canvas;
[SerializeField] private Text _numChildrenText;
private void Update()
{
_numChildrenText.text = "Canvas child count: " + _canvas.transform.childCount;
}
}
Whichever option we choose, here is how the script will appear when we attach it in the inspector after these changes:
Nice, now we see that our script relies on two things: the canvas and some text to display the number of children with. The inspector also tells us that those dependencies have not been hooked up yet. We can do that by dragging and dropping the corresponding objects into these fields.
Even better, when we need to change our scene around later, Unity helps make the process as painless as possible, keeping the references hooked up when objects are renamed and moved around.
This is awesome! Freed from these string bindings, we can now work on our app with confidence instead of fear. Remember this the next time you see GameObject.Find in your codebase.
Programmer, drummer and occasional blogger.