Structuring Your Unity MonoBehaviours
MonoBehaviours, the basic scripts that you write in Unity projects, are often cluttered with functions that are called implicitly and public fields that are accessed and set through the inspector. They quickly become disorganized, making them hard to trace through and understand from the perspective of a developer new to Unity. To fight against this, here are some conventions that I set in the structure of my MonoBehaviours, annotated side-by-side with an example.
- Document your MonoBehaviours with a quick description at the top. This will make it easy for people to understand what responsibilities your behaviour has when diving through the codebase.
- If you use GetComponent within your class, annotate the class with the RequireComponent attribute to make this dependency explicit. This will help avoid script setup errors, as the inspector will automatically add the required component when you attach your script to an object, and warn the user when they try to remove a required component. However, be warned that it will not add the component to objects which already have the script attached.
- Place your constants and static members at the very top of the file. Use constants for any animator parameters that are referenced throughout the file, to ensure that if the parameter name changes, the value in code only needs to be updated in one location that is easy to reference.
- Group your fields that are exposed within the Unity Inspector sidebar(e.g. public fields or fields annotated with [SerializeField]), into a region called “Unity Inspector Fields”. This will make it very explicit which fields can be configured inside the editor, and help keep your file organized.
- Document your inspector fields with the Tooltip annotation. Due to the limited amount of display space in the inspector, longer names are often cut off. A simple tooltip goes a long way towards resolving any name ambiguities, making your script easy to reuse.
- Group your private fields together after the inspector fields. If you end up using a lot of private fields, look into reorganizing the classes to divide up the responsibilities, or separate out groups of fields related to different tasks with a line of whitespace.
- I like placing properties after fields, before methods, since they are syntax sugar for methods but are used like fields. This is completely personal preference.
- Annotate any Unity lifecycle methods(Start, Awake, Update, OnDestroy etc.), event methods, and other functions that are implicitly(or automatically) invoked in your code with the [UsedImplicitly] attribute. This attribute, although included in the UnityEngine.dll, is used by ReSharper to disable code cleanup suggestions for methods that are seemingly unreferenced. It also helps to make the code easier to read for people who are newer to Unity and your codebase, especially for events that are referenced via the inspector.
- Use Awake for hooking up references to other dependencies, e.g. calls to GetComponent, GetComponentInChildren, FindObject etc., and Start for actual startup logic. This ensures that any scripts you access in Start will have all their references hooked up.
- Not used in the example code, but good to follow anyways: If a field does not need to be public, but you want to control it through the inspector, use the [SerializeField] attribute to expose the field in the editor instead. This will ensure that the public interface for your script is kept as small as possible, which in turn ensures that other scripts can’t mess with your state and break your script.
It might seem like a fair amount of bloat initially, but I found that sticking to these practices has really made life easier for other developers when rolling them on to large existing Unity codebases. Let me know what you think!
There’s a good discussion thread for this post on r/gamedev.