Tutorials don’t teach the best practices or the most efficient way to solve a particular problem, just the easiest way. These are sometimes the reason why developers (mostly beginners) are taught to use certain patterns they shouldn’t use if they care about performance, clean code and proper code design patterns. One day, they will certainly also care about these if they don’t already.
Why to avoid Find methods?
Whether GameObject.Find
, GameObject.FindWithTag
, GameObject.FindGameObjectsWithTag
, FindObjectOfType
or FindObjectsOfType
… you should forget and avoid them all in most of the cases. There are various reasons for that.
Horrible Performance
Find
-methods are incredibly slow. They traverse the entire hierarchy in your scene to find one particular or more GameObject’s. The larger the scene, the slower it gets. That means that Find
-methods are a sin for large projects, but they aren’t recommended for any other project either.
Often, people say that Find
methods are generally ok to use in Start()
or Awake()
. That is not the whole truth. Even there, you can’t rescue yourself from the slowness of these methods. Using them there would increase the loading times and you don’t do yourself nor your game a favour. Apart from that, there are also other issues with Find
-methods, not only the performance (but this is the worst thing about them). We’ll come to that in a moment.
Apart from the main issue that they need to traverse the entire hierarchy, there is also another thing that makes Find
-methods that slow. This affects mostly GameObject.Find
. This method requires a string
parameter. Internally, the name of every object is compared to the value of the given parameter and exactly that is expensive due to higher memory allocation on the heap. That’s pretty expensive and triggers the Garbage Collector, which weighs on the CPU in the end.
Partial “string-dependency”
That doesn’t affect every Find
method, but still a few, which happen to be the most frequently used ones. GameObject.Find
, GameObject.FindWithTag
and GameObject.FindGameObjectsWithTag
require a string
parameter.
There are basically two problems associated with this. On the one hand, there is the performance concern mentioned above and on the other hand, there is the “insecurity” of this approach. strings
are not reliable due to human error.
Let’s make an example:
using UnityEngine;
public class SomeBehavior : MonoBehaviour
{
private GameObject greenHouse;
private void Start()
{
greenHouse = GameObject.Find("GreenHouse");
}
}
What if you accidentally misspelled the name of this GameObject whose name is actually “Green House” or “GrenHouse”?
You’d get an error and your entire logic, which depends on a correct reference to this GameObject, wouldn’t work anymore. Then you have to find the mistake first, fix it and do that multiple times if necessary. That’s a bit annoying and leads to an unecessary loss of time.What if you decide to change the name of this object?
You’d have to change allFind
methods that are referring to this object and if you didn’t do that, you’d get errors. Once again, a needless effort that could be saved.What if you had two or more objects with the exact same name?
There is a certain chance that it chooses the wrong object which would lead to errors or unwanted behaviour. This is a not entirely controllable issue, especially in very large scenes with possibly several similar objects.
As you can see, strings
aren’t really reliable. Small negligent mistakes rob you of a lot of time that you could have invested better.
What are the alternatives?
Reference through the inspector
The by far best and most efficient way to get a reference in Unity is achievable using the Inspector. This is done by making a variable (field) visible in the Inspector in order to later drag and drop the reference into the field that has appeared.
If both objects with these two instances, that are supposed to communicate with each other, are NOT being instantiated later, so they’re in the scene from the beginning, you should definitely choose this way.
Example
Manager.cs:
using UnityEngine;
public class Manager : MonoBehaviour
{
[SerializeField] private ExampleClass example; // Reference to the `ExampleClass` instance, which you should assign in the inspector (drag and drop)
private void Start()
{
// Just an example. You call a non-static method in the `ExampleClass` (in the instance you assigned) for example. For that, you need a reference
example.ExampleMethod();
}
}
ExampleClass.cs:
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
public void ExampleMethod() // Example method
{
Debug.Log("You did it! The method has been called successfully.");
}
}
Assignment in the inspector
The [SerializeField]
attribute makes sure that the field is exposed to the inspector, even though the field is not public
.ExampleClass
is the type of this variable, which means that we want a reference to an instance of the ExampleClass
class.example
is the variable name with which we can use this variable.
Now, we only need to specify what instance we want to reference. We can do that in the inspector, as already mentioned, insofar as the instance is available as a component attached to a GameObject in the scene. Unity does some magic in the background and assigns the value to the variable.
This is how the assignment works…
- First, we have a GameObject with this
Manager
component.
- Then, we also have a GameObject with the
ExampleClass
component.
- Lastly, we just need to drag and drop the GameObject containing the desired component, being
ExampleClass
in this case, into the field in the inspector of theManager
component.
Easy as that and it works the same way with a GameObject
reference or any class attached to a GameObject as a component.
Kind of an injection
The downside of the above variant with the sole assignment in the Inspector is that it only works if both objects are also in the scene from the beginning and are NOT being instantiated manually. There are still alternatives. For the following examples, we’ll remain with the Manager
and ExampleClass
classes.
Let’s assume that the GameObject, the ExampleClass
component is attached to, is being instantiated somewhere later, so it is not in the scene from the beginning. Then you can’t simply drag and drop the reference, because this component only exists as a part of a so-called “prefab”. A “prefab” is a blueprint of a GameObject, which can be instantiated as often as you wish, so that exactly such a GameObject exists in the scene. Before you instantiate such a “prefab” however, there is no GameObject and can’t be assigned directly as we learned previously. Here we use a simple trick, a so-called “property injection”.
There is certainly a class that’s responsible for instantiating the object with the ExampleClass
component. Let’s call it Spawner
. This class will contain a method in which this Example
object will be instantiated once the method is called.
We still want a reference to this ExampleClass
instance in the Manager
class. We assume, that the two objects with the Manager
and the Spawner
components are in the scene from the beginning.
Manager.cs:
using UnityEngine;
public class Manager : MonoBehaviour
{
public ExampleClass Example { get; set; } // This will contain the reference to the `ExampleClass` instance
private void CallMethod()
{
// Just an example. You call a non-static method in the `ExampleClass` (in the instance you assigned) for example. For that, you need a reference
Example?.ExampleMethod();
}
}
Spawner.cs:
using UnityEngine;
public class Spawner : MonoBehaviour
{
[SerializeField] private GameObject examplePrefab; // Reference to the prefab that's supposed to be instantiated - assign in the Inspector
[SerializeField] private Manager manager; // Reference to the `Manager`, that needs the `ExampleClass` reference - assign in the inspector
private void Spawn()
{
GameObject exampleObj = Instantiate(examplePrefab, transform.position, Quaternion.identity); // Instantiation of the object
manager.Example = exampleObj.GetComponent<ExampleClass>(); // Assign the instance to the `Example` property in the `Manager` class
}
}
The code of the ExampleClass.cs file remains the same as before for this example. The Manager
class changed however and the Spawner
has been added…
Instead of the serialized variable in form of a field with the
[SerializeField]
attribute, we now have a get-set property, which isn’t exposed to the inspector because that’s not necessary now. Why this is now a property is solely related to the recommendation to use properties instead of fields for externally visible variables. That is an entirely different story however.
We did this, because we won’t assign the reference in the inspector anymore, but externally in the code.The
CallMethod()
function is just an example method in which we call the non-staticExampleMethod()
to demonstrate the usage of the assigned variable. So, this isn’t relevant for the current issue.The
Spawner
class now contains two fields that are both serialized (exposed to the Inspector), because we need to assign both of them by dragging and dropping as we learned it earlier. On the one hand, there is theexamplePrefab
variable containing a simple reference to the Prefab in the assets, so we can instantiate it. On the other hand, we got themanager
variable holding a reference to the already existingManager
instance.Inside the
Spawn()
function, the prefab is first being instantiated and then saved into a local variable, so we can use the reference later on.After that, we use this reference to get the
ExampleClass
component from it using GetComponent<>() . Eventually, we simply assign this component to theExample
property in theManager
instance.
Thus, the Manager
finally gets his reference to the desired ExampleClass
instance.
In case, the Manager gets instantiated and needs a reference to the ExampleClass in the scene
Now we can simply turn this example around in case the object with the Manager
component gets instantiated afterwards and particularly this instance needs a reference to an already existing ExampleClass
.
Manager.cs:
using UnityEngine;
public class Manager : MonoBehaviour
{
public ExampleClass Example { get; set; } // This will save the reference to the `ExampleClass` instance
private void CallMethod()
{
// Just an example. You call a non-static method in the `ExampleClass` (in the instance you assigned) for example. For that, you need a reference
Example?.ExampleMethod();
}
}
Spawner.cs:
using UnityEngine;
public class Spawner : MonoBehaviour
{
[SerializeField] private GameObject managerPrefab; // Reference to the prefab, that's supposed to be instantiated (Manager) - assign in the Inspector
[SerializeField] private ExampleClass example; // Reference to the already existing `ExampleClass` instance - assign in the inspector
private void Spawn()
{
GameObject managerObj = Instantiate(managerPrefab, transform.position, Quaternion.identity); // Instantiation of the object
managerObj.GetComponent<Manager>().Example = example; // Assignment of the `Example` property in the `Manager` component
}
}
The only changes have been made in the Spawner
class. The most significant change affects the last line of the Spawn()
function. Let’s take a closer look at it…
This is how this line looked like before:
manager.Example = exampleObj.GetComponent<ExampleClass>();
There, we get the ExampleClass
component from the newly instantiated object and assign it to the Example
property of the already existing and referenced Manager
instance.
But now the line looks like this:
managerObj.GetComponent<Manager>().Example = example;
Here, we get the Manager
component from the newly instantiated object (again with
GetComponent<>()
). It’s not the ExampleClass
component anymore, it’s the Manager
. We do this to get access to the Example
property, to which we can assign the reference of the example
variable. Remember: The example
variable holds a reference to the already existing ExampleClass
instance.
Note
This way can appear in various forms. It's not a strict approach. You're pretty flexible, so you could for example work with a method where you can pass the reference as a parameter. You may of course play with it to find the way that fits your situation the best.
Singleton-like
Lastly, I want to present the “Singleton” approach as a commonly used way to get a reference. It’s mostly used in situations where later instantiated instances need a reference to something in the scene. Here, we’ll use the Manager
once again, but this time acting as a central element in the scene, holding and distributing references to later instantiated objects.
Essentially, you don’t even need a real singleton. It’s more a static reference to an instance of this Manager
class.
The object, the ExampleClass
is attached to, will be instantiated afterwards. There, you want a reference to a UI-Image in the scene for example.
Manager.cs:
using UnityEngine;
using UnityEngine.UI;
public class Manager : MonoBehaviour
{
public static Manager Instance { get; private set;} // Static reference to a `Manager` instance in the scene
[field: SerializeField] public Image SomeImage { get; set; } // Reference to the UI-Image - assign in the Inspector
private void Awake()
{
Instance = this; // Assign THIS instance to the `Instance` variable. Now everything can access this `Manager` instance, no matter where from
}
}
ExampleClass.cs:
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
private Manager manager; // Reference to the `Manager` instance, currently unassigned
private Image someImage; // Reference to the UI-Image, currently unassigned
private void Start()
{
manager = Manager.Instance; // Get reference to the `Manager` instance
someImage = manager.SomeImage; // Get reference to the `Image` from the `Manager`
}
}
Let’s repeat it again…
First, we create a static field, which you can access anywhere without a specific reference to a
Manager
instance, since this variable is static. This variable is going to hold a reference to aManager
instance.Then we also have a property, which holds a reference to a UI-Image in the scene.
[field: SerializeField]
makes sure that this property is visible in the inspector, so we can assign theImage
component in the inspector once again.field:
creates a so-called "Backing Field" , which is needed for that. I won’t elaborate further, since that would go beyond the focus of this article.Lastly, we assign this current instance to the
Instance
variable. We do this inAwake()
. That’s pretty important to avoid possible conflicts by making sure that the assignment happens as soon as possible, so no other instance tries to access this variable earlier.Now, the class attached to the object that will be instantiated at some point can simply access this
Instance
variable that already contains the reference to theManager
.
This can be done inStart()
for example, since that is called right after the object is “born”.Now that we have a reference to the
Manager
, we can easily take the reference to the “Image” from thepublic
SomeImage
property.
That’s not exactly a singleton. To make this a typical “singleton”, you would have to ensure that only one instance of this Manager
class exists in the scene. However, this is irrelevant for this problem.
Unity has unique approaches to some pretty simple things, but in the end, they are not hard to understand at all or particularly hard to use. For an experienced .NET developer, the concept of assigning references in the Inspector is a bit strange, since “magic in the background” is a foreign word for us. And yet, due to the component-based system and the lack of “dependency injection” in Unity, it offers the best alternative to old familiar ways in C#.