Unity Field Validation: Open Snippit

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

Unity Field Validation

Open Snippit
The Problem

When you expose a field in unity:

class World : MonoBehaviour


{
Player _player;
}

you don't want it to be public, but you do want to validate it. So

class World : MonoBehaviour


{
[SerializeField]
Player _player;
}

you want to ensure it gets assigned/validated.


You could:

void Awake() => _player = GetComponent<Player>();

That works...but I don't want to have scripts doing work at start, that could be
done at edit time easily enough and save performance.

or you could do the equivalent of:

[RequireComponent(typeof(Player))]
class World : MonoBehaviour
{
[SerializeField]
Player _player;
}

That will enforce it... but it means it HAS to be on this object and gets
annoying removing components because if a dependency chain.
What I Want

I want to ensure that I didn't forget to drag it in the inspector, but isn't a lot of
work to either write or to maintain.

an earlier solution I have used was:

[RequireComponent(typeof(Player))]
class World : MonoBehaviour
{
[SerializeField]
Player _player;

void Awake()
{
if(_player == null) Debug.LogError("Player cannot be null!");
}

That does what I want, but is a lot of messy code to add, especially since I
need to write one of those for every object.

I can simplify this again with a simple extension method:

public static void RequiredBy<T>(this T t,MonoBehaviour item) where T : Component


{
if (t&nbsp;== null)
Debug.LogError($"<color=yellow>{typeof(T).Name}</color>&nbsp;required&nbsp;by&nbsp;
<color=blue>{item.name}</color>", item);
}

Which allows the much cleaner syntax of:

void Awake()
{
_player.RequiredBy(this);
}

This looks a lot nicer but I have to remember to add a line for every single
required field. Seeing as I do this a lot, the end result starts to look pretty
heavy:

class World : MonoBehaviour


{
[Header("Dependencies")]
[SerializeField]
Player _player;
[SerializeField]
Shop _shop;
[SerializeField]
Wallet _wallet;

void Awake()
{
_player.RequiredBy(this);
_shop.RequiredBy(this);
_wallet.RequiredBy(this);
}

}
Cleaning the Syntax

Personally I don't like giving so much real estate to something as non


descriptive to my actual intention as a developer as "validation".

So what if we take a queue from the [SerializedField] and make our own
attribute:

[AttributeUsage(AttributeTargets.Field)]
public class RequiredAttribute : Attribute {&nbsp;}

So now with the attribute we can simplify the check of what is required:

public static void Validate<T>(this T obj) where T : MonoBehaviour


{
var req = RequiredFields(obj);
foreach (var rInfo in req)
{
var val =&nbsp;rInfo.GetValue(obj);
switch (val)
{
case Object&nbsp;unityObj&nbsp;&nbsp;when&nbsp;!unityObj:
case null:
Debug.LogWarning($"Required&nbsp;<color=yellow>
{rInfo.Name</color>&nbsp;missing&nbsp;from&nbsp;{obj.name}",&nbsp;obj);
break;

}
}
}

So now instead of having to write a new validation line for each field we can
search for each [Required] attribute and apply it to all of them, so now the
code is a lot cleaner..

class World : MonoBehaviour


{
[Header("Dependencies")]
[SerializeField,Required]
Player _player;
[SerializeField,Required]
Shop _shop;
[SerializeField,Required]
Wallet _wallet;

void Awake() => this.Validate();


}

this is a lot better, one line per script to validate all the fields!
... but what if we forget to write that line? we can do better:

#if UNITY_EDITOR
public class CheckMissingFields
{
[RuntimeInitializeOnLoadMethod]
static void TryValidate()
{
foreach (var behaviour in Object.FindObjectsOfType<MonoBehaviour>())&nbsp;
behaviour.Validate();
}
}
#endif

Now we can ask unity to automatically validate all the fields if we try to load
the project!
The Final Result

So the final validation code looks like:


collapse:open

class World : MonoBehaviour


{
[Header("Dependencies")]
[SerializeField,Required]
Player _player;
[SerializeField,Required]
Shop _shop;
[SerializeField,Required]
Wallet _wallet;
}

And whenever a field is missing it will automatically display an error in the


inspector, an error that tells you what field, on what script is missing and if
you click it... will ping the script in the hierarchy no matter where it is located
in the hierarchy!

The Source Code

Open the File

You might also like