{% extends "../../../layouts/post.html" %} {% import "../../../layouts/macros.html" as macros %} {% block article %} # Convenient Unity Attributes Unity offers a range of convenient ways to manipulate and hack its inspector. Unfortunately, thanks to the size of Unity's documentation, these types of methods go unnoticed. I won't attempt to iterate every useful attribute in Unity's library. I encourage you to explore Unity's documentation. # Header [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/HeaderAttribute.html) This simple and effective attribute will display a header above the property to which it is applied. ```cs public class Movement : MonoBehaviour { [Header("Configuration")] public float MaxSpeed = 5f; public float MaxAcceleration = 5f; [Header("Dependencies")] public Rigidbody body; public Camera cam; } ``` {{ macros::image(src="./header-example.png", alt="Example of the inspector") }} # Space [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/SpaceAttribute.html) The `Space` attribute adds user-defined pixel spacing between fields. I don't often reach for this attribute, but it can be handy in cases where a `Header` isn't appropriate. It helps designers scan through fields by creating a small visual break. Unity's documentation illustrates a good use case (with some modifications). ```cs public class Example : MonoBehaviour { [SerializeField] private int health = 0; [SerializeField] private int maxHealth = 100; [Space(10)] // 10 pixels of spacing here. [SerializeField] private int shield = 0; [SerializeField] private int maxShield = 0; } ``` A 10-pixel space is between `maxHealth` and `shield`. {{ macros::image(src="./space-example.png", alt="Screenshot of example component") }} # Tooltip [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/TooltipAttribute.html) Unsurprisingly, the `Tooltip` attribute creates a tooltip over a field when hovered. The use case for these types of in-engine documentation is nearly endless: providing context, usage hints, and so much more. I won't use Unity's example because `Range` is better suited for that use case. We'll cover that attribute next. ```cs public class Movement : MonoBehaviour { [SerializeField] [Tooltip("Measured in meter/sec")] private float MaxSpeed = 5f; } ``` {{ macros::image(src="./tooltip-example.png", alt="Screenshot of tooltip attribute") }} Although in this particular case, I may suggest a custom attribute that appends "m/s" to the end of the field, a tooltip can provide this type of clarity and context. # Range [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/RangeAttribute.html) We'll revisit Unity's `Tooltip` example with a different attribute: `Range`. ```cs public class Example : MonoBehaviour { [Tooltip("Health value between 0 and 100.")] int health = 0; } ``` This component wants to restrict `health` between 0 and 100. Rather than enforcing that restriction through code, it relies on the designer to abide by this restriction. Let's correct it using `Range`. ```cs public class Example : MonoBehaviour { [SerializeField, Range(0, 100)] private int health = 0; } ``` {{ macros::image(src="./range-example.png", alt="Example of Range attribute") }} The inspector is now enforcing the restriction and displaying it as a slider between the two values. I find this helps reason about the range better than a number field. # SerializeField [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/SerializeField.html) Of all the attributes offered by Unity, this one sees the most use in my code. This attribute forces Unity's inspector to serialize and display the field in the inspector *regardless of accessibility*. This pattern is particularly effective for exposing values in the inspector while keeping them inaccessible to the codebase. Consider this simple example. ```cs public class Health: MonoBehaviour { public int Health = 5; public UnityEvent Damaged; public void TakeDamage(int damage) { if (damage > 0) { Damaged?.Invoke(); Health = Mathf.Max(Health - damage, 0); } } } ``` Setting `Health` to `public` allows a designer to set the initial health and tweak it during gameplay. These types of considerations are great for playtesting and debugging. Unfortunately, *anything* can modify `Health` without calling `TakeDamage(int)` which, can introduce an undesirable side effect: the `Damaged` event won't fire. It's possible to mitigate this issue by using a C# property. ```cs public class Health : MonoBehaviour { private int health = 5; public int Health { get => health; set { if (value < health) { Damaged?.Invoke(); } health = value; } } public UnityEvent Damaged; } ``` It's now impossible to modify `Health` without firing `Damaged`. However, by default, Unity does **not** render C# properties in the inspector. A designer wanting to set the initial health or tweak the health value during playtesting will be unable. Let's try using `SerializeField` instead. ```cs public class Health : MonoBehaviour { [SerializeField] private int health = 5; public UnityEvent Damaged; public void TakeDamage(int damage) { if (damage > 0) { Damaged?.Invoke(); health = Mathf.Max(health - damage, 0); } } } ```` `health` will be editable in the inspector, but only `TakeDamage` will be accessible to code. It's also possible to combine the `property` and `SerializeField` approaches by exposing a private backing field to the inspector. # HideInInspector [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/HideInInspector.html) This attribute acts as the reverse of `SerializeField`: this will hide a `public` field from the inspector. It is helpful for hiding complexities from a designer without rendering it inaccessible to the rest of the code. Let's hide some component dependencies without making them inaccessible. ```cs [RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))] public class Movement : MonoBehaviour { [HideInInspector] public Rigidbody Body; [HideInInspector] public CapsuleCollider Collider; private void Start() { Body = GetComponent(); Collider = GetComponent(); } } ``` {{ macros::image(src="./hideininspector-example.png", alt="Example of HideInInspector attribute") }} # RequireComponent [Documentation](https://docs.unity3d.com/2022.1/Documentation/ScriptReference/RequireComponent.html) This attribute doesn't neatly fit into this list, although I'm sure it's important enough to discuss. `RequireComponent` ensures that the specified component is attached to a GameObject. It's great for wrapping/enhancing built-in components or just assuring the existence of a dependency. `RequireComponent` will automatically add any required components which are missing. It will also prevent removing them while using the inspector. It's added to the `MonoBehaviour` rather than a field. ```cs [RequireComponent(typeof(Rigidbody))] public class Movement : MonoBehaviour { private Rigidbody body; private void Start() { body = GetComponent(); } } ``` Unity adds a `Rigidbody` if it doesn't already exist on the GameObject. Attempting to remove `Rigidbody` will cause an error. {{ macros::image(src="./remove-requiredcomponent.png", alt="Example of the error message") }} {% endblock article %}