Hello, everyone!
I will be writing about every way you have to communicate a MonoBehaviour with another MonoBehaviour. But first, I will start with a few disclaimers!
First, I hope I didn’t forget a way of doing this, if I did, please mention it in the comments.
Second, I will be giving my personal insight about advantages and disadvantages. I recommend you always think critically and use what suits your project. I am a strong believer that there isn’t a silver bullet to solve all problems and everything depends on the context of your project.
Third, take in mind that I am explaining general versions of each way of communication. You can start from one of those and make small modifications that may work better for your needs. The idea is that you get a big picture of all the options which gives you a powerful tool to chose what is suits your project the best.
So let’s get started with the list!
Reference in the inspector
One of the easiest ways is to have a public reference in your MonoBehaviour class. That way, Unity will let you just drag the object from the Hierarchy or the Project tabs.
In the above image, you can see how I have a StatusGridView MonoBehaviour class that I can set to the UnitContainerView MonoBehaviour class.
The easiest way to do this is by just adding the following line of code inside your class:
public StatusGridView statusGrid;
That way, Unity will recognize that StatusGridView, in this case, is a MonoBehaviour and it will let you drag it from the Hierarchy or the Project tab.
In case you don’t like having your classes public because of encapsulation and other things like that, you can also use the SerializeField tag by doing this:
[SerializeField] private StatusGridView statusGrid;
By doing so, you will have the same result but this time you won’t be able to access its value directly from another class. We use a lot the SerializeField tag option at my work because of the encapsulation but in my personal projects, I prefer using public because it lets me use TDD easily in my view objects.
The only downside I see to this approach is that doesn’t work in runtime. If you want to assign a reference in runtime you will have to do it by assigning it from another MonoBehaviour that has reference to yours or, when making an instance of a prefab, saving the reference or else it will be lost.
So let’s recap its pros and cons, at least in my opinion!
Pros
- You can assign MonoBehaviours in the Scene by just dragging them from the Hierarchy
- You can assign MonoBehaviours as prefabs by just dragging them from the Project
- You can make the access to the field private if needed
Cons
- It doesn’t provide a solution by itself when assigning values in runtime
This one is one of the most common and useful ways to do it. It is also probably the most performant one so I believe everyone can manage by just using this approach.
GetComponent<T>
This is another common way of searching for components in the same GameObject. Imagine you have YourComponent in a GameObject and it is AnotherComponent in the same GameObject. If you want to get a reference from the first one to the second one you can easily do in the first one:
var theOtherComponent = GetComponent<AnotherComponent>();
This way, Unity will search for a reference of the other component that your game object has. Take in mind, that if your game object has multiple copies of AnotherComponent script, it will pick one randomly. In that case, you will want to use GetComponents and then filter or search somehow for the component you need.
GetComponents<AnotherComponent>();
I am personally not a fan of this approach unless it is actually needed. I believe this is an expensive approach for Unity because it has to search through the MonoBehaviour components and filter them by their type.
Although this doesn’t mean it can’t be useful. It is a really common approach when two objects collide in Unity. For example, in Yemita we had to use it when two objects collided and we wanted to affect the other game object. We had something like this:
private void OnTriggerEnter(Collider other){
if(other.gameObject.layer == 8)
other.GetComponent<OtherClass>().DoSomething();
}
Here, also the performance was a key thing so what we did was to use Layers in order to filter collisions and don’t have to check that GetComponent<OtherClass> is not null. Consider doing this to avoid unnecessary checks in the Unity physics system or in your code.
Something similar you will have to do for example if you use Raycasts in order to get other game objects which you didn’t have a reference to before.
Also, one small note, you can also use GetComponentInChildren<T> and GetComponentsInChildren<T>. This one will work very similar to GetComponent<T> but will include components in game objects that are children of the game object where your component is.
So, making a recap:
Pros
- You can get reference to multiple components in your game object or it’s children.
- You can get a component reference in runtime of other game objects you didn’t know before but you now do (because a collision for example).
Cons
- It is an expensive way regarding performance of getting components.
- When having multiple components, you may have to filter it.
- It will return null if the component doesn’t exist, meaning you have to somehow check it.
This approach is a dangerous one when you abuse it or you are not careful with its downsides. Anyway, this doesn’t mean it isn’t useful or even needed. I personally use it a lot in games that use physics since I believe it is the best approach for those cases.
FindObjectOfType<T>
I believe this one is the “I still don’t know how to make a Singleton” or “I still don’t control the other two above approaches correctly” way. It is hard for me to believe this one is a good approach but let’s check it out to have an idea of what it actually implies and even find it a useful use.
The idea is that you can do from almost anywhere:
var otherMonoBehaviour = FindObjectOfType<OtherMonoBehaviour>();
This thing is a really expensive thing. It will search through the whole game objects in the scene until it finds the component you are searching for or return null if it doesn’t find it. Even more, you could use FindObjectsOfType<T> to find all the MonoBehaviours of that type.
Even it is a really powerful tool, it has a few downsides that are the performance I already mentioned and the fact that you literally get references to everything, which is kinda dangerous if you are not careful enough. You could change the state of something you didn’t want to.
I think your solution to using FindObjectOfType depends on what you want to do. Here are two cases I believe are the most common ones you would try to use:
- You have a Manager of some kind, maybe a HUDManager with the score and you want to get reference to it and add some points maybe? Even this is not the best approach regarding best practices of code, it is a common thing when you are learning or just making a quick game for a game jam. In that case, I strongly recommend just make a Singleton, you already left that high quality code path but you don’t have to deal with performance issues. In the next part I will explain the Singleton approach if you need it.
- You created things in runtime and now you don’t have reference to them. I believe here the best would had been to save them in vars and just past them through to the object you need. The same would be if you are already in runtime, try to have a reference to something it has the reference you need. Think it as a puzzle. You probably can solve it by using the reference in the inspector or the GetComponent<T> approach at some point and from there pass the reference.
Having said all of these, I believe there is a big use for FindObjectOfType<T> but is not during runtime, instead, I sometimes use it when having to get references in my Editor Tools. Sometimes, you are making an editor tool that manipulates a reference in the Scenes so it is a good idea to get the references that way. Think also that most of the references are already there, maybe you don’t have another game object with the references and a Singleton is a difficult thing to use during editor because the things are probably not initialized yet. For last, consider that even it is an expensive method to call, in runtime you probably have some extra freedom to use the extra resources.
Having said all this, let’s make a recap:
Pros
- It is pretty easy get a reference to any MonoBehaviour that is in the Scene.
- Very useful when making editor tools that manipulate the Scene’s MonoBehaviours.
Cons
- Can’t stress this enough but: very expensive regarding performance. Even more if you are using it during runtime, with a ton of MonoBehaviours and on every update.
Singleton
Another way to get a reference to the things is by using the Singleton way. It is not a good practice at all, it is actually considered an anti-pattern, so make sure this is what you want to do and its dangers of using it.
I already made what I believe was a good explanation on how to make a good Singleton in Unity at my 4 Horsemen of the Game Jams: 4 nasty tricks to get results fast in Unity post. Anyway, I will make a simplified and most common way to implement it here.
Usually, you will have a static field that references the class you are in. This can be something like:
public static MyMonoBehaviourClass Instance;
And then you can, at the Start or Awake method, do something like:
Instance = this;
This way, all classes through all the project will be able to access this class by doing:
MyMonoBehaviourClass.Instance.DoSomething();
Take into consideration that this is the very simplified version of a Singleton. In this approach, anyone could modify the reference of the Instance. This could be modified by making a private instance and making a GetInstance of that for example.
I will make the list of pros and cons where I am going to list the most important cons in my opinion:
Pros
- You can access a class from all the other classes.
- It is a fast and cheap way of doing it.
Cons
- You can access a class from all the other classes. It doesn’t suit too well with the best practices.
- You can only have one instance of the object because of it’s nature. You could make a list but that will need some extra management.
- You have to be careful in Unity when using different scenes. Think on how you are going to manage the reference when moving to another scene.
- It is a difficult thing to work with when doing Test Driven Design.
All these things don’t mean that you shouldn’t use them although I only recommend it if you are in a game jam and need quick results. If not, I personally recommend you search for another approach of doing getting the results you need. Probably with the tools I already mentioned you should be able to.
Wrapping up
These are the ways I believe you can communicate between classes. At this point, I may be forgetting other approaches but I believe these ones are the most common ones between experimented and new programmers.
I didn’t include all the variations of these approaches for example FindGameObjectsWithTag since I am not a fan of using tags and I think it is pretty similar to FindObjectOfType<T>. Or GameObject.Find(“”) name because you will be tied up to the game object name and has similar performance problems to the one I explained.
Sometimes, I mentioned that some of the approaches are not the best regarding best practices or performance. I strongly believe that if you are still learning the basics, don’t feel bad for using this approach. Use the ones you feel more comfortable with and when you feel ready, investigate more about best practices in programming. Remember that your goal will be always making good games and, while you don’t get to performance issues, everyone will be able to enjoy your game without knowing how it was coded.
If you have read everything down here, I want to give you a huge thank you! Please consider leaving a comment if you enjoyed it or have any questions.
Hope you had found it useful!
What about events? Wouldn’t events be a way for behaviors to interact? (although without directly referencing each other)
You are right 🙂 That would be another way of doing it and a more C# like way