When you create a new C# class in Unity, it automatically inherits from the MonoBehaviour
class, which is Unity’s base class for components. In Unity, you tend to create a lot of components, but it’s important to keep in mind that you don’t have to.
When I was new to Unity, I thought everything should inherit from MonoBehaviour
- that’s just how you work in Unity! Some of my students have also had this misconception. In fact, there are often scenarios where it makes more sense to not inherit at all, or to inherit from another base class. This post explains situations where it makes sense to use MonoBehaviour, and some cases where you’d be better off without it.
Why Use MonoBehaviour?
There are some instances where you can only achieve what you are looking to achieve if you use a MonoBehaviour.
You Can Attach Them to GameObjects
Probably the defining architectural characteristic of Unity is that your game world is built from “Game Objects” that have “Components” attached to them. This is how you build gameplay, in a lot of cases. This is valuable for code reuse, usability, and empowerment. If you design a component well, you empower designers to build and tweak gameplay without significant engineering support. If you’re thinking ahead, you can also create components as “building blocks” that can be reused in many different scenarios.
This is the biggest benefit of using MonoBehaviour
.
- You can attach any subclass of
MonoBehaviour
to a GameObject in your scene. - As a component on a GameObject, variables in the class can be exposed in the Inspector for designers to tweak.
- As a component on a GameObject, it can be serialized and saved to the disk either in a Unity scene, or as part of a Unity prefab.
But keep in mind, you can also create “building blocks” of reusable code with plain C# classes - there’s no problem with that!
You Can Receive Unity Events
Usually the first thing I miss if I decide to not inherit from MonoBehaviour
is that my class no longer receives all those helpful Unity events – like Awake, Start, Update, etc. This also includes many app lifecycle events, such as OnApplicationPause or OnApplicationQuit. I’ve run into many situations where it would be helpful to receive those events, but I’m not inside a MonoBehaviour subclass.
It’s still possible to receive such events though! You simply create public methods in the class that are called by some other MonoBehaviour
when the event occurs. For example, I might have a “Unity Events” component on a GameObject that simply passes events from that MonoBehaviour on to my non-MonoBehaviour classes.
You Can Receive Collision and Physics Events
MonoBehaviours get physics events, such as OnTriggerEnter, OnCollisionExit, etc. As far as I know, this is the only way to get physics events from the engine. If you want to get physics data outside of MonoBehaviours, you can maybe query some static methods of the Physics class, but your options will be limited.
If you want to receive trigger or collision events, a MonoBehaviour is the way to go.
You Can Easily Run Coroutines
Unlike a normal function, a coroutine is a function that can execute over multiple frames. For example, a coroutine can tell a character to walk to point X on one line, wait until the character gets to the point on the next line, and then execute a third line 10 seconds later when the character actually gets to the point. This capability makes coroutines great for scripted sequences or animations. Without coroutines, this would require some sort of asynchronous execution and callback system.
Coroutines are quite helpful, but the function to start a coroutine – named StartCoroutine
– is a function in the MonoBehaviour
class. Therefore, the only way to start and run a coroutine is if you have a MonoBehaviour
to run it for you. Note that this doesn’t mean the coroutine must be inside of a MonoBehaviour; you simply need a reference to a MonoBehaviour
to run a coroutine. Additionally, if the MonoBehaviour running the coroutine is deleted or disabled, the coroutine also stops running.
There have been plenty of times I’ve wanted to run a coroutine on a non-MonoBehaviour, or simply needed a coroutine to continue running, even if the MonoBehaviour was disabled for some reason. As a result, one of my go-to utility classes is a “CoroutineRunner” singleton, which is always active and available to run coroutines for me.
You Can Receive Events from Native Code
Another limitation imposed by Unity is that if you want to use or create a Native plugin (very useful for things like Game Center, In-App Purchase, Analytics, Facebook, etc), the main way you communicate from native code to managed C# code is through a MonoBehaviour.
I believe this limitation exists because Native communication uses the SendMessage
Unity feature.
UnitySendMessage("GameObjectName1", "MethodName1", "Message to send");
Notice that you must specify the name of a method attached to a GameObject. Therefore, you must use a MonoBehaviour
for this purpose.
An alternative to UnitySendMessage for plain old classes would be function pointers (using IntPtr). These are a bit more complicated to implement, but also my preferred approach - perhaps I’ll write a post about this topic in the future!
You Can Easily Serialize Data
This is actually a huge benefit of MonoBehaviours that is so seamless, you probably don’t notice it. Serialization is the process of taking runtime data (say, a list of data in a class) and saving it to a storage format (such as a text file) so that you can persist it between runs of your game, or the Unity editor itself. Serialized data can be transferred, put into version control, or saved on your hard drive.
In some engines, you might need to save such data to XML, JSON, or binary files. In Unity, you can actually benefit from the fact that any GameObject with a MonoBehaviour
attached is easily serialized by creating a prefab out of the GameObject. All data saved in the GameObject – position data, child objects, values set on attached components – is serialized into the prefab format (which is either a binary representation or a text-based YAML representation, based on your project settings).
This is a big deal because it can greatly reduce the effort required to save settings or configuration data for your game. You can create an “AudioSettings” prefab that stores variables related to audio – default volume, number of audio sources – or maybe a settings prefab that stores bundle ID, version #, or debug options. It is then easy enough to open the prefab and read the values in game; much easier than implementing JSON, XML, or some proprietary serialization and deserialization scheme yourself.
At the same time, it’s worth nothing that for serialized data that doesn’t need to be attached to a GameObject - like audio settings, for example - it might make more sense to use the ScriptableObject
base class. Again, a large topic worthy of it’s own post!
Why NOT Use MonoBehaviour?
Clearly, MonoBehaviours have a lot of benefits. Why would you forgo them in favor of “plain old” classes?
They Must Be Attached to GameObjects
The MonoBehaviour’s great strength can also sometimes be a weakness. In “normal” C# code, you can just use the new
keyword if you want to create a new instance of a class. But you can’t do that with MonoBehaviours - you must call AddComponent
to create a new instance that’s attached to a GameObject. While MonoBehaviours are great for gameplay-related logic and interacting with the game world, they can be cumbersome - or worse architecturally misguided - in some circumstances.
Take, for example, an InventoryItem
class that holds information about some inventory item that the player has. Can it be a MonoBehaviour on a GameObject? Sure. Should it be? Maybe not. If the player has 400 inventory items, it’s probably overkill to attach 400 components to a GameObject. It would make more sense to have this be a normal class. Perhaps when you view the inventory, we might make some UI components to represent the item in the inventory, but the idea of an “item in our inventory” is not so tangible within the game world, so it might make more sense to just store it in a normal class.
Sometimes, you just want to call “new” and be done with it. You don’t want or need the overhead of a MonoBehaviour, so avoiding it is a good idea.
They Can’t Be Static
You can create a singleton MonoBehaviour. They provide static-like functionality in MonoBehaviour form, and they are a great solution for static-like behavior.
But sometimes, you just want to create a static class – maybe for ease of access, or to host a collection of utility methods, or to act as a manager class. A MonoBehaviour
can contain static methods, but cannot itself be static.
I guess what I’m getting at here is that MonoBehaviours are intended for creating components that are conceivably reusable. They are designed to be instanced. You can use a singleton, but the MonoBehaviour implementation isn’t bulletproof – its possible to get more than one instance of a singleton MonoBehaviour in a scene. A static class won’t have this problem.
There is Some Overhead
And my final point is also minor, but worth mentioning nonetheless: as the first part of this article shows, MonoBehaviours provide you with a lot of functionality. Don’t need any of it? Don’t use them. The concept of a “MonoBehaviour” doesn’t map very well conceptually to any real world object, but I guess you could say that it is meant to model a the behavior of some object in your game world. In keeping with inheritance best practices, if you can’t say that your subclass “is a” behaviour of an object in your world, then it might not be a good fit.
Ultimately, in the name of keeping things simple and reducing unneeded overhead, I tend to avoid using MonoBehaviour unless I identify a good reason for it.
Conclusion
MonoBehaviours are central to Unity’s architecture, and a common tool in Unity games. However, it’s important to realize it’s not your only tool. It’s valuable to know when it is or isn’t appropriate to use it.