Unity, as a component-based game engine, strongly encourages you to use “components” attached to “game objects” as the basis for your game. The starter documentation points you towards components, and all new scripts you create in Unity automatically inherit from Monobehaviour – which is the class you inherit from in Unity to create a component.
While it is certainly easy to use Monobehaviours, it isn’t always the best solution: you could also just create a class that doesn’t inherit from anything. Before making that decision, you ought to understand what Monobehaviours offer you over a plain old class. That’s what this post is all about.
Why Use a Monobehavior?
There are several benefits associated with Monobehaviours that would make them a better choice than a plain old class. Let’s enumerate them, shall we?
1) You can attach them to GameObjects.
Probably the biggest architectural sticking point for the entire Unity engine is that you can use Monobehaviours to add “components” to GameObject instances in your scene. This is valuable for code reuse and to expose variables and functionality to designers. If you design a component well, designers can simply “stick” them onto any GameObject to imbue that GameObject with new functionality.
This is the most obvious benefit of Monobehaviours; if you need a class to attach to a GameObject, then they are a must.
2) 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 OnLevelWasLoaded, OnApplicationPause, OnApplicationQuit. I’ve run into many situations where I’ve decided to not inherit from Monobehaviour, but still needed these callbacks.
It is still possible to receive such events in a standard class. 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. This works, but it is much more cumbersome than just using a Monobehaviour.
3) You can receive collision and physics events.
This is sort of a subset of the previous point, but Monobehaviours get physics events, such as OnTriggerEnter, OnCollisionExit, etc. As far as I know, this is the only way to get such physics data from the engine. If you want to get physics data outside of Monobehaviours, you can maybe perform raycasts through the Physics class, but it is much more cumbersome.
If you want to receive trigger or collision events, a Monobehaviour is probably the way to go.
4) You can easily run Coroutines.
Without going into too much detail, a coroutine is a handy construct that allows you to execute a function that has special “Wait” or “Yield” commands in them. They are very handy for situations where you want to do something through script, but then wait a certain amount of time before continuing to the next command in the script. They act like a rudimentary form of threading that has many of the benefits with few of the added complexities.
They are quite helpful, but the function to start a coroutine – aptly 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.
I personally feel that this is an unfortunate limitation in the engine; I see no reason that you must have a Monobehaviour to run coroutines. If I need to run coroutines from a standard class, I usually just keep a “CoroutineRunner” component reference of some sort and use it to run any coroutines I need.
5) You can communicate with Native.
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 only way you can communicate from Native to your C# or java-scripts is through a Monobehaviour.
I believe this limitation exists because Native communication uses the SendMessage Unity feature, which is Monobehaviour functionality. I think it is kind of a crappy limitation, but what can you do? If you want to communicate with Native and back, you need to be using a Monobehaviour for it to work.
6) You can easily serialize data.
This is actually a huge benefit of Monobehaviours that is so seamless, you probably barely 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 load it up between play sessions or, in Unity’s case, editor sessions.
In some engines, you might need to save such data into registry or XML or JSON files to “save” data. In Unity, you can actually benefit from the fact that any GameObject with a Monobehaviour attached to it 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’s settings).
This is actually a big deal because it can greatly reduce the effort required to save settings or config data for your game. You can create an “AudioConfig” 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.
Why Not Use a Monobehaviour?
Clearly, Monobehaviours have a lot of benefits – there aren’t too many instances where not using Monobehaviour will score you points. However, let me just mention why I sometimes avoid Monobehaviours and choose other options.
1) They must be attached to GameObjects.
This is a relatively minor thing, but you don’t want to attach every class instance you have to a GameObject! Monobehaviour-based components are great for gameplay-related things and reacting to the game world. A lot of times, they are even great for manager-like classes.
But 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? Eh. A class like that holds data that could be used at a later time to create GameObjects – but the conceptual idea of an InventoryItem doesn’t really have an in-game “object”, so I’d probably avoid needing to attach it to a GameObject. Sometimes, you just want to call “new” and be done with it.
2) They can’t be static.
Yes, you can create a singleton Monobehaviour. It isn’t even difficult. There aren’t really any downsides to doing so. They provide static-like functionality in Monobehaviour form, and they are a great solution if you want a Monobehaviour to be a singleton.
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, so…don’t use it in these cases.
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 entirely possible to get more than one instance of a singleton Monobehaviour in a scene. A static class or proper singleton will not have this problem.
3) They have overhead and implications.
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 behaviour of some world object. In keeping with inheritance best practices, if you can’t say that your subclass “is a” monobehaviour, then it might not be a good fit.
Ultimately, in the name of keeping things simple and reducing unneeded overhead, I will avoid a Monobehaviour if I am simply not making use of any of its benefits.
Discussing code architecture can often leave you with the wispy feeling of having really done nothing at all, but I think there are important points to glean regarding Monobehaviours. When I was first starting with Unity, I found myself struggling to understand why I always inherit from Monobehaviour, and whether there were other “acceptable” ways of doing things. And so it turns out that while Monobehaviours are a big part of Unity’s sauce, it is just a tool in your toolbox – it is valuable to know when it is appropriate to use it.