Creating Custom RootObjectValidators

This tutorial will show you how to create custom RootObjectValidators by implementing a validator for a ScriptableObject.

RootObjectValidators are great for validating Unity objects such as ScriptableObjects, Materials and Components, as you only get a warning / error message for the object itself which we call the root. Unlike the ValueValidator referencing a root object inside another object will not throw an additional warning / error. Let's go through an example to see how they work.

If you're already familiar with creating other validator types, just look at the sample code and see what's different. All validator types have very similar code and the tutorials for each differ only slightly.

First we create the validator class and add the required boilerplate code. Alternatively, you can also create a new validator by right-clicking in the project folder and navigating to Odin Validator > Create Validator > Root Object Validator. This will create a new file for you and automatically add the boilerplate code. Odin tries to predict the correct code by looking at the name you chose for the validator, but you may have to change it a bit to make it work. You should now have something that looks similar to this code:

using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ItemValidator))]

public class ItemValidator : RootObjectValidator<Item>
{
    protected override void Validate(ValidationResult result)
    {
    }
}
Let's go through it and explain what we're doing.
using Sirenix.OdinInspector.Editor.Validation;

The RootObjectValidator type is in the Sirenix.OdinInspector.Editor.Validation namespace, so we need to include it.

[assembly: RegisterValidator(typeof(ItemValidator))]

We register the validator with the RegisterValidator attribute. There is another attribute that can be used to register a validator called RegisterValidationRule. The rest of the code is identical no matter which one you use, but registering a validator as a validation rule can give you additional functionality. To learn more about it, check out the following tutorial. Validators vs Validation Rules

Do not forget to register the validator, or it will not be picked up by the validation system.

public class ItemValidator : RootObjectValidator<Item>
{
    protected override void Validate(ValidationResult result)
    {
    }
}

We create the Validator class and inherit from RootObjectValidator<T>. Since we want to validate our Item ScriptableObject, we need to pass Item as a type parameter. You can also see that we're overriding the Validate method. This method is the bread and butter of any validator and is responsible for executing the validation logic and setting the appropriate validation result.

Let's create our small test ScriptableObject and then implement the actuall validation logic.

// Created in a new file called Item.cs
using UnityEngine;

[CreateAssetMenu]
public class Item : ScriptableObject
{
    public string Name;
    public string Description;
    public int Damage;
}
using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ItemValidator))]

public class ItemValidator : RootObjectValidator<Item>
{
    protected override void Validate(ValidationResult result)
    {
        if (string.IsNullOrEmpty(this.Value.Name))
        {
            result.AddWarning("Please provide a name for the item.");
        }
        if (string.IsNullOrEmpty(this.Value.Description))
        {
            result.AddWarning("Please provide a description for the item.");
        }
        if (this.Value.Damage > 100 || this.Value.Damage <= 0)
        {
            result.AddWarning("The damage has to be between 0 and 100.");
        }
    }
}

As you can see we check all values of the item ScriptableObject and make sure that they have sensible values. this.Value refers to the currently validated value, in our case that's the item.

The result object is your main way of interacting with the validation system. Whenever your validator finds an issue you can add a warning / error to it using

  • .AddWarning()
  • .AddError()

Alternatively, you can also use the .Add() method, which takes a ValidatorSeverity argument. This is useful for validation rules (see Validators vs Validation Rules) as it allows you to configure the severity of this validator inside of the editor.

You can also add Buttons, Fixes, Metadata, and Context Menu Items by calling these methods after you have added warning / error.

  • .WithFix() (see Creating Custom Fixes)
  • .WithButton()
  • .WithMetaData()
  • .WithContextClick()
  • .WithSceneGUI()
  • .WithSelectionObject()
  • .EnableRichText()

If you want to draw something in the scene as the result of a warning / error you can do so by using the .WithSceneGUI() method.
All of these method calls can be chained since they use a builder pattern.

That's pretty much all you need to create a custom RootObjectValidator.
The validation logic can of course be a lot more sophisticated, but the general workflow is always the same.

Validators vs Validation Rules

You already know how to create custom validators and are interested in the difference between Validators and Validation Rules? Then this tutorial is right for you.

Validators vs Validation Rules

Custom Fixes

You want to add a button to your validator that executes a custom fix? Then this tutorial is right for you.

Creating Custom Fixes

Other Validator Types

Are you interested in the other validator types? Check out this overview to see which one is right for your use case.

Validator Types Overview