These notes are about editing skills for writing a more readable, better organized script in Unity. The Inspector is the interface your designers and artists use all day; an hour spent on it repays itself many times over.
Exposing fields without making them public
In a MonoBehaviour you can declare many types of public variable and set most of them in the Inspector.
C#public int age;
The problem is that public also means any other script holding a reference can read and write the field. Usually you want the Inspector convenience without the access. Declare the field private and add a [SerializeField] attribute:
C#[SerializeField] private int age;
The field still shows up in the Inspector, but it stays private to other code.
The inverse also exists. A public field that should not be serialized — because you assign it at runtime, or because serializing it would be meaningless — gets [NonSerialized]:
C#[System.NonSerialized] public Transform runtimeTarget;
And [HideInInspector] public int index; keeps a field public and serialized but hides the row, which is useful for values another script writes and nobody should hand-edit.
Attributes that shape the layout
Attributes in square brackets are what regulate how the script appears. These are the ones worth memorizing:
C#[Header("Movement")] // a bold section title with a gap above it
[Tooltip("Units per second at full input.")] // text shown on hover
[Range(1, 10)] // a slider instead of a number box
[Min(0)] // clamps the lower bound
[TextArea(3, 10)] // multi-line text box, min and max rows
[Space(12)] // vertical gap in pixels
[SerializeField] private float moveSpeed = 5f;
[Header] and [Space] cost nothing and do more for readability than any other change. A component with twenty ungrouped fields is unusable; the same twenty fields under four headers are fine.
Two more that show up constantly:
C#[RequireComponent(typeof(Rigidbody))] // on the class: Unity adds the dependency automatically
public class Mover : MonoBehaviour { }
[ContextMenu("Reset Path")] // on a method: run it from the component's ⋮ menu
private void ResetPath() { }
[RequireComponent] is a small thing that prevents a whole class of null-reference bug: the component can no longer exist without its dependency, and Unity will refuse to let you remove the Rigidbody while the Mover is attached.
[ContextMenu] is the cheapest possible tool: any editor-time action you would otherwise write a custom editor for — regenerating data, snapping to the ground, clearing a cache — can be a private method with this attribute.
Conditional and validated fields
OnValidate runs in the editor whenever a serialized value changes. It is the right place to keep derived state consistent, and to catch a bad value at authoring time rather than at runtime:
C#private void OnValidate()
{
if (segmentCount < 1) segmentCount = 1;
if (mesh != null && mesh.isReadable == false)
Debug.LogWarning($"{name}: mesh is not readable", this);
}
Keep OnValidate cheap and side-effect-free outside the component itself. It is called more often than you think, including on domain reload and prefab instantiation.
When to write a custom editor
The attributes above cover most cases. Reach for [CustomEditor] or a PropertyDrawer only when you need something they genuinely cannot express:
- a field whose visibility depends on another field's value,
- a scene-view handle for editing a position or radius directly,
- a preview, a validation summary, or a button that does real work.
A PropertyDrawer is usually the better choice: it targets one serializable type and works everywhere that type appears, including inside lists, whereas a CustomEditor replaces the whole component's inspector and has to be maintained as the component grows.
The rule I follow: if I am writing editor code to save myself clicks, the attribute version is almost always enough. If I am writing it so that someone else does not make a mistake, the custom drawer is worth it.